1. 集群相關
一個運行中的 Elasticsearch 實例稱為一個節點,而集群是由一個或者多個擁有相同 cluster.name 配置的節點組成, 它們共同承擔數據和負載的壓力。當有節點加入集群中或者從集群中移除節點時,集群將會重新平均分布所有的數據。
當一個節點被選舉成為主節點時,它將負責管理集群范圍內的所有變更,例如增加、刪除索引,或者增加、刪除節點等。 而主節點並不需要涉及到文檔級別的變更和搜索等操作,所以當集群只擁有一個主節點的情況下,即使流量的增加它也不會成為瓶頸。 任何節點都可以成為主節點。我們的示例集群就只有一個節點,所以它同時也成為了主節點。
作為用戶,我們可以將請求發送到 集群中的任何節點 ,包括主節點。 每個節點都知道任意文檔所處的位置,並且能夠將我們的請求直接轉發到存儲我們所需文檔的節點。 無論我們將請求發送到哪個節點,它都能負責從各個包含我們所需文檔的節點收集回數據,並將最終結果返回給客戶端。 Elasticsearch 對這一切的管理都是透明的。
1. 集群健康
Elasticsearch 的集群監控信息中包含了許多的統計數據,其中最為重要的一項就是 集群健康 , 它在 status 字段中展示為 green 、 yellow 或者 red 。
例如啟動一個默認的es服務器之后查看:
$ curl http://127.0.0.1:9200/_cluster/health?pretty % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 467 100 467 0 0 29187 0 --:--:-- --:--:-- --:--:-- 29187{ "cluster_name" : "my-application", "status" : "green", "timed_out" : false, "number_of_nodes" : 1, "number_of_data_nodes" : 1, "active_primary_shards" : 0, "active_shards" : 0, "relocating_shards" : 0, "initializing_shards" : 0, "unassigned_shards" : 0, "delayed_unassigned_shards" : 0, "number_of_pending_tasks" : 0, "number_of_in_flight_fetch" : 0, "task_max_waiting_in_queue_millis" : 0, "active_shards_percent_as_number" : 100.0 }
解釋: status 字段指示着當前集群在總體上是否工作正常。它的三種顏色含義如下:
green:所有的主分片和副本分片都正常運行。
yellow:所有的主分片都正常運行,但不是所有的副本分片都正常運行。
red:有主分片沒能正常運行
2. 添加索引
索引實際上是指向一個或者多個物理 分片 的 邏輯命名空間 。
1. 分片:
一個 分片 是一個底層的工作單元 ,它僅保存了全部數據中的一部分。一個分片是一個 Lucene 的實例,它本身就是一個完整的搜索引擎。我們的文檔被存儲和索引到分片內,但是應用程序是直接與索引而不是與分片進行交互。
Elasticsearch 是利用分片將數據分發到集群內各處的。分片是數據的容器,文檔保存在分片內,分片又被分配到集群內的各個節點里。 當你的集群規模擴大或者縮小時, Elasticsearch 會自動的在各節點中遷移分片,使得數據仍然均勻分布在集群里。
一個分片可以是 主 分片或者 副本 分片。 索引內任意一個文檔都歸屬於一個主分片,所以主分片的數目決定着索引能夠保存的最大數據量。一個索引必須創建主分片,副本分片可以沒有。
分片和主流關系型數據庫的表分區的概念有點類似。
2. 副本:
一個副本分片只是一個主分片的拷貝。副本分片作為硬件故障時保護數據不丟失的冗余備份,並為搜索和返回文檔等讀操作提供服務。
如果主分片有3個,那么一個副本replica就對應有1X3=3個replica shard副本分片。副本分片數量計算公式 = 副本數量repilca num X 主分片數量primary shard num
在索引建立的時候就已經確定了主分片數,但是副本分片數可以隨時修改。也就是說分片數量不允許修改,副本數量可以修改。
3. 分片與副本的關系:
每個主分片(primary shard)不會和副本分片(replica shard)存在於同一個節點中,有效的保證es的數據高可用性。
例如1:比如一個索引有3個分片和1副本,那么一共就有3*2=6個分片,3個是主分片,3個是副本分片,每個主分片都會對應一個副本分片。
例如2:只有2個節點,但是有3個分片和2個副本,這樣的情況就會導致分片無法完全分配,因為主分片和副本分片不能存在於同一個節點中。
4. 測試:
(1)在包含一個節點的集群內創建名為 blogs 的索引。分配3個主分片和一份副本(每個主分片擁有一個副本分片):
再次查看集群健康信息:
{ "cluster_name": "my-application", "status": "yellow", "timed_out": false, "number_of_nodes": 1, "number_of_data_nodes": 1, "active_primary_shards": 3, "active_shards": 3, "relocating_shards": 0, "initializing_shards": 0, "unassigned_shards": 3, "delayed_unassigned_shards": 0, "number_of_pending_tasks": 0, "number_of_in_flight_fetch": 0, "task_max_waiting_in_queue_millis": 0, "active_shards_percent_as_number": 50.0 }
集群的健康狀況為 yellow 則表示全部 主 分片都正常運行(集群可以正常服務所有請求),但是 副本 分片沒有全部處在正常狀態。 實際上,所有3個副本分片都是 unassigned —— 它們都沒有被分配到任何節點。 在同一個節點上既保存原始數據又保存副本是沒有意義的,因為一旦失去了那個節點,我們也將丟失該節點上的所有副本數據。
當前我們的集群是正常運行的,但是在硬件故障時有丟失數據的風險。
上面的集群圖可以表示為:
(2)再次啟動一個節點Node2加入到節點后再次查看健康信息:
當第二個節點加入到集群后,3個 副本分片 將會分配到這個節點上——每個主分片對應一個副本分片。 這意味着當集群內任何一個節點出現問題時,我們的數據都完好無損。
所有新近被索引的文檔都將會保存在主分片上,然后被並行的復制到對應的副本分片上。這就保證了我們既可以從主分片又可以從副本分片上獲得文檔。
{ "cluster_name": "my-application", "status": "green", "timed_out": false, "number_of_nodes": 2, "number_of_data_nodes": 2, "active_primary_shards": 3, "active_shards": 6, "relocating_shards": 0, "initializing_shards": 0, "unassigned_shards": 0, "delayed_unassigned_shards": 0, "number_of_pending_tasks": 0, "number_of_in_flight_fetch": 0, "task_max_waiting_in_queue_millis": 0, "active_shards_percent_as_number": 100.0 }
status變為green。這表示所有6個分片(包括3個主分片和3個副本分片)都在正常運行。
上面的集群圖可以表示為:
(3) 水平擴容: 我們再次啟動第三個節點 Node3
Node 1 和 Node 2 上各有一個分片被遷移到了新的 Node 3 節點,現在每個節點上都擁有2個分片,而不是之前的3個。 這表示每個節點的硬件資源(CPU, RAM, I/O)將被更少的分片所共享,每個分片的性能將會得到提升。
分片是一個功能完整的搜索引擎,它擁有使用一個節點上的所有資源的能力。 我們這個擁有6個分片(3個主分片和3個副本分片)的索引可以最大擴容到6個節點,每個節點上存在一個分片,並且每個分片擁有所在節點的全部資源。
(4)增加副本數量
查看集群信息:
{ "cluster_name": "my-application", "status": "green", "timed_out": false, "number_of_nodes": 3, "number_of_data_nodes": 3, "active_primary_shards": 3, "active_shards": 9, "relocating_shards": 0, "initializing_shards": 0, "unassigned_shards": 0, "delayed_unassigned_shards": 0, "number_of_pending_tasks": 0, "number_of_in_flight_fetch": 0, "task_max_waiting_in_queue_millis": 0, "active_shards_percent_as_number": 100.0 }
其集群狀態圖如下:
(5) 關閉主節點:
我們嘗試將主節點關閉,集群必須擁有一個主節點來保證正常工作,所以發生的第一件事情就是選舉一個新的主節點: Node 2。
在我們關閉 Node 1 的同時也失去了主分片 1 和 2 ,並且在缺失主分片的時候索引也不能正常工作。 如果此時來檢查集群的狀況,我們看到的狀態將會為 red :不是所有主分片都在正常工作。
幸運的是,在其它節點上存在着這兩個主分片的完整副本, 所以新的主節點立即將這些分片在 Node 2 和 Node 3 上對應的副本分片提升為主分片, 此時集群的狀態將會為 yellow 。 這個提升主分片的過程是瞬間發生的,如同按下一個開關一般。
為什么我們集群狀態是 yellow 而不是 green 呢? 雖然我們擁有所有的三個主分片,但是同時設置了每個主分片需要對應2份副本分片,而此時只存在一份副本分片。 所以集群不能為 green 的狀態,不過我們不必過於擔心:如果我們同樣關閉了 Node 2 ,我們的程序 依然 可以保持在不丟任何數據的情況下運行,因為 Node 3 為每一個分片都保留着一份副本。
集群健康信息如下:
{ "cluster_name": "my-application", "status": "yellow", "timed_out": false, "number_of_nodes": 2, "number_of_data_nodes": 2, "active_primary_shards": 3, "active_shards": 6, "relocating_shards": 0, "initializing_shards": 0, "unassigned_shards": 3, "delayed_unassigned_shards": 0, "number_of_pending_tasks": 0, "number_of_in_flight_fetch": 0, "task_max_waiting_in_queue_millis": 0, "active_shards_percent_as_number": 66.66666666666666 }
狀態圖如下:
2. 相關性
1.什么是相關性
返回結果是按相關性_score倒序排列的(如果我們指定了排序字段就會按照排序字段進行排序)。 但是什么是相關性? 相關性如何計算?
每個文檔都有相關性評分,用一個正浮點數字段 _score
來表示 。 _score
的評分越高,相關性越高。
查詢語句會為每個文檔生成一個 _score 字段。評分的計算方式取決於查詢類型不同的查詢語句用於不同的目的: fuzzy 查詢會計算與關鍵詞的拼寫相似程度,terms 查詢會計算 找到的內容與關鍵詞組成部分匹配的百分比,但是通常我們說的 relevance 是我們用來計算全文本字段的值相對於全文本檢索詞相似程度的算法。
Elasticsearch 的相似度算法被定義為檢索詞頻率/反向文檔頻率, TF/IDF (Term Frequency&Inverse Document Frequency),包括以下內容:
(1)檢索詞頻率
檢索詞在該字段出現的頻率?出現頻率越高,相關性也越高。 字段中出現過 5 次要比只出現過 1 次的相關性高。
(2)反向文檔頻率
每個檢索詞在索引中出現的頻率?頻率越高,相關性越低。檢索詞出現在多數文檔中會比出現在少數文檔中的權重更低。
(3)字段長度准則
字段的長度是多少?長度越長,相關性越低。 檢索詞出現在一個短的 title 要比同樣的詞出現在一個長的 content 字段權重更大。
單個查詢可以聯合使用 TF/IDF 和其他方式,比如短語查詢中檢索詞的距離或模糊查詢里的檢索詞相似度。
相關性並不只是全文本檢索的專利。也適用於 yes|no 的子句,匹配的子句越多,相關性評分越高。
如果多條查詢子句被合並為一條復合查詢語句,比如 bool 查詢,則每個查詢子句計算得出的評分會被合並到總的相關性評分中。
例如:
(1) explain分析返回的score:
GET /_search?explain { "query" : { "match" : { "content" : "java 和 JS" }} }
結果: 可看到是按照score分數降序排序。
{ "took" : 53, "timed_out" : false, "_shards" : { "total" : 16, "successful" : 16, "skipped" : 0, "failed" : 0 }, "hits" : { "total" : { "value" : 5, "relation" : "eq" }, "max_score" : 1.3862942, "hits" : [ { "_index" : "news", "_type" : "_doc", "_id" : "NQILB3QBfZgDx-b1yeNE", "_score" : 1.3862942, "_source" : { "creator" : "creator3", "createTime" : "2020-08-19T14:07:27.034Z", "type" : "js", "title" : "js記錄", "content" : "這里是js記錄", "amount" : 6 } }, { "_index" : "news", "_type" : "_doc", "_id" : "_J4LB3QBhQI7S8XqzLjT", "_score" : 0.87546873, "_source" : { "creator" : "creator6", "createTime" : "2020-08-19T14:07:27.946Z", "type" : "java", "title" : "java記錄", "content" : "這里是java記錄", "amount" : 7 } }, { "_index" : "news", "_type" : "_doc", "_id" : "NAILB3QBfZgDx-b1xOMr", "_score" : 0.87546873, "_source" : { "creator" : "creator1", "createTime" : "2020-08-19T14:07:25.469Z", "type" : "java", "title" : "java記錄", "content" : "這里是java記錄", "amount" : 5 } }, { "_index" : "news", "_type" : "_doc", "_id" : "_Z4LB3QBhQI7S8Xqzrhk", "_score" : 0.6931471, "_source" : { "creator" : "creator8", "createTime" : "2020-08-19T14:07:28.346Z", "type" : "js", "title" : "js記錄", "content" : "這里是js記錄", "amount" : 2 } }, { "_index" : "news", "_type" : "_doc", "_id" : "-54LB3QBhQI7S8Xqyrjs", "_score" : 0.6931471, "_source" : { "creator" : "creator4", "createTime" : "2020-08-19T14:07:27.459Z", "type" : "es", "title" : "js記錄", "content" : "這里是js記錄", "amount" : 3 } } ] } }
(2) explain=true 可以返回來自於哪個節點哪個分片上的信息,另外詞頻率和文檔頻率是在每個分片中計算出來的,而不是每個索引中。
GET /_search?explain=true { "query" : { "match" : { "content" : "面試" }} }
結果:
{ "took" : 1043, "timed_out" : false, "_shards" : { "total" : 16, "successful" : 16, "skipped" : 0, "failed" : 0 }, "hits" : { "total" : { "value" : 1, "relation" : "eq" }, "max_score" : 1.1220688, "hits" : [ { "_shard" : "[news][0]", "_node" : "cu2o5KFkT0iE5bB2IegMOg", "_index" : "news", "_type" : "_doc", "_id" : "2VQSMHQBKZKMrWbM2NPS", "_score" : 1.1220688, "_source" : { "creator" : "creator0", "createTime" : "2020-08-27T13:19:33.988Z", "type" : "面試筆記", "title" : "2018年面試記錄", "content" : "面試心酸啊" }, "_explanation" : { ① "value" : 1.1220688, "description" : "weight(content:面試 in 0) [PerFieldSimilarity], result of:", "details" : [ { ② "value" : 1.1220688, "description" : "score(freq=1.0), computed as boost * idf * tf from:", "details" : [ { "value" : 2.2, "description" : "boost", "details" : [ ] }, { ③ "value" : 0.98082924, "description" : "idf, computed as log(1 + (N - n + 0.5) / (n + 0.5)) from:", "details" : [ { "value" : 1, "description" : "n, number of documents containing term", "details" : [ ] }, { "value" : 3, "description" : "N, total number of documents with field", "details" : [ ] } ] }, { ④ "value" : 0.52000004, "description" : "tf, computed as freq / (freq + k1 * (1 - b + b * dl / avgdl)) from:", "details" : [ { "value" : 1.0, "description" : "freq, occurrences of term within document", "details" : [ ] }, { "value" : 1.2, "description" : "k1, term saturation parameter", "details" : [ ] }, { "value" : 0.75, "description" : "b, length normalization parameter", "details" : [ ] }, { "value" : 3.0, "description" : "dl, length of field", "details" : [ ] }, { "value" : 4.3333335, "description" : "avgdl, average length of field", "details" : [ ] } ] } ] } ] } } ] } }
解釋:它提供了 _explanation ,每個入口都包含一個 description 、 value 、 details 字段,它分別告訴你計算的類型、計算結果和任何我們需要的計算細節。
① 關於計算的總結
②檢索詞頻率:檢索詞 `面試` 在這個文檔的 `content` 字段中的出現次數。
③反向文檔頻率:檢索詞 `面試` 在索引上所有文檔的 `content` 字段中出現的次數。
④字段長度准則:在這個文檔中, `面試` 字段內容的長度 -- 內容越長,值越小。
2. 相關度控制:
1. 查詢提升權重
bool 查詢可以組合任意其他的查詢,以及其他 bool 查詢。假設想要查詢關於 “full-text search(全文搜索)” 的文檔,但我們希望為提及 “Elasticsearch” 或 “Lucene” 的文檔給予更高的 權重 ,這里 更高權重 是指如果文檔中出現 “Elasticsearch” 或 “Lucene” ,它們會比沒有的出現這些詞的文檔獲得更高的相關度評分 _score ,也就是說,它們會出現在結果集的更上面。
GET /_search { "query": { "bool": { "must": { "match": { "content": { "query": "full text search", "operator": "and" } } }, "should": [ { "match": { "content": "Elasticsearch" }}, { "match": { "content": "Lucene" }} ] } } }
content 字段必須包含 full 、 text 和 search 所有三個詞。
如果 content 字段也包含 Elasticsearch 或 Lucene ,文檔會獲得更高的評分 _score 。
但是如果我們想讓包含 Lucene 的有更高的權重,並且包含 Elasticsearch 的語句比 Lucene 的權重更高,可以通過指定 boost 來控制任何查詢語句的相對的權重, boost 的默認值為 1 ,大於 1 會提升一個語句的相對權重。所以下面重寫之前的查詢:
GET /_search { "query": { "bool": { "must": { "match": { "content": { "query": "full text search", "operator": "and" } } }, "should": [ { "match": { "content": { "query": "Elasticsearch", "boost": 3 } }}, { "match": { "content": { "query": "Lucene", "boost": 2 } }} ] } } }
boost 參數被用來提升一個語句的相對權重( boost 值大於 1 )或降低相對權重( boost 值處於 0 到 1 之間),但是這種提升或降低並不是線性的,換句話說,如果一個 boost 值為 2 ,並不能獲得兩倍的評分 _score 。
java中測試:
(1) 不改變權重
BoolQueryBuilder filter = QueryBuilders.boolQuery()
.must(QueryBuilders.termsQuery("ordernum", "order4", "order5", "order6"));
結果:
查詢結果有:3 hits條
1.0 {"amount":4,"createTime":"2020-08-27T13:42:41.559Z","description":"訂單描述4","orderid":5,"ordernum":"order4","username":"zhangsan4"}
1.0 {"amount":5,"createTime":"2020-08-27T13:42:42.662Z","description":"訂單描述5","orderid":6,"ordernum":"order5","username":"zhangsan0"}
1.0 {"amount":6,"createTime":"2020-08-27T13:42:43.932Z","description":"訂單描述6","orderid":7,"ordernum":"order6","username":"zhangsan1"}
(2) 用should提升username為zhangsan0增加權重1F,username為zhangsan1增加權重0.1F。也就是實現zhangsan0-1-4的排序
BoolQueryBuilder filter = QueryBuilders.boolQuery() .must(QueryBuilders.termsQuery("ordernum", "order4", "order5", "order6")) .should(QueryBuilders.termQuery("username", "zhangsan0")) // 默認是1F .should(QueryBuilders.termQuery("username", "zhangsan1").boost(0.1F));
結果:
查詢結果有:3 hits條
2.4816046 {"amount":5,"createTime":"2020-08-27T13:42:42.662Z","description":"訂單描述5","orderid":6,"ordernum":"order5","username":"zhangsan0"}
1.1481605 {"amount":6,"createTime":"2020-08-27T13:42:43.932Z","description":"訂單描述6","orderid":7,"ordernum":"order6","username":"zhangsan1"}
1.0 {"amount":4,"createTime":"2020-08-27T13:42:41.559Z","description":"訂單描述4","orderid":5,"ordernum":"order4","username":"zhangsan4"}
2. 其他還有隨機評分等其他方法。