咱們都知道es是一個分佈式的存儲和檢索系統,在存儲的時候默認是根據每條記錄的_id字段作路由分發的,這意味着es服務端是準確知道每一個document分佈在那個shard上的。node
相對比於CURD上操做,search一個比較複雜的執行模式,由於咱們不知道那些document會被匹配到,任何一個shard上都有可能,因此一個search請求必須查詢一個索引或多個索引裏面的全部shard才能完整的查詢到咱們想要的結果。併發
找到全部匹配的結果是查詢的第一步,來自多個shard上的數據集在分頁返回到客戶端的以前會被合併到一個排序後的list列表,因爲須要通過一步取top N的操做,因此search須要進過兩個階段才能完成,分別是query和fetch。負載均衡
(一)query(查詢階段)分佈式
當一個search請求發出的時候,這個query會被廣播到索引裏面的每個shard(主shard或副本shard),每一個shard會在本地執行查詢請求後會生成一個命中文檔的優先級隊列。性能
這個隊列是一個排序好的top N數據的列表,它的size等於from+size的和,也就是說若是你的from是10,size是10,那麼這個隊列的size就是20,因此這也是爲何深度分頁不能用from+size這種方式,由於from越大,性能就越低。fetch
es裏面分佈式search的查詢流程以下:code
1,客戶端發送一個search請求到Node 3上,而後Node 3會建立一個優先級隊列它的大小=from+size 2,接着Node 3轉發這個search請求到索引裏面每個主shard或者副本shard上,每一個shard會在本地查詢而後添加結果到本地的排序好的優先級隊列裏面。 3,每一個shard返回docId和全部參與排序字段的值例如_score到優先級隊列裏面,而後再返回給coordinating節點也就是Node 3,而後Node 3負責將全部shard裏面的數據給合併到一個全局的排序的列表。
上面提到一個術語叫coordinating node,這個節點是當search請求隨機負載的發送到一個節點上,而後這個節點就會成爲一個coordinating node,它的職責是廣播search請求到全部相關的shard上,而後合併他們的響應結果到一個全局的排序列表中而後進行第二個fetch階段,注意這個結果集僅僅包含docId和全部排序的字段值,search請求能夠被主shard或者副本shard處理,這也是爲何咱們說增長副本的個數就能增長搜索吞吐量的緣由,coordinating節點將會經過round-robin的方式自動負載均衡。排序
(二)fetch(讀取階段)索引
query階段標識了那些文檔知足了該次的search請求,可是咱們仍然須要檢索回document整條數據,這個階段稱爲fetch隊列
流程以下:
1,coordinating 節點標識了那些document須要被拉取出來,併發送一個批量的mutil get請求到相關的shard上 2,每一個shard加載相關document,若是須要他們將會被返回到coordinating 節點上 3,一旦全部的document被拉取回來,coordinating節點將會返回結果集到客戶端上。
這裏須要注意,coordinating節點拉取的時候只拉取須要被拉取的數據,好比from=90,size=10,那麼fetch只會讀取須要被讀取的10條數據,這10條數據可能在一個shard上,也可能在多個shard上因此 coordinating節點會構建一個multi-get請求併發送到每個shard上,每一個shard會根據須要從_source字段裏面獲取數據,一旦全部的數據返回,coordinating節點會組裝數據進入單個response裏面而後將其返回給最終的client。
總結:
本文介紹了es的分佈式search的查詢流程分爲query和fetch兩個階段,在query階段會從全部的shard上讀取相關document的docId及相關的排序字段值,並最終在coordinating節點上收集全部的結果數進入一個全局的排序列表後,而後獲取根據from+size指定page頁的數據,獲取這些docId後再構建一個multi-get請求發送相關的shard上從_source裏面獲取須要加載的數據,最終再返回給client端,至此整個search請求流程執行完畢,至於爲何es要經過兩個階段來完成一次search請求而不是一次搞定,歡迎你們在評論區討論。