014.Elasticsearch分布式原理

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ù)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末驶睦,一起剝皮案震驚了整個濱河市砰左,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌场航,老刑警劉巖菜职,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異旗闽,居然都是意外死亡酬核,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進店門适室,熙熙樓的掌柜王于貴愁眉苦臉地迎上來嫡意,“玉大人,你說我怎么就攤上這事捣辆∈呙” “怎么了?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵汽畴,是天一觀的道長旧巾。 經(jīng)常有香客問我耸序,道長,這世上最難降的妖魔是什么鲁猩? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任坎怪,我火速辦了婚禮,結(jié)果婚禮上廓握,老公的妹妹穿的比我還像新娘搅窿。我一直安慰自己,他們只是感情好隙券,可當我...
    茶點故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布男应。 她就那樣靜靜地躺著,像睡著了一般娱仔。 火紅的嫁衣襯著肌膚如雪沐飘。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天牲迫,我揣著相機與錄音耐朴,去河邊找鬼。 笑死恩溅,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的谓娃。 我是一名探鬼主播脚乡,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼滨达!你這毒婦竟也來了奶稠?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤捡遍,失蹤者是張志新(化名)和其女友劉穎锌订,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體画株,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡辆飘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了谓传。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蜈项。...
    茶點故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖续挟,靈堂內(nèi)的尸體忽然破棺而出紧卒,到底是詐尸還是另有隱情,我是刑警寧澤诗祸,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布跑芳,位于F島的核電站轴总,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏博个。R本人自食惡果不足惜怀樟,卻給世界環(huán)境...
    茶點故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望坡倔。 院中可真熱鬧漂佩,春花似錦、人聲如沸罪塔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽征堪。三九已至瘩缆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間佃蚜,已是汗流浹背庸娱。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留谐算,地道東北人熟尉。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像洲脂,于是被迫代替她去往敵國和親斤儿。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,515評論 2 359