* ES集群會在生產(chǎn)環(huán)境被長期實踐, 一些重要概念, 包括應(yīng)用和優(yōu)化調(diào)試方法值得記錄分享
* 所以, 會有關(guān)于ES的一系列分享, 先從基礎(chǔ)開始, 成體系了后再加目錄
ES:?
Written in Java, based on Lucene
Realtime analytics & Full Text Search Engine
Distributed, Easy to scale,?High availability
Multi tenant architecture (多租戶)
Document oriented(Json)
Schema Free
Restful API, Json over HTTP
Open Source
Easy to Configure
倒排:
Apache Lucene將所有信息寫到一個稱為倒排索引(inverted index)的結(jié)構(gòu)中。不同于關(guān)系型數(shù)據(jù)庫中表的處理方式,倒排索引建立索引中詞和文檔之間的映射葱跋。你可以把倒排索引看成這樣一種數(shù)據(jù)結(jié)構(gòu),其中的數(shù)據(jù)是面向詞而不是面向文檔的。來看一個簡單的例子撬讽。我們有一些文檔,只有它們的標(biāo)題字段需要被索引,它們看起來如下所示:
Elasticsearch Server 1.0 (document 1);
Mastering Elasticsearch (document 2);
Apache Solr 4 Cookbook (document 3)。
那么,簡化版的索引可以看成是這樣的:
每一個詞指向包含它的文檔編號。這樣就可以執(zhí)行一種非常高效且快速的搜索,比如基于詞的查詢往核。此外,每個詞有一個計數(shù),告訴Lucene該詞出現(xiàn)的頻率。
分析:
分析的工作, 由分詞器完成, [字符映射器] --> 分詞器(tokenizer) -----(標(biāo)記流, token stream)----> 標(biāo)記過濾器(token filter)?
token filter通常做的事情, lowercase filter, synonyms filter(同義), multiole language stemming filter(詞干)
過濾器是一個接一個處理的犁苏。所以我們通過使用多個過濾器,幾乎可以達(dá)到無限的分析可能性. 這里應(yīng)該記住的是, 索引應(yīng)該和查詢詞匹配. 如果它們不匹配,Lucene 不會返回所需文檔. 比如, 你在建立索引時使用了詞干提取和小寫, 那你應(yīng)該保證查詢中的詞也必須是詞干和小寫, 否則你的查詢不會返回任何結(jié)果.
評分:
文檔和查詢的匹配程度用公式計算的結(jié)果. Lucene默認(rèn)使用TF/IDF(詞頻/逆向文檔頻率)評分, 這是一種計算文檔在上下文中相關(guān)度的算法. ?
_id & _type & _all & _source & _index :
每個文檔存儲在一個索引中并有一個Elasticsearch自動生成的唯一標(biāo)識符和文檔類型. 文檔需要有對應(yīng)文檔類型的唯一標(biāo)識符, 這意味著在一個索引中, 兩個不同類型的文檔可以有相同的唯一標(biāo)識符.?
_id可以直接在mapping里指定用哪個字段值填充, 這個很有幫助, 有利于數(shù)據(jù)重寫和更新. _type字段也是必須的, 默認(rèn)情況下編入索引, 但不會被分析和存儲
_source可以通過includes或者excludes來指定存儲哪些字段.
_index, 用來確定文檔源自那個索引, 在使用別名時很重要. 默認(rèn)未啟用.
Elasticsearch使用文檔的唯一標(biāo)識符來計算文檔應(yīng)該被放到哪個分片中.?
從這部分的原理可以看出路由的重要性.
分片和副本:
副本的數(shù)量可以線上在集群中實時調(diào)整, 不過分片的數(shù)量一旦創(chuàng)建好, 更改分片的數(shù)量就只能另外創(chuàng)建一個新的索引重新索引數(shù)據(jù).
索引的創(chuàng)建, 在復(fù)雜系統(tǒng)往往需要控制索引的創(chuàng)建規(guī)則, 不可隨意創(chuàng)建, 通過auto_create_index的true, false和模式控制
action.auto_create_index: -ainemo*, +ai*, -* (ainemo不可以, ai開頭的可以, 不可以隨意創(chuàng)建)
模式的順序很重要, ES按順序檢查, 一旦條件成立, 則后面的無效, 所以是程序語言中順序"或" 的關(guān)系
幾個比較重要的屬性(field properties):
index: analyzed, no, not_analyzed. analyzed表示字段分析后編入索引, no表示無法搜索改字段. 字符串的話還有一個not_analyzed, 表示字符串不做上面提到的分析進入索引 (這個很重要, 在使用聚合分析的時候很有效果)
omit_norms:?true, false硬萍。對于經(jīng)過分析的字符串字段,默認(rèn)值為false,而對于未經(jīng)分析但已編入索引的字符串字段,默認(rèn)值設(shè)置為true。當(dāng)屬性為true時,它會禁用Lucene對該字段的加權(quán)基準(zhǔn)計算(norms calculation),這樣就無法使用索引期間的加權(quán),從而可以為只用于過濾器中的字段節(jié)省內(nèi)存(在計算所述文件的得分時不會被考慮在內(nèi))
boost: 文檔的加權(quán)值, 表示這個文檔中的字段的重要性, 默認(rèn)為1, 分值越高越重要. 匹配計算結(jié)果的時候很重要.
include_in_all: 是否包含在_all中, 不包含的話可以減少索引, 但不被全文檢索
通常通過字段冗余實現(xiàn)不同的業(yè)務(wù), 比如一個字段用于搜索,一個字段用于排序或一個經(jīng)語言分析器分析,一個只基于空白字符來分析.?
"name": {
? ? ? ? "type": "string",
? ? ? ? "fields": {
? ? ? ? ? ? "raw": { "type" : "string", "index": "not_analyzed" }
? ? ? ? ?}
}
上述定義將創(chuàng)建兩個字段:我們將第一個字段稱為name,第二個稱為name.raw. 當(dāng)然, 你不必在索引的過程中指定兩個獨立字段, 指定一個name字段就足夠了. Elasticsearch會處理余下的工作,將該字段的數(shù)值復(fù)制到多字段定義的所有字段围详。
相似度模型(TODO):
BM25, 基于概率模型, 簡短文本文檔表現(xiàn)較好;
隨機性偏差模型, 基于具有相同名稱的概率模型, 處理自然語言文本時表現(xiàn)好;
信息基礎(chǔ)模型, 類似與隨機性偏差.
段合并 (Segment Merging): [TODO: update it according to mastering elasticsearch]
Segment mergingis the process during which the underlying Lucene library takes several segments and creates a new segment based on the information found in them. The resulting segment has all the documents stored in the original segments except the ones that were marked for deletion. After the merge operation, the source segments are deleted from the disk. Because segment merging is rather costly in terms of CPU and I/O usage, it is crucial to appropriately control when and how often this process is invoked.
路由 (route):
默認(rèn)情況下, Elasticsearch會在所有索引的分片中均勻地分配文檔. 為了獲得文檔, Elasticsearch必須查詢所有分片并合并結(jié)果. 然而, 如果你可以把數(shù)據(jù)按照一定的依據(jù)來劃分, 就可以使用一個強大的文檔和查詢分布控制機制: 路由. 簡而言之, 它允許選擇用于索引和搜索數(shù)據(jù)的分片.
先看兩個圖, 索引和搜索
左邊是索引, 右邊是查詢. 通常來說, ES會查詢所有的節(jié)點來得到標(biāo)識符和匹配文檔的得分. 緊接著通過一個內(nèi)部請求, 發(fā)送到相關(guān)分片, 最后獲取所需文檔. 顯然效率很低, 所以一個重要的優(yōu)化就是路由, 路由可以控制文檔和查詢轉(zhuǎn)發(fā)的目的分片∑庸裕現(xiàn)在,你可能已經(jīng)猜到了,可以在索引和查詢時都指定路由值。實際上, 如果使用路由, 那么檢索和查詢階段都必須使用路由. 使用路由值之后, 查詢的請求就只會發(fā)送到單個分片上.?
實現(xiàn)來說, 可以使用相同的路由參數(shù) (路由值)來實現(xiàn):
curl -XPUT "/_id?routing=12" [索引]; curl -XGET "/_search?routing=12" [查詢]
通常的實現(xiàn)不會通過每個請求添加路由值的, 一般性做法是在類型定義時添加路由字段 (會比使用路由參數(shù)的方法慢, 因為需要額外的解析) [post是type]:
搜索:
查詢過程, 默認(rèn)分成兩個階段, Scatter (發(fā)散) 階段 和 Gather (收集) 階段
發(fā)散階段會查詢所有的shards, 得到document identifier 和文檔得分. 會等待所有查詢結(jié)束, 匯總數(shù)據(jù), 排序. 然后再通過一個內(nèi)部請求, 到相應(yīng)的shard獲得最終數(shù)據(jù), 稱為收集階段. 這個是默認(rèn)的流程, 如果我們不做任何的優(yōu)化和配置. 如何改變:
1 通過搜索類型search_type: query_then_fetch, query_and_fetch, dfs_query_and_fetch, count, scan
2 通過搜索執(zhí)行偏好preference: _primary, _primary_first, _local, _only_node:id, _shards
? ? ? ? ? 通過_search_shards可以看查詢?nèi)绾螆?zhí)行
基本查詢:
這塊的記錄其實主要是讓大家了解下ES的幾種常用查詢方式和搜索的基本原理, 簡單看看就好, 需要詳細(xì)了解的還是看官方文檔:?ES DOC
- Term query:
"term": {"title": "crime"}, term查詢是未經(jīng)分析的, 所以是完全匹配.
"terms" : {"tags": ["novel", "books"], "minimun_match": 1}, 匹配多個詞條, 可控制匹配度
- Match query:
和term的區(qū)別是, match查詢是需要分析器介入的, 比如默認(rèn)的
"match": {"title": "nemo and office"}, 會匹配所有titile含有nemo, and或office詞條的文檔,
? ? ? ? ? ? operator: or, and. 控制匹配詞條的關(guān)系, 默認(rèn)是or
? ? ? ? ? ? fuzziness: 模糊查詢的相似度
"multi_match": 多字段查詢, 結(jié)果默認(rèn)通過布爾查詢(還有最大分查詢), 結(jié)合評分高低間的平衡來計算結(jié)果
? ? ? ? "multi_match": {"query": "nemo office", "fields": ["title", "otitle"]}
"match_phrase": 類似于布爾查詢, 不過是使用分析后的文本構(gòu)建的查詢
? ? ? ? ?slop: 1, 詞條間的未知詞條數(shù)量, 所以 "nemo office" 和 "nemo and office"是匹配的
- query_string: 這個很好, 支持全部的Lucene查詢語法, 會使用一個查詢解析器構(gòu)建成實際的查詢
"query_string" : {?"query" : "title:nemo^10 +title:office -otitle:xylink +author:(+junjian +dory)",
"default_field" : "title"}
// title中包含crime并且重要程度為10, 并且希望包含office, otitle不包含xylink, 而且需要author字段包含junjian和dory詞條, ps: 還有個simple_query_string, 解析錯誤不會拋異常, 丟棄無效部分.
- 模糊查詢:
"fuzzy": {"title": "offi"}, 基于編輯距離(ED)來匹配文檔
- 通配符查詢:?
"wildcard": {"title": "off?ce"},?
查詢還有很多, 參看文檔就好.
過濾器:
post_filter, filtered, filter
過濾器類型: range, exists, missing, script
當(dāng)然, 之前講的查詢都可以封裝到過濾器中, 區(qū)別是通過過濾器返回的文檔得分都是1.0
應(yīng)該盡可能使用過濾器助赞。過濾器不影響評分,而得分計算讓搜索變得復(fù)雜,而且需要CPU資源寒砖。另一方面,過濾是一種相對簡單的操作。由于過濾應(yīng)用在整個索引的內(nèi)容上,過濾的結(jié)果獨立于找到的文檔,也獨立于文檔之間的關(guān)系嫉拐。過濾器很容易被緩存,從而進一步提高過濾查詢的整體性能
關(guān)于搜索最后,?補充一個api, 就是驗證_validate, 因為復(fù)雜的查詢經(jīng)歷了什么我們不是特別的好控制, 而且部分查詢出錯, _search依舊可以返回貌似正確的結(jié)果. 使用方法是把_search 換成 _validate, 返回結(jié)果類似于:
{
"valid": false,
"_shards": { "total": 1,??"successful": 1,?"failed": 0 }
}
想要了解出錯的具體問題, 加一個explain參數(shù) (通常還需要加一個--data-binary參數(shù), 以保留換行符, 方便定位問題), 就會知道哪些地方寫錯了.
另外, 關(guān)于查詢重寫, 數(shù)據(jù)排序以及高亮的原理, 可以看看官方文檔.
查詢性能分析:
假設(shè)在elasticsearch.yml文件中設(shè)置了以下的日志配置:
index.search.slowlog.threshold.query.warn: 10s
index.search.slowlog.threshold.query.info: 5s
index.search.slowlog.threshold.query.debug: 2s
index.search.slowlog.threshold.query.trace: 1s
并且在logging.yml配置文件中設(shè)置了以下日志級別:logger:
index.search.slowlog: TRACE, index_search_slow_log_file
注意, index.search.slowlog.threshold.query.trace屬性設(shè)置成1s, 而日志級別index.search.slowlog屬性設(shè)置成TRACE. 這意味著每當(dāng)一個查詢執(zhí)行時間查過1秒(在分片上,不是總時間)時, 它將被記錄到慢查詢?nèi)罩疚募?日志文件由logging.yml配置文件中的index_search_slow_log_file節(jié)點指定). 例如, 慢查詢?nèi)罩疚募锌赡苷业较旅娴臈l目:
[2013-01-24 13:33:05,518][TRACE][index.search.slowlog.query] [Local test] [library][1] took[1400.7ms], took_millis[1400], search_type[QUERY_THEN_FETCH], total_shards[32], source[{"query":{"match_all":{}}}], extra_source[]
可以看到, 前面的日志行包含查詢時間, 搜索類型和搜索源本身,它顯示了執(zhí)行的查詢. 當(dāng)然,你的配置文件中可能有不同的值,但慢查詢?nèi)罩究梢允且粋€很有價值的源,從中可以找到執(zhí)行時間過長的哩都、可能需要定義預(yù)熱的查詢,它們可能是父子查詢,需要獲取一些標(biāo)識符來提高性能,或者你第一次使用了一些費時的過濾器.
到這里, 我覺得已經(jīng)把ES關(guān)鍵的一些實用信息都分享到了, 如有遺漏歡迎討論.