## 路由功能淺談
<div style="text-indent:2em;">
<p>在本章<i> 選擇恰當的分片數量和分片副本數量 </i>一節中,已經提到使用路由功能可以只在一個分片上執行查詢命令,作為提高系統吞吐量的一種解決方案。接下來作者將詳細地介紹這一功能。</p>
<h3>分片和分片中數據</h3>
<p>通常情況下,ElasticSearch是如何把數據分發到各個分片中,哪個分片存儲哪一類的文檔等細節并不重要。因為查詢時,將查詢命令分發到每個分片就OK了。唯一的關鍵點在于算法,將數據均等地分配到各個分片的算法。在刪除或者更新文檔時,情況就會變得有點復雜了。實際上,這也不是什么大問題。只要保證分片算法在處理文檔時,對于相同的文檔標識生成相同的映射值就可以了。如果我們有這樣的分片算法,ElasticSearch就知道在處理文檔時,如何定位到正確的分片。但是,在選擇文檔的存儲分片時,采用一個更加智能的辦法不就更省事兒了嗎?比如,把某一特定類型的書籍存儲到特定的分片上去,這樣在搜索這一類書籍的時候就可以避免搜索其它的分片,也就避免了多個分片搜索結果的合并。這就是路由功能(routing)的用武之地。路由功能向ElasticSearch提供一種信息來決定哪些分片用于存儲和查詢。同一個路由值將映射到同一個分片。這基本上就是在說:“通過使用用戶提供的路由值,就可以做到定向存儲,定向搜索。”</p>
<h3>路由功能的簡單應用</h3>
<p>我們將通過一個例子來說明ElasticSearch是如何分配分片,哪些文檔會存儲在特定的分片上這一過程,為了使細節更清楚,我們借助一個第三方插件。這個插件將幫助我們更生動形象地了解ElasticSearch處理數據的過程。用如下的命令來安裝插件:
</p>
<blockquote>
bin/plugin -install karmi/elasticsearch-paramedic</blockquote>
<p>重啟ElasticSearch后,用瀏覽器打開http://localhost:9200/_plugin/paramedic/index.html 我們將能看到顯示索引的各種信息的統計結果的頁面。例如:最值得關注的信息是集群的狀態和每個索引下分片及分片副本的相關信息。</p>
<p>啟動兩個節點的ElasticSearch集群,用如下的命令創建索引:
<blockquote style="text-indent:0em;border:0px;" >
curl -XPUT localhost:9200/documents -d '{<blockquote>
settings: {<blockquote>number\_of\_replicas: 0,<br/>
number\_of\_shards: 2</blockquote>
}</blockquote>
}'</blockquote>
<p/>
<p>我們創建了具有2個分片0個分片副本的索引。這意味著該集群最多有兩個結點,多余的節點無法存儲數據,除非我們增加分片副本的數量(讀者可以回顧本章<i>選擇恰當的分片數量和分片副本數量</i>一節內容了解相關知識)。創建索引后,下一步的操作就是添加數據了。我們用如下的命令來進行數據添加:
<blockquote style="text-indent:0em;">
curl -XPUT localhost:9200/documents/doc/1 -d '{ "title" : "Document No.
1" }'<br/>
curl -XPUT localhost:9200/documents/doc/2 -d '{ "title" : "Document No.
2" }'<br/>
curl -XPUT localhost:9200/documents/doc/3 -d '{ "title" : "Document No.
3" }'<br/>
curl -XPUT localhost:9200/documents/doc/3 -d '{ "title" : "Document No.
4" }'<br/>
</blockquote>
</p>
<p>添加數據完成后,Paramedic插件顯示集群中存在兩個分片,截圖如下:
<img src="../42_paramedic_1.png"/>
</p>
<p>在節點所呈現的信息中,我們一樣能夠找到值得關注的信息。集群中的每個節點都存儲著兩個文檔,這讓我們容易得出如下的結論:分片算法完美地完成了數據分片的任務;集群中有一個由兩個分片構建成的索引,索引對每個分片中文檔的數量進行了均等分配。</p>
<p>現在我們做一些破壞,關閉一個節點。用Paramedic插件,我們將看到類似下圖的截圖:
<img src="../42_paramedic_2.png"/>
</p>
<p>我們關注的第一條信息是集群的狀態已經變成了紅色狀態。這意味著至少有一個主分片已經丟失,這就表明有一部分數據已經失效,同時有一部分索引也失效了。盡管如此,在ElasticSearch集群上仍然可以進行查詢操作。ElasticSearch集群把決定權交給了開發者:決定返回給用戶不完整的數據還是阻塞用戶查詢。先看看查詢命令的返回結果吧:
<blockquote style="text-indent:0em;">
{<blockquote>
"took" : 30,<br/>
"timed\_out" : false,<br/>
"\_shards" : {<blockquote>
"total" : 2,<br/>
"successful" : 1,<br/>
"failed" : 1,<br/>
"failures" : [ {<blockquote>
"index" : "documents",<br/>
"shard" : 1,<br/>
"status" : 500,<br/>
"reason" : "No active shards"</blockquote>
}]</blockquote>
},</blockquote>
"hits" : {<blockquote>
"total" : 2,<br/>
"max\_score" : 1.0,<br/>
"hits" : [ {<blockquote>
"\_index" : "documents",<br/>
"\_type" : "doc",<br/>
"\_id" : "1",<br/>
"\_score" : 1.0, "\_source" : { "title" : "Document No. 1" }</blockquote>
}, {<blockquote>
"\_index" : "documents",<br/>
"\_type" : "doc",<br/>
"\_id" : "3",<br/>
"\_score" : 1.0, "\_source" : { "title" : "Document No. 3" }</blockquote>
} ]</blockquote>
}
}
</blockquote>
</p>
<p>正如你所看見的那樣,ElasticSearch返回了分片失效的信息。我們看到分片1失效。在返回的結果集中,我們只看到了ID為1和3的文檔。至少在主分片1重新連接到集群之前,其它的文檔丟失。如果重新啟動第二個節點,經過一段時間(取決于網絡情況和gateway模塊的參數設置),集群將返回綠色狀態,而且整個索引中的文檔都可用。接下來,我們將用路由功能(routing)來做與上面一樣的事情,并觀察兩者在集群中的不同點。</p>
<h4 style="text-indent:0em;">啟用路由功能(routing)索引數據</h4>
<p>通過路由功能,用戶能夠控制ElasticSearch用哪個分片來存儲文檔。路由的參數值是無關緊要的,可以由用戶隨意給出。關鍵點在于相同的參數值應該用來把不同的文檔導向同一個分片。</p>
<p>將路由參數信息寫入到ElasticSearch中有多種方式,最簡單的一種是在索引文檔的時候提供一個<span style="font-size:13">routing</span>URL 參數。比如:
<blockquote style="text-indent:0em;">curl -XPUT localhost:9200/documents/doc/1?routing=A -d '{ "title":"Document" }'</blockquote>
</p>
<p>另外一種方式就是在創建文檔時把\_routing域放進去:
<blockquote style="text-indent:0em;">curl -XPUT localhost:9200/documents/doc/1 -d '{ "title":"Document","\_routing":"A" }'</blockquote>
</p>
<p>然而這種方式只有在mapping中定義了\_routing域才會生效。例如:
<blockquote style="text-indent:0em;">
"mappings": {<blockquote>
"doc": {<blockquote>
"\_routing": {<blockquote>"required": true,<br/>
"path": "\_routing"</blockquote>
},
"properties": {<blockquote>
"title" : {"type": "string" }</blockquote>
}</blockquote>
}</blockquote>
}
</blockquote>
</p>
<p>讓我們在這里停留一下。在本例中,我們用到了\_routing域。值得一提的是path參數可以指向文檔中任何not-analyzed域。這是一個非常強大的特性。例舉routing特性的一個主要的優點:比如我們在文檔中定義了library\_id域,用來表示書籍所在的藏書室編號。當我們基于library\_id域來實現路由功能,基于該藏書室檢索相關圖書將使檢索過程更高效,這樣做也是合乎邏輯的。</p>
<p>現在,我們回過頭來繼續討論routing值的定義方式。最后一個辦法是使用批處理索引。在下面的例子中,routing被放置到每個文檔的頭部信息塊中。例如:
<blockquote style="text-indent:0em;">
curl -XPUT localhost:9200/\_bulk --data-binary
'{<blockquote>"index" : {<blockquote>"\_index" : "documents", "\_type" : "doc", "\_routing" : "A"
</blockquote>}</blockquote>}
{ "title" : "Document No. 1"}'
</blockquote>
既然已經了解routing是如何工作的,那么就回到我們例子中來。
</p>
<h3 style="text-indent:0em;">啟用路由功能(routing)索引數據</h3>
<p>現在,我們對照前面的例子,依樣畫葫蘆,除了一點不一樣,那就是使用路由功能(routing)。第一件事就是刪除所有舊的文檔。如果不清空索引數據,在添加id相同的文檔到索引中時,routing可能會把相同的文檔寫入到其它的分片上(經驗證:索引中會存在id一樣的兩個文檔,只不過在不同的分片中)。因此,運行如下的命令清空索引數據:
<blockquote style="text-indent:0em;">
curl -XDELETE localhost:9200/documents/\_query?q=*:*
</blockquote>
</p>
<p>經過這一步,我們重新索引數據。但是這次要添加上routing信息。因此索引數據的命令如下:
<blockquote style="text-indent:0em;">curl -XPUT localhost:9200/documents/doc/1?routing=A -d '{ "title" :
"Document No. 1" }'<br/>
curl -XPUT localhost:9200/documents/doc/2?routing=B -d '{ "title" :
"Document No. 2" }'<br/>
curl -XPUT localhost:9200/documents/doc/3?routing=A -d '{ "title" :
"Document No. 3" }'<br/>
curl -XPUT localhost:9200/documents/doc/4?routing=A -d '{ "title" :
"Document No. 4" }'<br/>
</blockquote>
</p>
<p>routing參數指揮ElasticSearch把攜帶該參數的文檔放到特定的分片中。當然這也并不意味著routing參數值不同的文檔會被放置到不同的分片中。但是在我們的例子中,文檔數目不多,routing參數不同,文檔所在的分片也會不同。讀者可以在Paramedic頁面上驗證:兩個節點中一個節點上只有一個文檔(它的routing值是B),另一個節點中有3個文檔(它們的routing值是A)。如果我們關閉一個節點,Paramedic頁面上集群的狀態將會變成紅色狀態,檢索所有的文檔,其結果如下:
<blockquote style="text-indent:0em;">
{<blockquote>
"took" : 1,<br/>
"timed\_out" : false,<br/>
"\_shards" : {<blockquote>
"total" : 2,<br/>
"successful" : 1,<br/>
"failed" : 1,<blockquote>
"failures" : [ {
"index" : "documents",
"shard" : 1,
"status" : 500,
"reason" : "No active shards"</blockquote>
} ]</blockquote>
},
"hits" : {<blockquote>
"total" : 3,<br/>
"max\_score" : 1.0,<br/>
"hits" : [ {<blockquote>
"\_index" : "documents",<br/>
"\_type" : "doc",<br/>
"\_id" : "1",<br/>
"\_score" : 1.0, "\_source" : { "title" : "Document No. 1" }</blockquote>
}, {<blockquote>
"\_index" : "documents",<br/>
"\_type" : "doc",<br/>
"\_id" : "3",<br/>
"\_score" : 1.0, "\_source" : { "title" : "Document No. 3" }</blockquote>
}, {<blockquote>
"\_index" : "documents",<br/>
"\_type" : "doc",<br/>
"\_id" : "4",<br/>
"\_score" : 1.0, "\_source" : { "title" : "Document No. 4" }</blockquote>
} ]</blockquote>
}</blockquote>
}
</blockquote>
</p>
<p>在本例中,id為2的文檔丟失了。集群中文檔routing值為B的節點失效。如果運氣不好,關閉了另一個節點,我們將丟失3個文檔的數據。</p>
<h4>routing功能用于檢索</h4>
<p>當我們能夠很好的掌控routing機制時,routing能讓我們在的數據檢索變得更高效。如果我們只想從整個索引中獲取特定的部分數據,為什么還要去一一檢索所有的節點呢?對于那些帶routing參數值為A的索引數據,我們只需簡單地執行如下的查詢命令,就可以檢索到它們。
<blockquote>
curl -XGET 'localhost:9200/documents/\_search?pretty&q=*:*&routing=A'
</blockquote>
</p>
<p>我們僅僅是在查詢命令中添加了一個routing參數,和一個我們感興趣的參數值。對于上面的命令,ElasticSearch將返回如下的結果:
<blockquote style="text-indent:0em;">
{
"took" : 1,<br/>
"timed\_out" : false,<br/>
"\_shards" : {<blockquote>
"total" : 1,<br/>
"successful" : 1,<br/>
"failed" : 0,<br/>
},</blockquote>
"hits" : {<blockquote>
"total" : 3,<br/>
"max\_score" : 1.0,<br/>
"hits" : [ {<blockquote>
"\_index" : "documents",<br/>
"\_type" : "doc",<br/>
"\_id" : "1",<br/>
"\_score" : 1.0, "\_source" : { "title" : "Document No. 1" }</blockquote>
}, {<blockquote>
"\_index" : "documents",<br/>
"\_type" : "doc",<br/>
"\_id" : "3",<br/>
"\_score" : 1.0, "\_source" : { "title" : "Document No. 3" }</blockquote>
}, {<blockquote>
"\_index" : "documents",<br/>
"\_type" : "doc",<br/>
"\_id" : "4",<br/>
"\_score" : 1.0, "\_source" : { "title" : "Document No. 4" }</blockquote>
} ]</blockquote>
}
}
</blockquote>
</p>
<p>
一切就像魔法一樣。但是要注意,我們忘記去啟動另一個節點,該節點上的分片中存儲著用routing值為B索引的文檔。盡管我們并沒有索引的全景圖,但是ElasticSearch的回復值并沒有包含分片失效的信息。這證明帶routing參數的查詢命令只會到指定的分片上獲取數據,而忽略其它的分片。如果用同樣的命令,帶上參數routing=B,我們將得到類似如下的異常信息:
<blockquote style="text-indent:0em;">
{
"error" : "SearchPhaseExecutionException[Failed to execute phase
[query\_fetch], total failure; shardFailures {[\_na\_][documents][1]: No
active shards}]",
"status" : 500
}
</blockquote>
</p>
<p>對于集群的性能優化,routing機制是一個功能非常強大工具。它讓我們能夠根據應用程序的數據分類邏輯將文檔分發到各個分片,通過它不僅節約服務器資源,還能構建出更加快捷的查詢服務。</p>
<p>我們還要復述一個要點:Routing能夠確保在索引階段把routing值相同的文檔分發到相同的分片上。但是,同一個分片可能會有多個不同的routing值。Routing允許我們在查詢的時候限定查詢的節點數量,但是無法取代過濾功能(filtering)的地位。這意味著查詢命令無論是否啟用routing功能,都應該帶有相同的過濾器(filters)。</p>
<h3>別名功能</h3>
<p>最后,有一個能夠簡化routing功能的特性值得一提。如果讀者是一個搜索引擎專家,那么在開發中,多半會選擇對外隱藏搜索引擎的配置細節,以實現架構上的解耦,同時程序員在使用中不必關注搜索引擎的各種細節,以提升開發效率。對于程序員來說,一個理想的搜索引擎就是不必關注routing、分片、分片副本。通過別名功能,我們可以像使用普通的索引一樣使用routing功能。例如:通過如下的命令可以創建一個別名:
<blockquote style="text-indent:0em;">
curl -XPOST 'http://localhost:9200/\_aliases' -d '
{<blockquote>
"actions" : [<blockquote>
{<blockquote>
"add" : {<blockquote>
"index" : "documents",<br/>
"alias" : "documentsA",<br/>
"routing" : "A"</blockquote>
}</blockquote>
}</blockquote>
]</blockquote>
}'</blockquote>
</p>
<p>
在前面的例子中,我們創建了一個虛擬的索引,命名為documentsA,虛擬索引中的數據都來自于documents 索引。更重要的是:搜索的分片將被限制在routing值為A的分片中。正因為有這個特性,只需要把別名documentsA提供給開發人員,他們就能像使用普通的索引一樣進行查詢和索引的操作。從而屏蔽了相關的細節。
</p>
<h3>多個routing值的聯合</h3>
<p>ElasticSearch允許在單個查詢請求中指定多個routing值。多個routing值也就意味著會在多個分片上搜索,搜索哪個分片取決于給定routing值的文檔會被映射到哪個分片。下面的查詢命令就是一個簡單的例子:
<blockquote style="text-indent:0em;">
curl -XGET 'localhost:9200/documents/\_search?routing=A,B'
</blockquote>
</p>
<p>執行查詢命令后,ElasticSearch會把搜索請求發送到索引中的所有分片。因為routing值為A代表著索引的一個分片,routing值為B代表著索引的第二個分片,而索引一共只有2個分片。</p>
<p>當前,別名也是支持多個routing值的。下面的例子演示了這個特性。
<blockquote style="text-indent:0em;">
curl -XPOST 'http://localhost:9200/\_aliases' -d '
{<blockquote>
"actions" : [<blockquote>
{<blockquote>
"add" : {<blockquote>
"index" : "documents",<br/>
"alias" : "documentsA",<br/>
"search\_routing" : "A,B",<br/>
"index\_routing" : "A"</blockquote>
}</blockquote>
}</blockquote>
]</blockquote>
}'
</blockquote>
</p>
<p>上面的例子中用到了兩個我們沒有提到的配置參數,對于搜索和索引兩個過程,我們可以配置不同的值。上例中,我們設定在查詢時(search\_routing參數),兩個routing參數值(A和B)將被用到;在索引時(index\_routing參數),只有一個routing值(A)會被用到。提示一下,索引過程是不支持多個routing參數,要記得選擇合適的過濾器(在別名中也可以配置該參數)。</p>
</div>
- 前言
- 第1章 認識Elasticsearch
- 認識Apache Lucene
- 熟悉Lucene
- 總體架構
- 分析你的文本
- Lucene查詢語言
- 認識 ElasticSearch
- 基本概念
- ElasticSearch背后的核心理念
- ElasticSearch的工作原理
- 本章小結
- 第2章 強大的用戶查詢語言DSL
- Lucene默認打分算法
- 查詢重寫機制
- 重排序
- 批處理
- 查詢結果的排序
- Update API
- 使用filters優化查詢
- filters和scope在ElasticSearch Faceting模塊的應用
- 本章小結
- 第3章 索引底層控制
- 第4章 探究分布式索引架構
- 選擇恰當的分片數量和分片副本數量
- 路由功能淺談
- 調整集群的分片分配
- 改變分片的默認分配方式
- 查詢的execution preference
- 學以致用
- 本章小結
- 第5章 管理Elasticsearch
- 選擇正確的directory實現類——存儲模塊
- Discovery模塊的配置
- 索引段數據統計
- 理解ElasticSearch的緩存
- 本章小結
- 第6章 應對突發事件
- 第7章 優化用戶體驗
- 第8章 ElasticSearch Java API
- 第9章 開發ElasticSearch插件