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