導(dǎo)語:互聯(lián)網(wǎng)產(chǎn)品中的檢索功能隨處可見。當(dāng)你的項(xiàng)目規(guī)模是百度大搜|商搜或者微信公眾號搜索這種體量的時候,自己開發(fā)一個搜索引擎,加入各種定制的需求和優(yōu)化,是非常自然的事情。但如果只是普通的中小型項(xiàng)目甚至創(chuàng)業(yè)團(tuán)隊(duì)|創(chuàng)業(yè)項(xiàng)目,直接拿輪子則是更合理的選擇。
創(chuàng)新互聯(lián)服務(wù)項(xiàng)目包括瑪曲網(wǎng)站建設(shè)、瑪曲網(wǎng)站制作、瑪曲網(wǎng)頁制作以及瑪曲網(wǎng)絡(luò)營銷策劃等。多年來,我們專注于互聯(lián)網(wǎng)行業(yè),利用自身積累的技術(shù)優(yōu)勢、行業(yè)經(jīng)驗(yàn)、深度合作伙伴關(guān)系等,向廣大中小型企業(yè)、政府機(jī)構(gòu)等提供互聯(lián)網(wǎng)行業(yè)的解決方案,瑪曲網(wǎng)站推廣取得了明顯的社會效益與經(jīng)濟(jì)效益。目前,我們服務(wù)的客戶以成都為中心已經(jīng)輻射到瑪曲省份的部分城市,未來相信會繼續(xù)擴(kuò)大服務(wù)區(qū)域并繼續(xù)獲得客戶的支持與信任!
ElasticSearch就是這樣一個搜索引擎的輪子。更重要的是,除去常規(guī)的全文檢索功能之外,它還具有基礎(chǔ)的統(tǒng)計(jì)分析功能(最常見的就是聚合),這也讓他變得更加強(qiáng)大和實(shí)用。
還在用數(shù)據(jù)庫的like來實(shí)現(xiàn)產(chǎn)品的全文檢索嗎?拋棄她,用ElasticSearch吧~
ElasticSearch(下簡稱ES)是基于Lucene的一個開源搜索引擎產(chǎn)品。Lucene是Java編寫的一套開源文檔檢索的基礎(chǔ)庫,包括詞、文檔、域、倒排索引、段、相關(guān)性得分等基本功能,而ES則是使用了這些庫,搭建的一個可以直接拿來使用的搜索引擎產(chǎn)品。直觀地理解,Lucene提供汽車零部件,而ES直接賣車。
說起ES的誕生,也是個很有意思的故事。ES的作者Shay Banon——“幾年前他還是一個待業(yè)工程師,跟隨自己的新婚妻子來到倫敦。妻子想在倫敦學(xué)習(xí)做一名廚師,而自己則想為妻子開發(fā)一個方便搜索菜譜的應(yīng)用,所以才接觸到Lucene。直接使用Lucene構(gòu)建搜索有很多問題,包含大量重復(fù)性的工作,所以Shay便在Lucene的基礎(chǔ)上不斷地進(jìn)行抽象,讓Java程序嵌入搜索變得更容易,經(jīng)過一段時間的打磨便誕生了他的第一個開源作品Compass,中文即'指南針'的意思。之后,Shay找到了一份面對高性能分布式開發(fā)環(huán)境的新工作,在工作中他漸漸發(fā)現(xiàn)越來越需要一個易用的、高性能、實(shí)時、分布式搜索服務(wù),于是他決定重寫Compass,將它從一個庫打造成了一個獨(dú)立的server,并將其改名為Elasticsearch?!?/p>
可見鼓搗起來的程序員是多么有愛,雖然據(jù)說Shay Banon承諾給妻子的菜譜搜索還沒問世......
本文大概地介紹了ES的原理,以及Wetest在使用ES中的一些經(jīng)驗(yàn)總結(jié)。因?yàn)镋S本身涉及的功能和知識點(diǎn)非常廣泛,所以這里重點(diǎn)挑出了實(shí)際項(xiàng)目中可能會用到,也可能會踩坑的一些關(guān)鍵點(diǎn)進(jìn)行了闡述。
集群(Cluster): ES是一個分布式的搜索引擎,一般由多臺物理機(jī)組成。這些物理機(jī),通過配置一個相同的cluster name,互相發(fā)現(xiàn),把自己組織成一個集群。
節(jié)點(diǎn)(Node):同一個集群中的一個 Elasticearch主機(jī)。
主分片(Primary shard):索引(下文介紹)的一個物理子集。同一個索引在物理上可以切多個分片,分布到不同的節(jié)點(diǎn)上。分片的實(shí)現(xiàn)是Lucene 中的索引。
注意:ES中一個索引的分片個數(shù)是建立索引時就要指定的,建立后不可再改變。所以開始建一個索引時,就要預(yù)計(jì)數(shù)據(jù)規(guī)模,將分片的個數(shù)分配在一個合理的范圍。
副本分片(Replica shard):每個主分片可以有一個或者多個副本,個數(shù)是用戶自己配置的。ES會盡量將同一索引的不同分片分布到不同的節(jié)點(diǎn)上,提高容錯性。對一個索引,只要不是所有shards所在的機(jī)器都掛了,就還能用。主、副本、節(jié)點(diǎn)的概念如下圖:
索引(Index):邏輯概念,一個可檢索的文檔對象的集合。類似與DB中的database概念。同一個集群中可建立多個索引。比如,生產(chǎn)環(huán)境常見的一種方法,對每個月產(chǎn)生的數(shù)據(jù)建索引,以保證單個索引的量級可控。索引->類型->文檔,ES中的文檔以這樣的邏輯關(guān)系組織了起來。
類型(Type):索引的下一級概念,大概相當(dāng)于數(shù)據(jù)庫中的table。同一個索引里可以包含多個 Type。 個人感覺在實(shí)際使用中type這一級常常用的不多,直接就在一個索引中建一個type,在這個type下去建立文檔集合和進(jìn)行搜索了。
文檔(Document):即搜索引擎中的文檔概念,也是ES中一個可以被檢索的基本單位,相當(dāng)于數(shù)據(jù)庫中的row,一條記錄。
字段(Field):相當(dāng)于數(shù)據(jù)庫中的column。ES中,每個文檔,其實(shí)是以json形式存儲的。而一個文檔可以被視為多個字段的集合。比如一篇文章,可能包括了主題、摘要、正文、作者、時間等信息,每個信息都是一個字段,最后被整合成一個json串,落地到磁盤。
映射(Mapping):相當(dāng)于數(shù)據(jù)庫中的schema,用來約束字段的類型,不過 Elasticsearch 的 mapping 可以不顯示地指定、自動根據(jù)文檔數(shù)據(jù)創(chuàng)建。
Elasticsearch很友好地提供了RestFul的API,可以通過HTTP請求直接完成所有操作。比如下面官方的一個例子,往索引twitter添加文檔,type是tweet,文檔的id是1:
相應(yīng)地,根據(jù)user字段檢索文檔:
1、索引的shards個數(shù):
shards的個數(shù),最好是和節(jié)點(diǎn)數(shù)相關(guān)的。理論上對同一個索引,單機(jī)上的shards個數(shù)最好不要超過兩個,這樣每個查詢盡可能并行。但因?yàn)镋S中shards的個數(shù)是確定了就沒辦法再調(diào)整的,所以如果考慮到數(shù)據(jù)會高速增長,一開始分配多些也可以。另一個常見思路是按時間緯度(如月)去定義ES索引——因?yàn)榭梢詣討B(tài)調(diào)整新加的索引的shards個數(shù)。其他的一些情況,比如下面舉到的Wetest聚合的例子,因?yàn)樾枰獢?shù)據(jù)盡量地按照渠道切分開,所以定義了很多個shards(200個),但太多的shards通常是不推薦的,ES管理起來也有開銷。
2、heap內(nèi)存:官方建議是可用內(nèi)存的一半,是通過啟動ES的環(huán)境中,定義環(huán)境變量的方式完成的。如export ES_HEAP_SIZE=10g
3、cluster.name:集群的邏輯名稱。只有cluster name相同的機(jī)器,才會在邏輯上組成一個集群。比如,內(nèi)網(wǎng)中有5臺ES機(jī)器的實(shí)例,是可以構(gòu)成幾個互不干擾的ES集群的。
4、discovery.zen.minimum_master_nodes:
這個是用于集群的分布式?jīng)Q策的最少master機(jī)器個數(shù)。和常見的分布式協(xié)調(diào)算法一樣,為了避免腦裂現(xiàn)象,建議超過一半的機(jī)器,n/2+1
5、discovery.zen.ping.unicast.hosts:
ES集群的機(jī)器列表。注意ES單點(diǎn)不用配置集群中的所有機(jī)器列表,像一個連通圖一樣,只要每臺機(jī)器配置了其他機(jī)器,而這些配置又是互相可以連接的,那ES最終就會發(fā)現(xiàn)所有機(jī)器,構(gòu)成集群。如['111.111.111.0','111.111.111.1','111.111.111.2']
mapping類似于數(shù)據(jù)庫里的表結(jié)構(gòu),定義個mapping就意味著創(chuàng)建了一個索引。與數(shù)據(jù)庫不同的是,一個索引并不需要顯示地建立mapping,比如,上面那個在twitter索引插入文檔數(shù)據(jù)的例子,如果執(zhí)行的時候還沒有定義索引,ES便會根據(jù)文檔的字段和內(nèi)容,自動創(chuàng)建索引和mapping。然而,這樣創(chuàng)建的索引字段,往往可能不是我們所需要的。所以,還是自己預(yù)先通過手動定義mapping來創(chuàng)建索引比較好。下面是創(chuàng)建mapping的例子,這個例子在my_index這個目錄下,為user、blogpost這些type創(chuàng)建了mapping。其中properties下面是各種字段的定義,包括了string、數(shù)值、日期等類型的定義。
如圖中的紅框部分,這個例子中有兩個需要注意的地方:
1、user_id是string類型的,但它的index被定義為了“not_analzyed",這個需要搞清其中的意義:通常,搜索引擎中全文檢索的功能簡單說是這樣實(shí)現(xiàn)的:對原始文檔進(jìn)行分詞后用這些詞去建立倒排索引,在線上檢索時,再將用戶的查詢詞進(jìn)行分詞,用分詞結(jié)果去拉取多個倒排索引的拉鏈結(jié)果、歸并、相關(guān)性排序等,得到最終結(jié)果。但是,對于有些string類型的字段,其實(shí)并不想建倒排,就只想精確匹配,比如用戶的名字,只想查到name字段精確為“張三”的人,而不是分詞后得到的“張四”和“李三”兩個人,這個時候,就需要定義index類型字段。這個字段有no、analyzed、not_analyzed三種類型,no是壓根兒不給這字段建索引,analyzed是分析和按全文檢索的方式建,not_analyzed是完全匹配的關(guān)鍵詞查詢方式。
2、date類型,創(chuàng)建mapping時需要通過“format”指定錄入的多種可能時間格式。這樣創(chuàng)建文檔的時候,ES會根據(jù)輸入文檔的字段自動去確定是哪一種。不過直觀地想象下,在創(chuàng)建文檔時,指定明確的時間格式,省去ES動態(tài)判斷的開銷,應(yīng)該會提升些微小的性能。此外,要注意,epoch_second(秒單位時間戳)和epoch_millis(毫秒單位)盡量不要混用,如果非要混用也要在插入的時候明確指明是哪個。曾經(jīng)踩過坑,插入epoch_second的是秒級時間戳,但ES優(yōu)先認(rèn)為是毫秒,導(dǎo)致時間被縮小1000倍,最近的時間變成了1970年當(dāng)年的某個時間。
下圖列出了ES當(dāng)前版本中可以進(jìn)行mapping的數(shù)據(jù)類型、內(nèi)置的字段、mapping操作可以攜帶的參數(shù)。因?yàn)槠蜻@里就不詳細(xì)解釋了:
這里要詳細(xì)介紹的,是上圖中紅框標(biāo)出的,我們創(chuàng)建mapping時實(shí)際用到的比較關(guān)鍵的兩個內(nèi)置類型,和兩個mapping參數(shù)。這幾個都會直接影響最后索引訪問的性能:
1)_source: es會把所有字段拼成一個原始的json落入磁盤,所以這個可以理解為全量原始數(shù)據(jù),他不能用來索引,卻可以在需要的時候返回。注意盡量不要禁用,比如禁用后,用script去update就不支持了。
2)_all:一個“偽”字段,用來實(shí)現(xiàn)模糊的全文索引。可以這樣理解:在建索引的時候,把所有字段拼成一個字符串,然后對這個“大”字段進(jìn)行切詞,建倒排,然后這個字段就被丟棄了,沒有真正落入磁盤。當(dāng)全文檢索時,如果沒有指明查詢的域,比如標(biāo)題、正文(這種是很常見的),就從這個大的倒排中拉取文檔拉鏈??梢韵胂?,一些標(biāo)記或值類型的字段,如日期、得分,這種在全文檢索時是沒意義的,就可以不包含在_all內(nèi),而文本域,如title、doc,就包含在_all之中。這些都是在建mapping時可以、而且最好指定的。
3)doc_values: doc_values和下面的field_data都是在聚合(后面會介紹)、排序這些統(tǒng)計(jì)時用的參數(shù),默認(rèn)都是開啟的。排序、聚合,這種在文檔全局進(jìn)行的工作,用倒排索引肯定不合適。所以,對not_analyzed(即不建倒排)的字段,doc_values用一種列模式的方式(可以參考Hbase)來存儲文檔的正排,方便在文檔全局做統(tǒng)計(jì)。doc_values是存儲在磁盤的,如果你明確有些字段只是展示,不用于統(tǒng)計(jì)的話,可以把這個禁用掉。Doc_values一定不會對analyzed域建索引(都切詞了,想想也不合適,怎么建列索引嘛),而是用下面的field data。
4)field_data:對analyzed的文本域,比如正文,其實(shí)也會有統(tǒng)計(jì)的需求(比如ES也支持按一些關(guān)鍵詞對文檔進(jìn)行聚合統(tǒng)計(jì),但這種任務(wù)常用的方法是通過離線工具,如Hadoop或者單機(jī)的分析,做好了后推送到在線索引,直接在ES去算其實(shí)感覺有些奇怪)。雖然并不適合在搜索引擎中做,但你真的做了,es也會把這個數(shù)據(jù)動態(tài)地load內(nèi)存的一個field data中進(jìn)行運(yùn)算。所以,想想就知道,這是個非常耗內(nèi)存的操作,很可能把jvm heap吃完了?。s默認(rèn)是只打開,但不load,只是在你需要進(jìn)行analyzed域的排序和聚合的時候,才去動態(tài)load這個內(nèi)存(lazy的方式)。所以,盡量不要在查詢的時候去打開這個潘多拉魔盒,或者干脆就把這個選項(xiàng)關(guān)掉吧。
誰說搜索引擎只能用來搜索?ES不僅能搜索,還能在搜索的結(jié)果集合上直接進(jìn)行統(tǒng)計(jì),很強(qiáng)大吧。ES目前穩(wěn)定的非實(shí)驗(yàn)階段聚合主要分兩種:Metrics Aggregation(指標(biāo)聚合)和Bucket Aggregation(桶聚合)。
指標(biāo)聚合主要指常規(guī)的集合數(shù)學(xué)統(tǒng)計(jì)類運(yùn)算,如官方guide的這個例子:找到交易的所有紅色的車,然后求它們的平均價(jià)格:
結(jié)果大概是這樣的:
神奇吧~指標(biāo)運(yùn)算還包括其他,如大、最小、求和、個數(shù)、地理坐標(biāo)運(yùn)算等。然而我們今天要進(jìn)行實(shí)例講解的則主要是Bucket Aggregation,桶聚合。桶聚合是指把文檔,按照某個給定字段分成不同的組,然后在組內(nèi)進(jìn)行進(jìn)一步聚合運(yùn)算,并返回桶級的結(jié)果。比較直觀的理解,如:直方圖、分時間段統(tǒng)計(jì)等等。如下面這個例子,是桶聚合中的term聚合,即按照color這個字段,精確匹配后進(jìn)行分桶,然后桶內(nèi)還進(jìn)一步嵌套了平均價(jià)格聚合、和按制造商進(jìn)一步的分桶聚合。
統(tǒng)計(jì)的結(jié)果類似下面這樣,紅色的車共有4輛,平均價(jià)格是32500,并且又包含了3輛本田和1輛寶馬:
上面是簡單的例子。在我們的WeTest輿情中,有論壇熱帖這樣一個功能,即,實(shí)時統(tǒng)計(jì)某個數(shù)據(jù)源中(如百度貼吧),某個論壇里(如王者榮耀吧),一段時間內(nèi)(如3個月),回復(fù)數(shù)最多的TopN個帖子。
另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)cdcxhl.cn,海內(nèi)外云服務(wù)器15元起步,三天無理由+7*72小時售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國服務(wù)器、虛擬主機(jī)、免備案服務(wù)器”等云主機(jī)租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡單易用、服務(wù)可用性高、性價(jià)比高”等特點(diǎn)與優(yōu)勢,專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場景需求。