1.集群發(fā)現(xiàn)機制
通常我們在每臺機器部署并啟動一個ES進程按脚,怎么讓多臺機器上的多個ES進程于毙,互相發(fā)現(xiàn)對方,然后完美的組成一個ES集群呢辅搬?
默認情況下唯沮,ES進程會綁定在自己的回環(huán)地址上,也就是127.0.0.1堪遂,然后掃描本機上的9300~9305端口號介蛉,嘗試跟這些端口上啟動的其他ES進程進行通信,然后組成一個集群蚤氏。這對于在本機上搭建ES集群的開發(fā)環(huán)境是很方便的甘耿。但是對于生產(chǎn)環(huán)境下的集群是不行的,需要將每臺ES進程綁定在一個非回環(huán)的IP地址上竿滨,才能跟其他節(jié)點進行通信佳恬,同時需要使用集群發(fā)現(xiàn)機制(discovery)來跟其他節(jié)點上的ES node進行通信,同時discovery機制也負責ES集群的Master選舉于游。
ES node中有Master Node和Data Node兩種角色毁葱。
ES 是一種p2p,也就是點對點(Peer to Peer)的分布式系統(tǒng)架構(gòu)贰剥,不是Hadoop生態(tài)普遍采用的那種Master-Slave主從架構(gòu)的分布式系統(tǒng)倾剿。集群中的每個node是直接跟其他節(jié)點進行通信的,幾乎所有的API操作蚌成,比如index前痘,delete,search等都不是Client跟Master通信担忧,而是Client跟任何一個node進行通信芹缔,那個node再將請求轉(zhuǎn)發(fā)給對應的node來進行執(zhí)行。
兩個角色瓶盛,Master Node最欠,Data Node。正常情況下惩猫,就只有一個Master Node芝硬。master node的責任就是負責維護整個集群的狀態(tài)信息,也就是一些集群元數(shù)據(jù)信息轧房,同時在新node加入集群或者從集群中下線時拌阴,或者是創(chuàng)建或刪除了一個索引后,重新分配shard奶镶。包括每次集群狀態(tài)如果有改變的化皮官,那么master都會負責將集群狀態(tài)同步給所有的node脯倒。
Master Node負責接收所有的集群狀態(tài)變化相關(guān)的信息,然后將改變后的最新集群狀態(tài)推動給集群中所有的Data Node捺氢,集群中所有的node都有一份完整的集群狀態(tài)藻丢。只不過Master Node負責維護而已。其他的node摄乒,除了master之外的Data Node悠反,就是負責數(shù)據(jù)的讀寫。
如果要讓多個Node組成一個es集群馍佑,首先第一個要設(shè)置的參數(shù)斋否,就是cluster.name
,多個node的cluster.name
一樣拭荤,才滿足組成一個集群的基本條件茵臭。cluster.name
的默認值是my-application
,在生產(chǎn)環(huán)境中舅世,一定要修改這個值旦委,否則可能會導致未知的node無端加入集群,造成集群運行異常雏亚。
而ES中默認的discovery機制缨硝,就是zen discovery機制,zen discovery機制提供了unicast discovery集群發(fā)現(xiàn)機制罢低,集群發(fā)現(xiàn)時的節(jié)點間通信是依賴的Transport Module查辩,也就是ES底層的網(wǎng)絡通信模塊和協(xié)議。
ES默認配置是使用unicast集群發(fā)現(xiàn)機制网持,從而讓經(jīng)過特殊配置的節(jié)點可以組成一個集群宜岛,而不是隨便哪個節(jié)點都可以組成一個集群。但是默認配置下功舀,unicast是本機谬返,也就是localhost,因此只能在一臺機器上啟動多個node來組成一個集群日杈。雖然ES還是會提供multicast plugin作為一個發(fā)現(xiàn)機制,但是已經(jīng)不建議在生產(chǎn)環(huán)境中使用了佑刷。雖然我們可能想要multicast的簡單性莉擒,就是所有的node可以再接收到一條multicast ping之后就立即自動加入集群。但是multicast機制有很多的問題瘫絮,而且很脆弱涨冀,比如網(wǎng)絡有輕微的調(diào)整,就可能導致節(jié)點無法發(fā)現(xiàn)對方麦萤。因此現(xiàn)在建議在生產(chǎn)環(huán)境中用unicast機制鹿鳖,提供一個ES種子節(jié)點作為中轉(zhuǎn)路由節(jié)點就可以了扁眯。
另外還需要在集群中規(guī)劃出專門的Master Eligible Node和Data node,一個節(jié)點只要它是Master Eligible Node翅帜,才有可能被選舉為真正的Master Node姻檀,選舉出真正的Master Node之后,其他的Master Eligible Node涝滴,將在Master Node故障之后绣版,通過選舉,從中再產(chǎn)生一個新的Master Node歼疮,同時杂抽,所有的非Master Node,都是Data Node韩脏,也就是說缩麸,Master Eligible Node只是有機會成為Master Node,只要你不是Master Node赡矢,你就是Data Node杭朱,而不是Master Eligible Node的Data Node是沒有升級成為Master Node的資格的。
如果是一個小集群济竹,10個以內(nèi)的節(jié)點痕檬,那就所有節(jié)點都可以作為Master Eligible Node以及Data node即可,超過10個node的集群再單獨拆分Master Eligible Node和Data Node送浊。
# 設(shè)置為Master Eligible Node
node.master: true
# 設(shè)置為Data Node
node.data: true
默認情況下梦谜,ES會將自己綁定到127.0.0.1上,對于運行一個單節(jié)點的開發(fā)模式下的ES是ok的袭景。但是為了讓節(jié)點間可以互相通信以組成一個集群唁桩,需要讓節(jié)點綁定到一個IP地址上
network.host: 192.168.0.1
只要不是本地回環(huán)地址,ES就會認為我們從開發(fā)模式遷移到生產(chǎn)模式耸棒,同時會啟用一系列的集群檢測
ping:ping是一個node用discovery機制來發(fā)現(xiàn)其他node的一個過程
unicast:unicast discovery集群發(fā)現(xiàn)機制是要求配置一個主機列表醋闭,這些主機作為Gossip通信協(xié)議的路由器拐辽,如果通過hostname來指定,那么在ping的時候會被解析為IP地址
discovery.zen.ping.unicast.hosts: ["host1", "host2"]
簡單來說,如果要讓多個節(jié)點發(fā)現(xiàn)對方并且組成一個集群棵逊,那么就得有一個中間的公共節(jié)點,當不同的節(jié)點發(fā)送請求到這些公共節(jié)點凹嘲,通過這些公共節(jié)點交換各自的信息堂飞,進而讓所有的node感知到其他的node存在,并且進行通信爽篷,最后組成一個集群悴晰。這就是基于gossip流言式通信協(xié)議的unicast集群發(fā)現(xiàn)機制。
當一個node與discovery.zen.ping.unicast.hosts
中的一個成員通信之后,就會接收到一份完整的集群狀態(tài)铡溪,接著這個node再跟master通信漂辐,并且加入集群中。這就意味著棕硫,discovery.zen.ping.unicast.hosts
是不需要列出集群中的所有節(jié)點的髓涯,只要提供少數(shù)幾個node,比如3個饲帅,讓新的node可以連接上即可复凳,如果我們給集群中分配了幾個節(jié)點作為專門的master節(jié)點,那么這里配置那些master節(jié)點即可灶泵,這個配置中也可以指定端口:
discovery.zen.ping.unicast.hosts: ["host1", "host2:9201"]
為集群選舉出一個master是很重要的育八,ES集群會自動完成這個操作,node.master
設(shè)置為false的節(jié)點是無法稱為Master的赦邻,discovery.zen.minimum_master_nodes
參數(shù)用于設(shè)置對于一個ES集群來說髓棋,必須擁有的最少的正常在線的Master Eligible Node的個數(shù),否則會發(fā)生"集群腦裂"現(xiàn)象惶洲,假如在集群中設(shè)置了3個Master Eligible Node按声,那么這個值應該為(master_eligible_nodes / 2) + 1
,即2
discovery.zen.minimum_master_nodes: 2
1.1 集群腦裂
discovery.zen.minimum_master_nodes
參數(shù)對于集群的可靠性來說恬吕,是非常重要的签则。這個設(shè)置可以預防腦裂問題,也就是預防一個集群中存在兩個master铐料。
這個參數(shù)的作用渐裂,就是告訴ES直到有足夠的master候選節(jié)點時,才可以選舉出一個master钠惩,否則就不要選舉出一個master柒凉。這個參數(shù)必須被設(shè)置為集群中master候選節(jié)點的quorum數(shù)量,也就是大多數(shù)篓跛。quorum的算法膝捞,就是:master候選節(jié)點數(shù)量 / 2 + 1,比如我們有10個節(jié)點愧沟,都能維護數(shù)據(jù)蔬咬,也可以是master候選節(jié)點,那么quorum就是10 / 2 + 1 = 6沐寺,如果我們有三個master候選節(jié)點林艘,還有100個數(shù)據(jù)節(jié)點,那么quorum就是3 / 2 + 1 = 2芽丹,Elasticsearch要求最少有3個節(jié)點,如果我們有2個節(jié)點卜朗,都可以是master候選節(jié)點拔第,那么quorum是2 / 2 + 1 = 2咕村。此時就有問題了,因為如果一個Master掛掉了蚊俺,那么剩下一個master候選節(jié)點懈涛,是無法滿足quorum數(shù)量的,也就無法選舉出新的master泳猬,集群就徹底掛掉了批钠。此時就只能將這個參數(shù)設(shè)置為1,如果發(fā)生了網(wǎng)絡分區(qū)得封,那么兩個分區(qū)中都會有一個Master埋心,還是無法避免集群腦裂
那么這個是參數(shù)是如何避免腦裂問題的產(chǎn)生的呢?比如我們有3個節(jié)點忙上,quorum是2拷呆,現(xiàn)在網(wǎng)絡故障,1個節(jié)點在一個網(wǎng)絡區(qū)域疫粥,另外2個節(jié)點在另外一個網(wǎng)絡區(qū)域茬斧,不同的網(wǎng)絡區(qū)域內(nèi)無法通信。這個時候有兩種情況:
如果master是單獨的那個節(jié)點梗逮,另外2個節(jié)點是master候選節(jié)點项秉,那么此時那個單獨的master節(jié)點因為沒有指定數(shù)量的候選master node在自己當前所在的集群內(nèi),因此就會取消當前master的角色慷彤,嘗試重新選舉娄蔼,但是無法選舉成功。然后另外一個網(wǎng)絡區(qū)域內(nèi)的node因為無法連接到master瞬欧,就會發(fā)起重新選舉贷屎,因為有兩個master候選節(jié)點,滿足了quorum艘虎,因此可以成功選舉出一個master唉侄。此時集群中就會還是只有一個master。
如果master和另外一個node在一個網(wǎng)絡區(qū)域內(nèi)野建,然后一個node單獨在一個網(wǎng)絡區(qū)域內(nèi)属划。那么此時那個單獨的node因為連接不上master,會嘗試發(fā)起選舉候生,但是因為master候選節(jié)點數(shù)量不到quorum同眯,因此無法選舉出master。而另外一個網(wǎng)絡區(qū)域內(nèi)唯鸭,原先的那個master還會繼續(xù)工作须蜗。這也可以保證集群內(nèi)只有一個master節(jié)點。
在ES集群是可以動態(tài)增加和下線節(jié)點的,所以可能隨時會改變quorum明肮。所以這個參數(shù)也是可以通過API隨時修改的菱农,特別是在節(jié)點上線和下線的時候,都需要作出對應的修改柿估。而且一旦修改過后循未,這個配置就會持久化保存下來。
PUT /_cluster/settings
{
"persistent" : {
"discovery.zen.minimum_master_nodes": 2
}
}
此外還有一些其他的關(guān)于集群發(fā)現(xiàn)機制相關(guān)的配置秫舌,以下將列出上述討論的參數(shù)以及一些其他的參數(shù)做總結(jié):
#設(shè)置集群為single-node模式,這樣的話,ES將不會再從外部去發(fā)現(xiàn)其他節(jié)點,默認不配資,代表可以發(fā)現(xiàn)其他節(jié)點
discovery.type: single-node
discovery.zen.ping.unicast.hosts: ["host1", "host2"]
#主機名被DNS解析為IP地址的超時時間
discovery.zen.ping.unicast.resolve_timeout: 5s
#在決定開始選舉或加入現(xiàn)有集群之前節(jié)點將等待多長時間
discovery.zen.ping_timeout: 3s
#節(jié)點決定加入現(xiàn)有集群后,向Master發(fā)送加入請求,超時時間默認discovery.zen.ping_timeout時間的20倍
discovery.zen.join_timeout: 20
#Master發(fā)送修改集群狀態(tài)的消息給其他節(jié)點,如果在此時間內(nèi)還沒有至少discovery.zen.minimum_master_nodes個備用Master節(jié)點回復,則此次修改集群狀態(tài)的動作不會發(fā)生
discovery.zen.commit_timeout: 30s
#滿足上面的條件后,Master發(fā)送修改集群狀態(tài)的消息給全部Node,然后全部Node開始修改自身的集群狀態(tài)信息,Master只有會等待所有的Node響應,最多等待此處的時間,然后才會開始下一次狀態(tài)修改的流程
discovery.zen.publish_timeout: 30s
#設(shè)置集群中備用master的quorum,如果集群中備用的Master的個數(shù)少于此配置,將引發(fā)集群腦裂的現(xiàn)象
discovery.zen.minimum_master_nodes: 2
#選舉Master的時候,是否忽略node.master=false的節(jié)點發(fā)送的ping消息,默認false
discovery.zen.master_election.ignore_non_master_pings: false
#當集群中沒有存活的Master后,禁用外部請求允許的操作,write:外部請求只能讀,不能寫,all:外部請求不能讀寫ES集群
discovery.zen.no_master_block: write
#Master與Data Node互相發(fā)送ping消息的時間間隔
ping_interval: 1s
#Master與Data Node互相發(fā)送ping消息超時時間
ping_timeout: 30s
#Master與Data Node互相發(fā)送ping消息失敗后的重試次數(shù)
ping_retries: 3
2. 分片機制
一個index有多個主分片的妖,每個主分片都是一個最小的工作單元,承載部分數(shù)據(jù)足陨,每條數(shù)據(jù)只能存在于一個主分片及其副本中嫂粟,每個主分片都有完整的建立索引和處理請求的能力,ES-6.X默認一個index有5個主分片钠右,每個主分片有1個副本赋元,而ES-7.X默認一個index有1個主分片,一個主分片有一個副本飒房,主分片的副本數(shù)可以動態(tài)的修改搁凸,主分片的數(shù)量則需要在創(chuàng)建index的時候設(shè)置好,否則會使用默認值狠毯,修改主分片的個數(shù)代價很大护糖,需要重建索引,操作復雜嚼松,本文不做討論
ES集群會自動地嫡良、盡量均勻的把一個index的所有分片分布到集群的不同節(jié)點上,增減節(jié)點的時候献酗,ES也自動地做分片的負載均衡
主分片和其副本不能同時存在于一個節(jié)點上寝受,否則這個index的健康狀態(tài)就不是"green"
-
查看index的分片數(shù)
GET /index_name/_settings { "index_name" : { "settings" : { "index" : { "creation_date" : "1593417909285", "number_of_shards" : "5", "number_of_replicas" : "1", "uuid" : "-nelhb1LRX6z7tXk647yGw", "version" : { "created" : "6060099" }, "provided_name" : "shop" } } } }
-
設(shè)置index的分片數(shù)
PUT index_name { "settings": { "index": { "number_of_shards": 1, "number_of_replicas": 1 } } }
-
修改副本數(shù)
PUT /index_name/_settings { "number_of_replicas": 2 }
-
查看索引分片信息
GET /index_name/_search_shards
-
手動移動分片
POST /_cluster/reroute { "commands": [ { "move": { "index": "index_name", "shard": 0, "from_node": "node_name1", "to_node": "node_name2" } } ] }
3. 集群擴容
垂直擴容:當集群性能達到瓶頸后,采購更強大的服務器來替換原來的服務器罕偎,成本高昂很澄,一般不采用這種方式
水平擴容:集群性能達到瓶頸后,添加更多的ES節(jié)點颜及,是業(yè)界常用的方案
擴容極限:假設(shè)ES集群中的索引是有3個shard甩苛、每個shard有一個副本,那么擴容的極限就是6個ES node俏站,每個node上有1個shard或者1個副本讯蒲,這樣每個shard(主分片或者副本)可以占用單臺服務器的所有資源,性能最好
如果超過擴容極限:假設(shè)ES集群中的索引是有3個shard肄扎、每個shard有一個副本墨林,那么6個ES node已經(jīng)到達擴容極限了赁酝,要想再擴容,就要動態(tài)修改副本個數(shù)旭等,比如將副本數(shù)由1修改為3后赞哗,再擴容6個ES node,將提升1倍集群的吞吐量(原來讀請求只能由1個shard或者1個副本處理辆雾,現(xiàn)在可以由1個shard或者3個副本處理)
4. 讀寫請求路由原理
4.1 Document路由原理
客戶端對一條document進行操作(CRUD),傳給ES集群的除了數(shù)據(jù)本身月劈,還會傳一個routing number度迂,這個routing number默認是這個document的"_id",可以手動指定也可以自動生成
這條document位于哪個分片上猜揪,由以下公式?jīng)Q定:hash(routing number) % number_of_shards惭墓,計算結(jié)果就是數(shù)據(jù)所在主分片的編號,這樣就找到了數(shù)據(jù)在哪個分片上
-
可以手動指定routing number的值
POST /index/type/id?routing=your_routing_number
手動設(shè)置routing number的值這個功能很重要而姐,假如想讓業(yè)務上某一類數(shù)據(jù)都保存在同一個主分片上以提高批量讀取的性能腊凶,那么將這些數(shù)據(jù)設(shè)置為相同的routing_number即可
這就解釋了為什么無法修改一個索引的主分片個數(shù),一旦修改拴念,那么根據(jù)新的主分片個數(shù)計算得到的主分片編號就不對了钧萍,也就無法找到數(shù)據(jù)了
-
根據(jù)routing number查看document在哪個主分片上
GET /index/_search_shards?routing=your_routing_number
4.2 請求轉(zhuǎn)發(fā)
寫請求:
- 假設(shè)集群中有三個節(jié)點,node01政鼠、node02风瘦、node03
- 客戶端向node01發(fā)送寫請求(增刪改),node01此時擔任一個協(xié)調(diào)節(jié)點的角色(coordinating node公般,請求第一次發(fā)送給誰万搔,誰就是協(xié)調(diào)節(jié)點),node01通過路由算法得知該條文檔在node03官帘,于是node01將寫請求轉(zhuǎn)發(fā)到node03
- node03上數(shù)據(jù)對應的主分片處理該條請求之后瞬雹,將數(shù)據(jù)變化同步給其副本
- node03將處理結(jié)果返回給node01
- node01將結(jié)果返回給客戶端
- 注意:寫請求只能由主分片去完成
讀請求:
- 假設(shè)集群中有三個節(jié)點,node01刽虹、node02酗捌、node03
- 客戶端向node01發(fā)送讀請求,node01通過路由算法得知該數(shù)據(jù)的主分片編號状婶,由于節(jié)點對等關(guān)系意敛,每個節(jié)點都保存了全部的集群狀態(tài)信息,所有node01就可以知道這個主分片的副本在哪些節(jié)點
- 于是node01直接將讀請求轉(zhuǎn)發(fā)到主分片所在的節(jié)點或者副本所在的節(jié)點膛虫,對同一數(shù)據(jù)的多次請求會做負載均衡(隨機輪詢)
- 然后node01將返回結(jié)果返回給客戶端
- 特殊情況:如果請求轉(zhuǎn)發(fā)到副本時草姻,此時數(shù)據(jù)只在有主分片中有(還沒有來得及同步給副本就收到讀請求了),那么此時讀請求會阻塞稍刀,直到可以副本同步成功之后再從副本中讀取數(shù)據(jù)
5. 寫一致性
在進行增刪改操作的時候撩独,可以手動指定寫一致性策略:
PUT /index_name/type_name/id?consistency=quorum
寫一致性策略包括:
one:只要集群中有一個primary shard是可用的敞曹,就可以執(zhí)行此操作
all:集群中所有的primary shard和replica shard都是可用的,才可以執(zhí)行這個操作
-
quorum:要求所有的shard(包括主分片和副本)中大部分都是可用的综膀,這個操作才可以執(zhí)行澳迫,quorum的計算方法為:
(number_of_shards + number_of_replicas) / 2 + 1
當可用shard數(shù)>=上述計算時,寫操作才可以執(zhí)行
注意:當number_of_replicas > 1時剧劝,quorum機制才生效橄登,考慮以下特殊情況:假設(shè)number_of_shards=1,number_of_replicas=1讥此,那么quorum=2拢锹,也就是說,只有存活的shard>=2時萄喳,寫操作才可以執(zhí)行卒稳,那么假如ES集群就一個node,顯然是無法滿足這個條件的他巨。為了讓單節(jié)點的ES也正常運行充坑,所以當number_of_replicas > 1時,quorum機制才生效
當quorum不滿足要求時染突,ES會默認等待1min捻爷,如果1min還無法達到執(zhí)行寫操作的quorum要求,那么宣告寫操作失敗份企,可以手動執(zhí)行這個超時時間:
# 默認單位為ms役衡,其他單位需要手動指定,例如timeout=30s
PUT /index_name/type_name/id?timeout=30
6. 樂觀鎖
線程安全問題:多線程環(huán)境下薪棒,對文檔的修改操作可能會出現(xiàn)線程安全問題
-
樂觀鎖和悲觀鎖
- 悲觀鎖:認為我在修改數(shù)據(jù)的同時手蝎,別人一定會修改我的數(shù)據(jù),所以每當我要修改數(shù)據(jù)俐芯,我就先上鎖
- 樂觀鎖:認為我在修改數(shù)據(jù)的同時棵介,別人一定不會修改我的數(shù)據(jù),所以我修改數(shù)據(jù)的時候不上鎖吧史,但是我在修改的時候邮辽,要先判斷一下別人有沒有修改過,如果修改過了贸营,我先更新到被人修改之后的版本吨述,我再更新,一般情況下钞脂,ES的業(yè)務場景都是讀多寫少揣云,所以ES使用基于版本號的樂觀鎖機制來控制并發(fā)
-
文檔的版本號
插入一條新文檔后,它的初始"_version"=1
PUT /user/_doc/3 { "uid": "1003", "uname": "Jerry" } { "_index" : "user", "_type" : "_doc", "_id" : "3", "_version" : 1, "result" : "created", "_shards" : { "total" : 2, "successful" : 2, "failed" : 0 }, "_seq_no" : 5, "_primary_term" : 1 }
之后每次對這個文檔好進行修改包括刪除操作冰啃,它的"_version"都會加1:
PUT /user/_doc/3 { "uid": "1003", "uname": "Tony" } { "_index" : "user", "_type" : "_doc", "_id" : "3", "_version" : 2, "result" : "updated", "_shards" : { "total" : 2, "successful" : 2, "failed" : 0 }, "_seq_no" : 6, "_primary_term" : 1 } DELETE /user/_doc/3 { "_index" : "user", "_type" : "_doc", "_id" : "3", "_version" : 3, "result" : "deleted", "_shards" : { "total" : 2, "successful" : 2, "failed" : 0 }, "_seq_no" : 7, "_primary_term" : 1 }
假設(shè)有兩個客戶端同時修改這條數(shù)據(jù)邓夕,A客戶端讀取到此數(shù)據(jù)時刘莹,version=1,然后A客戶端開始修改此數(shù)據(jù)焚刚,同一時間点弯,B客戶端也修改此數(shù)據(jù),修改成功之后矿咕,verison變?yōu)榱?抢肛,那么A客戶端再提交自己的修改就會失敗,ES的機制是碳柱,修改前后的版本號要相同才可以提交雌团,A客戶端再次提交修改請求,拿到verison=2的數(shù)據(jù)并修改士聪,才會成功,以下是一個模擬使用版本號進行并發(fā)控制的實驗:
# 插入一條數(shù)據(jù)猛蔽,其初始版本為1 PUT /user/_doc/4 { "uid": "1004", "uname": "Bob" } { "_index" : "user", "_type" : "_doc", "_id" : "4", "_version" : 1, "result" : "created", "_shards" : { "total" : 2, "successful" : 2, "failed" : 0 }, "_seq_no" : 8, "_primary_term" : 1 } # 客戶端A修改了數(shù)據(jù)剥悟,提交的時候指定當前verison=1,與服務端版本號一致 # 所以可以修改成功曼库,然后version變?yōu)? PUT /user/_doc/4?version=1 { "uid": "1004", "uname": "Bob Bob" } { "_index" : "user", "_type" : "_doc", "_id" : "4", "_version" : 2, "result" : "updated", "_shards" : { "total" : 2, "successful" : 2, "failed" : 0 }, "_seq_no" : 9, "_primary_term" : 1 } # 客戶端B修改數(shù)據(jù)区岗,提交的時候指定當前verison=1,與服務端版本號不一致 # 修改失敗 PUT /user/_doc/4?version=1 { "uid": "1004", "uname": "Bob Bob Bob" } { "error": { "root_cause": [ { "type": "version_conflict_engine_exception", "reason": "[_doc][4]: version conflict, current version [2] is different than the one provided [1]", "index_uuid": "PIpA38NgRfyomgHHdAmHnw", "shard": "0", "index": "user" } ], "type": "version_conflict_engine_exception", "reason": "[_doc][4]: version conflict, current version [2] is different than the one provided [1]", "index_uuid": "PIpA38NgRfyomgHHdAmHnw", "shard": "0", "index": "user" }, "status": 409 } # 客戶端B再次修改數(shù)據(jù)毁枯,提交的時候指定當前verison=2慈缔,與服務端版本號一致 # 修改成功,version+1 { "_index" : "user", "_type" : "_doc", "_id" : "4", "_version" : 3, "result" : "updated", "_shards" : { "total" : 2, "successful" : 2, "failed" : 0 }, "_seq_no" : 10, "_primary_term" : 1 }
-
External Version
ES提供了一個特性种玛,你可以不用它提供的內(nèi)部version版本號來進行并發(fā)控制藐鹤,可以基于你自己維護的一個版本號來進行并發(fā)控制,例如赂韵,假如你的數(shù)據(jù)在MySQL里也有一份娱节,然后你的應用系統(tǒng)本身就維護了一個版本號,這個時候祭示,你可能想要用你自己維護的那個version來進行控制肄满,外部version與內(nèi)部version的區(qū)別是:內(nèi)部version:只有當你提供的version與ES中的version一模一樣的時候,才可以進行修改质涛,只要不一樣稠歉,就報錯;外部version:只要你提供的version比ES中的version大汇陆,就能完成修改
# 插入一條數(shù)據(jù)怒炸,其初始版本為1 PUT /user/_doc/5 { "uid": "1005", "uname": "Ella" } { "_index" : "user", "_type" : "_doc", "_id" : "5", "_version" : 1, "result" : "created", "_shards" : { "total" : 2, "successful" : 2, "failed" : 0 }, "_seq_no" : 11, "_primary_term" : 1 } # 客戶端A修改了數(shù)據(jù),提交的時候指定外部verison=5毡代,大于服務器版本 # 所以可以修改成功横媚,然后version變?yōu)? PUT /user/_doc/5?version=5&version_type=external { "uid": "1005", "uname": "Ella Ella" } { "_index" : "user", "_type" : "_doc", "_id" : "5", "_version" : 5, "result" : "updated", "_shards" : { "total" : 2, "successful" : 2, "failed" : 0 }, "_seq_no" : 12, "_primary_term" : 1 } # 客戶端B修改數(shù)據(jù)纠炮,提交的時候指定當前verison=4,小于服務器版本 # 修改失敗 PUT /user/_doc/5?version=4&version_type=external { "uid": "1005", "uname": "Ella Ella Ella" } { "error": { "root_cause": [ { "type": "version_conflict_engine_exception", "reason": "[_doc][5]: version conflict, current version [5] is higher or equal to the one provided [4]", "index_uuid": "PIpA38NgRfyomgHHdAmHnw", "shard": "0", "index": "user" } ], "type": "version_conflict_engine_exception", "reason": "[_doc][5]: version conflict, current version [5] is higher or equal to the one provided [4]", "index_uuid": "PIpA38NgRfyomgHHdAmHnw", "shard": "0", "index": "user" }, "status": 409 } # 客戶端B再次修改數(shù)據(jù)灯蝴,提交的時候指定當前verison=7恢口,大于服務器版本 # 修改成功,version變?yōu)? PUT /user/_doc/5?version=7&version_type=external { "uid": "1005", "uname": "Ella Ella Ella" } { "_index" : "user", "_type" : "_doc", "_id" : "5", "_version" : 7, "result" : "updated", "_shards" : { "total" : 2, "successful" : 2, "failed" : 0 }, "_seq_no" : 13, "_primary_term" : 1 }
另外穷躁,retry_on_conflict參數(shù)可以設(shè)置重試次數(shù):
POST /user/_doc/5?retry_on_conflict=5&version=7
當更新失敗后耕肩,再次獲取document數(shù)據(jù)和最新版本號,基于最新的版本號再次嘗試更新问潭,這樣的重復會自動執(zhí)行retry_on_conflict設(shè)置的次數(shù)猿诸,知道更新成功或者重復這么多次后還沒有更新成功則返回Error
7. Lazy Delete
上面說到,刪除一條文檔的時候狡忙,將其version+1梳虽,實際上,刪除文檔并不會直接進行物理刪除灾茁,而是標記為delete狀態(tài)窜觉,隨著數(shù)據(jù)的增加,ES會選擇一個合適的時機批量刪除標記為delete狀態(tài)這批數(shù)據(jù)北专,所以在物理刪除該數(shù)據(jù)之前禀挫,客戶端再插入一條id相同的數(shù)據(jù),只是將原來的數(shù)據(jù)的狀態(tài)又改為update拓颓,然后version+1语婴,其實還是修改了原數(shù)據(jù),而非插入一條新數(shù)據(jù)