elastic search分布式工作原理
前⾔
- Elasticsearch 是分布式的,但是對于我們開發者來說并未過多的參與其中,我們隻需啟動對應數量的節點,并給它們配置設定相同的 cluster.name 讓它們歸屬于同⼀個叢集,建立索引的時候隻需指定索引主分⽚數和 副分⽚數 即可,其他的都交給了 ES 内部⾃⼰去實作。
- 這和資料庫的分布式和 同源的 solr 實作分布式都是有差別的,資料庫要做叢集分布式,⽐如分庫分表需要我們指定路由規則和資料同步政策等,包括讀寫分離,主從同步等,solr的分布式也需依賴 zookeeper,但是 Elasticsearch 完全屏蔽了這些。
- 雖然Elasticsearch 天⽣就是分布式的,并且在設計時屏蔽了分布式的複雜性,但是我們還得知道它内部的原理。
節點互動原理
- es和其他中間件⼀樣,⽐如mysql,redis有master-slave模式。es叢集也會選舉⼀個節點做為master節點
- master節點它的職責是維護全局叢集狀态,在節點加⼊或離開叢集的時候重新配置設定分⽚。
- 所有⽂檔級别的寫操作不會與master節點通信,master節點并不需要涉及到⽂檔級别的變更和搜尋等操作,es分布式不太像mysql的master-slave模式,mysql是寫在主庫,然後再同步資料到從庫。⽽es⽂檔寫操作是分⽚上⽽不是節點上,先寫在主分⽚,主分⽚再同步給副分⽚,因為主分⽚可以分布在不同的節點上,是以當叢集隻有⼀個master節點的情況下,即使流量的增加它也不會成為瓶頸,就算它挂了,任何節點都有機會成為主節點。
- 讀寫可以請求任意節點,節點再通過轉發請求到⽬的節點,⽐如⼀個⽂檔的新增,⽂檔通過路由算法配置設定到某個主分⽚,然後找到對應的節點,将資料寫⼊到主分⽚上,然後再同步到副分⽚上。
寫入⽂檔
- 用戶端向node-1發送新增⽂檔請求。
- 節點通過⽂檔的路由算法确定該⽂檔屬于主分⽚-P0。因為主分⽚-P0在node-3,是以請求會轉發到node-3。
- ⽂檔在node-3的主分⽚-P0上新增,新增成功後,将請求轉發到node-1和node-2對應的副分⽚-R0上。⼀旦所有的副分⽚都報告成功,node-3向node-1報告成功,node-1向用戶端報告成功。
讀取⽂檔
- 用戶端向node-1發送讀取⽂檔請求。
- 在處理讀取請求時,node-1在每次請求的時候都會通過輪詢所有的副本分⽚來達到負載均衡。
elastic search文檔的路由原理
前⾔
- 當新增⼀個⽂檔的時候,⽂檔會被存儲到⼀個主分⽚中。 Elasticsearch 如何知道⼀個⽂檔應該存放到哪個分⽚中呢?當我們建立⽂檔時,它如何決定這個⽂檔應當被存儲在分⽚ 1 還是分⽚ 2 中呢?
路由算法
- ⾸先這肯定不會是随機的,否則将來要擷取⽂檔的時候我們就不知道從何處尋找了。實際上,這個過程是根據下⾯這個公式決定的:
shard = hash(routing) % number_of_primary_shards
- routing 是⼀個可變值,預設是⽂檔的 _id ,也可以設定成⼀個⾃定義的值。 routing通過 hash 函數⽣成⼀個數字,然後這個數字再除以 number_of_primary_shards (主分⽚的數量)後得到 餘數 。這個分布在 0 到 number_of_primary_shards-1 之間的餘數,就是我們所尋求的⽂檔所在分⽚的位置。
- 這就解釋了為什麼我們要在建立索引的時候就确定好主分⽚的數量 并且永遠不會改變這個數量:因為如果數量變化了,那麼所有之前路由的值都會⽆效,⽂檔也再也找不到了。
- 新增⼀個⽂檔(指定id)
PUT /nba/_doc/1
{
"name": "哈登",
"team_name": "⽕箭",
"position": "得分後衛",
"play_year": "10",
"jerse_no": "13"
}
- 檢視⽂檔在哪個分⽚上
GET /nba/_search_shards?routing=1
{
"nodes":{
"V1JO7QXLSX-yeVI82WkgtA":{
"name":"node-1",
"ephemeral_id":"_d96PgOSTnKo6nrJVqIYpw",
"transport_address":"192.168.1.101:9300",
"attributes":{
"ml.machine_memory":"8589934592",
"xpack.installed":"true",
"ml.max_open_jobs":"20"
}
},
"z65Hwe_RR_efA4yj3n8sHQ":{
"name":"node-3",
"ephemeral_id":"MOE_Ne7ZRyaKRHFSWJZWpA",
"transport_address":"192.168.1.101:9500",
"attributes":{
"ml.machine_memory":"8589934592",
"ml.max_open_jobs":"20",
"xpack.installed":"true"
}
}
},
"indices":{
"nba":{
}
},
"shards":[
[
{
"state":"STARTED",
"primary":true,
"node":"V1JO7QXLSX-yeVI82WkgtA",
"relocating_node":null,
"shard":2,
"index":"nba",
"allocation_id":{
"id":"leX_k6McShyMoM1eNQJXOA"
}
},
{
"state":"STARTED",
"primary":false,
"node":"z65Hwe_RR_efA4yj3n8sHQ",
"relocating_node":null,
"shard":2,
"index":"nba",
"allocation_id":{
"id":"6sUSANMuSGKLgcIpBa4yYg"
}
}
]
]
}
剖析elastic search的樂觀鎖
鎖的簡單分類
- 悲觀鎖
顧名思義,就是很悲觀,每次去拿資料的時候都認為别⼈會修改,是以每次在拿資料的時候都會上鎖,這樣别⼈想拿這個資料就會阻塞,直到它拿到鎖。傳統的關系型資料庫⾥邊就⽤到了很多這種鎖機制,⽐如⾏鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。
- 樂觀鎖
顧名思義,就是很樂觀,每次去拿資料的時候都認為别⼈不會修改,是以不會上鎖,但是在更新的時候會判斷⼀下在此期間别⼈有沒有去更新這個資料,⽐如可以使⽤版本号等機制。樂觀鎖适⽤于多讀的應⽤類型,這樣可以提⾼吞吐量,因為我們elasticsearch⼀般業務場景都是寫少讀多,是以通過樂觀鎖可以在控制并發的情況下⼜能有效的提⾼系統吞吐量。
版本号樂觀鎖
- Elasticsearch 中對⽂檔的 index , GET 和 delete 請求時,都會傳回⼀個 _version,當⽂檔被修改時版本号遞增。
- 所有⽂檔的更新或删除 API,都可以接受 version 參數,這允許你在代碼中使⽤樂觀的并發控制,這⾥要注意的是版本号要⼤于舊的版本号,并且加上version_type=external。
- 擷取⽂檔
GET /nba/_doc/1
{
"_index" : "nba",
"_type" : "_doc",
"_id" : "1",
"_version" : 1,
"_seq_no" : 4,
"_primary_term" : 7,
"found" : true,
"_source" : {
"name" : "哈登",
"team_name" : "⽕箭",
"position" : "得分後衛",
"play_year" : "10",
"jerse_no" : "13"
}
}
- 通過版本号新增⽂檔(version要⼤于舊的version)
POST /nba/_doc/1?version=2&version_type=external
{
"name": "哈登",
"team_name": "⽕箭",
"position": "得分後衛",
"play_year": "10",
"jerse_no": "13"
}
淺談elastic search的分詞原理
前言一
- 我們建立⼀個⽂檔
PUT test/_doc/1
{
"msg":"喬丹是籃球之神"
}
- 我們通過'喬丹'這個關鍵詞來搜尋這個⽂檔
POST /test/_search
{
"query": {
"match": {
"msg": "喬丹"
}
}
}
- 我們發現能比對⽂檔出來,那整⼀個過程的原理是怎樣的呢?
前言二
- 我們來試下使⽤中⽂分詞器
PUT test/_mapping
{
"properties": {
"msg_chinese":{
"type":"text",
"analyzer": "ik_max_word"
}
}
}
POST test/_doc/1
{
"msg":"喬丹是籃球之神",
"msg_chinese":"喬丹是籃球之神"
}
POST /test/_search
{
"query": {
"match": {
"msg_chinese": "喬"
}
}
}
POST /test/_search
{
"query": {
"match": {
"msg": "喬"
}
}
}
- 為什麼同樣是輸⼊'喬',為什麼msg能比對出⽂檔,⽽msg_chinese不能呢?
寫時分詞
- 我們使⽤來分析這個msg這個字段是怎樣分詞的
POST test/_analyze
{
"field": "msg",
"text": "喬丹是籃球之神"
}
喬,丹,是,籃,球,之,神
- 再來分析這個msg_chinese這個字段是怎樣分詞的
POST test/_analyze
{
"field": "msg_chinese",
"text": "喬丹是籃球之神"
}
喬丹, 是, 籃球, 之神
- ⽂檔寫⼊的時候會根據字段設定的分詞器類型進⾏分詞,如果不指定就是預設的standard分詞器。
- 寫時分詞器需要在mapping中指定,⽽且⼀旦指定就不能再修改,若要修改必須重建索引。
讀時分詞
- 由于讀時分詞器預設與寫時分詞器預設保持⼀緻,拿上⾯的例⼦,你搜尋 msg 字段,那麼讀時分詞器為 Standard ,搜尋 msg_chinese 時分詞器則為 ik_max_word。這種預設設定也是⾮常容易了解的,讀寫采⽤⼀緻的分詞器,才能盡最⼤可能保證分詞的結果是可以比對的。
- 允許讀時分詞器單獨設定
POST test/_search
{
"query": {
"match": {
"msg_chinese": {
"query": "喬丹",
"analyzer": "standard"
}
}
}
}
- ⼀般來講不需要特别指定讀時分詞器,如果讀的時候不單獨設定分詞器,那麼讀時分詞器的驗證⽅法與寫時⼀緻。
深入分析
- 分析器(analyzer)有三部分組成
char filter : 字元過濾器
tokenizer : 分詞器
token filter :token過濾器
- char filter(字元過濾器)
字元過濾器以字元流的形式接收原始⽂本,并可以通過添加、删除或更改字元來轉換該流。⼀個分析器可能有0個或多個字元過濾器。
tokenizer (分詞器)
- ⼀個分詞器接收⼀個字元流,并将其拆分成單個token (通常是單個單詞),并輸出⼀個token流。⽐如使⽤whitespace分詞器當遇到空格的時候會将⽂本拆分成token。"eating anapple" >> [eating, and, apple]。⼀個分析器必須隻能有⼀個分詞器
POST _analyze
{
"text": "eating an apple",
"analyzer": "whitespace"
}
token filter (token過濾器)
token過濾器接收token流,并且可能會添加、删除或更改tokens。⽐如⼀個lowercase token fifilter可以将所有的token轉成⼩寫。⼀個分析器可能有0個或多個token過濾器,它們按順序應⽤。
standard分析器
- tokenizer
Stanard tokenizer
- token fifilters
Standard Token Filter
Lower Case Token Filter
參考個人部落格:http://www.sddzcyz.cn/