ElasticSearch 寫操作 剖析


ElasticSearch 寫操作 剖析

在看ElasticSearch權威指南基礎入門中關於:分片內部原理這一小節內容后,大致對ElasticSearch的索引、搜索底層實現有了一個初步的認識。記錄一下在看文檔的過程中碰到的問題以及我的理解。此外,在文章的末尾,還討論分布式系統中的主從復制原理,以及采用這種副本復制方案帶來的數據一致性問題。

ElasticSearch index 操作背后發生了什么?

更具體地,就是執行PUT操作向ElasticSearch添加一篇文檔時,底層發生的一系列操作。

PUT user/profile/10
{
  "content":"向user索引中添加一篇id為10的文檔"
}

通過PUT請求發起了索引新文檔的操作,該操作能夠執行的前提是:集群中有“一定數量”的活躍 shards。這個配置由wait_for_active_shards指定。ElasticSearch關於分片有2個重要的概念:primary shard 和 replica。在定義索引的時候指定索引有幾個主分片,以及每個主分片有多少個副本。比如:

PUT user
{
  "settings": {
    "number_of_shards": 3,
    "number_of_replicas": 2
  },

介紹一下集群的環境:ElasticSearch6.3.2三節點集群。定義了一個user索引,該索引有三個主分片,每個主分片2個副本。如圖,每個節點上有三個shards:一個 primary shard,二個replica

wait_for_active_shards

To improve the resiliency of writes to the system, indexing operations can be configured to wait for a certain number of active shard copies before proceeding with the operation. If the requisite number of active shard copies are not available, then the write operation must wait and retry, until either the requisite shard copies have started or a timeout occurs.

在索引一篇文檔時,通過wait_for_active_shards指定有多少個活躍的shards時,才能執行索引文檔的操作。默認情況下,只要primary shard 是活躍的就可以索引文檔。即wait_for_active_shards值為1

By default, write operations only wait for the primary shards to be active before proceeding (i.e. wait_for_active_shards=1)

來驗證一下:在只有一台節點的ElasticSearch上:三個primary shard 全部分配在一台節點中,並且存在着未分配的replica

執行:

PUT user/profile/10
{
  "content":"向user索引中添加一篇id為10的文檔"
}

返回結果:

{
  "_index": "user",
  "_type": "profile",
  "_id": "10",
  "_version": 1,
  "result": "created",
  "_shards": {
    "total": 3,
    "successful": 1,
    "failed": 0
  },
  "_seq_no": 0,
  "_primary_term": 1
}

在_shards 中,total 為3,說明該索引操作應該在3個(一個primary shard,二個replica)分片中執行成功;但是successful為1 說明 PUT操作 在其中一個分片中執行成功了,就返回給client索引成功的確認。這個分片就是primary shard,因為只有一台節點,另外2個replica 屬於 unassigned shards,不可能在2個replica 中執行成功。總之,默認情況下,只要primary shard 是活躍的,就能索引文檔(index操作)

現在在單節點的集群上,修改索引配置為:wait_for_active_shards=2,這意味着一個索引操作至少要在2個分片上執行成功,才能返回給client acknowledge。

"settings": {
        "index.write.wait_for_active_shards": "2"
    }

再次向user索引中PUT 一篇文檔:

PUT user/profile/10
{
  "content":"向user索引中添加一篇id為10的文檔"
}

返回結果:

{
  "statusCode": 504,
  "error": "Gateway Time-out",
  "message": "Client request timeout"
}

由於是單節點的ElasticSearch,另外的2個replica無法分配,因此不可能是活躍的。而我們指定的wait_for_active_shards為2,但現在只有primary shard是活躍的,還差一個replica,因此無法進行索引操作了。

The primary shard assigned to perform the index operation might not be available when the index operation is executed. Some reasons for this might be that the primary shard is currently recovering from a gateway or undergoing relocation. By default, the index operation will wait on the primary shard to become available for up to 1 minute before failing and responding with an error.

索引操作會在1分鍾之后超時。再看創建索引的org.elasticsearch.cluster.metadata.MetaDataCreateIndexService源代碼涉及到兩個參數:一個是wait_for_active_shards,另一個是ackTimeout,就好理解了。

    public void createIndex(final CreateIndexClusterStateUpdateRequest request,
                            final ActionListener<CreateIndexClusterStateUpdateResponse> listener) {
        onlyCreateIndex(request, ActionListener.wrap(response -> {
            if (response.isAcknowledged()) {
                activeShardsObserver.waitForActiveShards(new String[]{request.index()}, request.waitForActiveShards(), request.ackTimeout(),
                    shardsAcknowledged -> {
                        if (shardsAcknowledged == false) {
                            logger.debug("[{}] index created, but the operation timed out while waiting for " +
                                             "enough shards to be started.", request.index());
                        }

總結一下:由於文檔最終是存在在某個ElasticSearch shard下面的,而每個shard又設置了副本數。默認情況下,在進行索引文檔操作時,ElasticSearch會檢查活躍的分片數量是否達到wait_for_active_shards設置的值。若未達到,則索引操作會超時,超時時間為1分鍾。另外,值得注意的是:檢查活躍分片數量只是在開始索引數據的時候檢查,若檢查通過后,在索引文檔的過程中,集群中又有分片因為某些原因掛掉了,那么並不能保證這個文檔一定寫入到 wait_for_active_shards 個分片中去了 。

因為索引文檔操作(也即寫操作)發生在 檢查活躍分片數量 操作之后。試想以下幾個問題:

  • 問題1:檢查活躍分片數量滿足 wait_for_active_shards 設置的值之后,在持續 bulk index 文檔過程中有 shard 失效了(這里的shard是replica),那 難道不能繼續索引文檔了?
  • 問題2:在什么時候檢查集群中的活躍分片數量?難道要 每次client發送索引文檔請求時就要檢查一次嗎?還是說周期性地隔多久檢查一次?
  • 問題3:這里的 check-then-act 並不是原子操作,因此wait_for_active_shards這個配置參數又有多大的意義?

因此,官方文檔中是這么說的:

It is important to note that this setting greatly reduces the chances of the write operation not writing to the requisite number of shard copies, but it does not completely eliminate the possibility, because this check occurs before the write operation commences. Once the write operation is underway, it is still possible for replication to fail on any number of shard copies but still succeed on the primary.

  • 該參數只是盡可能地保證新文檔能夠寫入到我們所要求的shard數量中(reduce the chance of ....)。比如:wait_for_active_shards設置為2,那也只是盡可能地保證將新文檔寫入了2個shard中,當然一個是primary shard,另外一個是某個replica
  • check 操作發生在 write操作之前,某個doc的寫操作check actives shard發現符合要求,但check完之后,某個replica掛了,只要不是primary shard,那該doc的寫操作還是會繼續進行。但是在返回給用戶響應中,會標識出有多少個分片失敗了。實際上,ES索引一篇文檔時,是要求primary shard寫入該文檔,然后primary shard將文件並行轉發給所有的replica,當所有的replica都"寫入"之后,給primary shard響應,primary shard才返回ACK給client。從這里可看出,需要等到primary shard以及所有的replica都寫入文檔之后,client才能收到響應。那么,wait_for_active_shards這個參數的意義何在呢?我的理解是:這個參數只是"盡可能"保證doc寫入到所有的分片,如果活躍的分片數量未達到wait_for_active_shards,那么寫是不允許的,而達到了之后,才允許寫,而又由於check-then-act並不是原子操作,並不能保證doc一定是成功地寫入到wait_for_active_shards個分片中去了。(總之,在正常情況下,ES client 寫請求一篇doc時,該doc在primary shard上寫入,然后並行轉發給各個replica,在各個replica上也執行寫完成(這里的完成有可能是成功,也有可能是失敗),primary shard收到各個replica寫完成的響應,才返回響應給ES client。參考這篇文章

最后,說一下wait_for_active_shards參數的取值:可以設置成 all 或者是 1到 number_of_replicas+1 之間的任何一個整數。

Valid values are all or any positive integer up to the total number of configured copies per shard in the index (which is number_of_replicas+1)

number_of_replicas 為索引指定的副本的數量,加1是指:再算上primary shard。比如前面user索引的副本數量為2,那么wait_for_active_shards最多設置為3。

好,前面討論完了ElasticSearch能夠執行索引操作(寫操作)了,接下來是在寫操作過程中發生了什么?比如說ElasticSearch是如何做到近實時搜索的?在將文檔寫入ElasticSearch時候發生了故障,那文檔會不會丟失?

由於ElasticSearch底層是Lucene,在將一篇文檔寫入ElasticSearch,並最終能被Client查詢到,涉及到以下幾個概念:倒排索引、Lucene段、提交點、translog、ElasticSearch分片。這里概念都是參考《ElasticSearch definitive guide》中相關的描述。

In Elasticsearch, the most basic unit of storage of data is a shard. But, looking through the Lucene lens makes things a bit different. Here, each Elasticsearch shard is a Lucene index, and each Lucene index consists of several Lucene segments. A segment is an inverted index of the mapping of terms to the documents containing those terms.

它們之間的關系示意圖如下:

一個ElasticSearch 索引可由多個 primary shard組成,每個primary shard相當於一個Lucene Index;一個Lucene index 由多個Segment組成,每個Segment是一個倒排索引結構表

從文檔的角度來看:文章會被analyze(比如分詞),然后放到倒排索引(posting list)中。倒排索引之於ElasticSearch就相當於B+樹之於Mysql,是存儲引擎底層的存儲結構。

當文檔寫入ElasticSearch時,文檔首先被保存在內存索引緩存中(in-memeory indexing buffer)。而in-memory buffer是每隔1秒鍾刷新一次,刷新成一個個的可搜索的段(file system cache)--下圖中的綠色圓柱表示(segment),然后這些段是每隔30分鍾同步到磁盤中持久化存儲,段同步到磁盤的過程稱為 提交 commit。(這里要注意區分內存中2個不同的區域:一個是 indexing buffer,另一個是file system cache。寫入indexing buffer中的文檔 經過 refresh 變成 file system cache中的segments,從而搜索可見)

But the new segment is written to the filesystem cache first—which is cheap—and only later is it flushed to disk—which is expensive. But once a file is in the cache, it can be opened and read, just like any other file.

在這里涉及到了兩個過程:① In-memory buffer中的文檔被刷新成段;②段提交 同步到磁盤 持久化存儲。

過程①默認是1秒鍾1次,而我們所說的ElasticSearch是提供了近實時搜索,指的是:文檔的變化並不是立即對搜索可見,但會在一秒之后變為可見,一秒鍾之后,我們寫入的文檔就可以被搜索到了,就是因為這個原因。另外ElasticSearch提供了 refresh API 來控制過程①。refresh操作強制把In-memory buffer中的內容刷新成段。refresh示意圖如下:

比如說,你可以在每次index一篇文檔之后就調用一次refresh API,也即:每索引一篇文檔就強制刷新生成一個段,這會導致系統中存在大量的小段,由於一次搜索需要查找所有的segments,因此大量的小段會影響搜索性能;此外,大量的小段也意味着OS打開了大量的文件描述符,在一定程度上影響系統資源消耗。這也是為什么ElasticSearch/Lucene提供了段合並操作的原因,因為不管是1s一次refresh,還是每次索引一篇文檔時手動執行refresh,都可能導致大量的小段(small segment)產生,大量的小段是會影響性能的。
此外,當我們討論segments時,該segment既可以是已提交的,也可以是未提交的segment。所謂已提交的segment,就是這些segment是已經fsync到磁盤上持久化存儲了的,由於已提交的segments已經持久化了,那么它們對應的translog日志也可以刪除了。而未提交的segments中包含的文檔是搜索可見的,但是如果宕機,就可能導致未提交的segments包含的文檔丟失了,此時可以從translog恢復。

對於過程②,就是將段刷新到磁盤中去,默認是每隔30分鍾一次,這個刷新過程稱為提交。如果還未來得及提交時,發生了故障,那豈不是會丟失大量的文檔數據?這個時候,就引入了translog

每篇文檔寫入到In-memroy buffer中時,同時也會向 translog中寫一條記錄。In-memory buffer 每秒刷新一次,刷新后生成新段,in-memory被清空,文檔可以被搜索。

而translog 默認是每5秒鍾刷新一次到磁盤,或者是在每次請求(index、delete、update、bulk)之后就刷新到磁盤。每5秒鍾刷新一次就是異步刷新,可以通過如下方式開啟:

PUT /my_index/_settings
{
    "index.translog.durability": "async",
    "index.translog.sync_interval": "5s"
}

這種方式的話,還是有可能會丟失文檔數據,比如Client發起index操作之后,ElasticSearch返回了200響應,但是由於translog要等5秒鍾之后才刷新到磁盤,如果在5秒內系統宕機了,那么這幾秒鍾內寫入的文檔數據就丟失了。

而在每次請求操作(index、delete、update、bulk)執行后就刷新translog到磁盤,則是translog同步刷新,比如說:當Client PUT一個文檔:

PUT user/profile/10
{
  "content":"向user索引中添加一篇id為10的文檔"
}

在前面提到的三節點ElasticSearch集群中,該user索引有三個primary shard,每個primary shard2個replica,那么translog需要在某個primary shard中刷新成功,並且在該primary shard的兩個replica中也刷新成功,才會給Client返回 200 PUT成功響應。這種方式就保證了,只要Client接收到的響應是200,就意味着該文檔一定是成功索引到ElasticSearch中去了。因為translog是成功持久化到磁盤之后,再給Client響應的,系統宕機后在下一次重啟ElasticSearch時,就會讀取translog進行恢復。

By default, Elasticsearch fsyncs and commits the translog every 5 seconds if index.translog.durability is set to async or if set to request (default) at the end of every index, delete, update, or bulk request. More precisely, if set to request, Elasticsearch will only report success of an index, delete, update, or bulk request to the client after the translog has been successfully fsynced and committed on the primary and on every allocated replica.

這也是為什么,在我們關閉ElasticSearch時最好進行一次flush操作,將段刷新到磁盤中。因為這樣會清空translog,那么在重啟ElasticSearch就會很快(不需要恢復大量的translog了)

translog 也被用來提供實時 CRUD 。當你試着通過ID查詢、更新、刪除一個文檔,它會在嘗試從相應的段中檢索之前, 首先檢查 translog 任何最近的變更。這意味着它總是能夠實時地獲取到文檔的最新版本。

放一張總結性的圖,如下:

有個問題是:為什么translog可以在每次請求之后刷新到磁盤?難道不會影響性能嗎?相比於將 段(segment)刷新到磁盤,刷新translog的代價是要小得多的,因為translog是經過精心設計的數據結構,而段(segment)是用於搜索的"倒排索引",我們無法做到每次將段刷新到磁盤;而刷新translog相比於段要輕量級得多(translog 可做到順序寫disk,並且數據結構比segment要簡單),因此通過translog機制來保證數據不丟失又不太影響寫入性能。

Changes to Lucene are only persisted to disk during a Lucene commit, which is a relatively expensive operation and so cannot be performed after every index or delete operation.
......
All index and delete operations are written to the translog after being processed by the internal Lucene index but before they are acknowledged. In the event of a crash, recent transactions that have been acknowledged but not yet included in the last Lucene commit can instead be recovered from the translog when the shard recovers.

如果宕機,那些 已經返回給client確認但尚未 lucene commit 持久化到disk的transactions,可以從translog中恢復。

總結一下:

這里一共有三個地方有“刷新操作”,其中 refresh 的應用場景是每個Index/Update/Delete 單個操作之后,要不要refresh一下?而flush 是針對索引而言的:要不要對 twitter 這個索引 flush 一下,使得在內存中的數據是否已經持久化到磁盤上了,這里會引發Lucene的commit,當這些數據持久化磁盤上后,相應的translog就可以刪除了(因為這些數據已經持久化到磁盤,那就是可靠的了,如果發生意外宕機,需要借助translog恢復的是那些尚未來得及flush到磁盤上的索引數據)

  1. in-memory buffer 刷新 生成segment

    每秒一次,文檔刷新成segment就可以被搜索到了,ElasticSearch提供了refresh API 來控制這個過程

  2. translog 刷新到磁盤

    index.translog.durability來設置,或者由index.translog.flush_threshold_size來設置當translog達到一定大小之后刷新到磁盤(默認512MB)

  3. 段(segment) 刷新到磁盤(flush)

    每30分鍾一次,ElasticSearch提供了flush API 來控制這個過程。在段被刷新到磁盤(就是通常所說的commit操作)中時,也會清空刷新translog。

寫操作的可靠性如何保證?

數據的可靠性保證是理解存儲系統差異的重要方面。比如說,要對比MySQL與ES的異同、對比ES與redis的區別,就可以從數據的可靠性這個點入手。在MySQL里面有redo log、基於 bin log的主從復制、有double write等機制,那ES的數據可靠性保證又是如何實現的呢?
我認為在分布式系統中討論數據可靠性需要從二個角度出發:單個副本數據的本地刷盤策略 和 寫多個副本的之后何時向client返回響應 也即 同步復制、異步復制的問題。

  • 1 單個副本數據的本地刷盤策略
    在ES中,這個策略叫translog機制。當向ES索引一篇doc時,doc先寫入in-memory buffer,同時寫入translog。translog可配置成每次寫入之后,就flush disk。這與MySQL寫redo log日志的的原理是類似的。

  • 2 寫多個副本的之后何時向client返回響應
    這里涉及到參數wait_for_active_shards,前面提到這個參數雖有一些"缺陷"(check-then-act),但它還是盡可能地保證一篇doc在寫入primary shard並且也"寫入"(寫入並不代表落盤)了若干個replica之后,才返回ack給client。ES中的 wait_for_active_shards參數與Kafka中 broker配置 min.insync.replicas 原理是類似的。

存在的一些問題

這個 issue 和 這個 issue 討論了index.write.wait_for_active_shards參數的來龍去脈。

以三節點ElasticSearch6.3.2集群,索引設置為3個primary shard,每個primary shard 有2個replica 來討論:

  • client向其中一個節點發起Index操作索引文檔,這個寫操作請求當然是發送到primary shard上,但是當Client收到200響應時,該文檔是否已經復制到另外2個replica上?
  • Client將一篇文檔成功寫入到ElasticSearch了(收到了200響應),它能在replica所在的節點上 GET 到這篇文檔嗎?Client發起查詢請求,又能查詢到這篇文檔嗎?(注意:GET 和 Query 是不一樣的)
  • 前面提到,當 index 一篇文檔時,primary shard 和2個replica 上的translog 要 都刷新 到磁盤,才返回 200 響應,那它是否與參數 index.write.wait_for_active_shards默認值 矛盾?因為index.write.wait_for_active_shards默認值為1,即:只要primary shard 是活躍的,就可以進行 index 操作。也就是說:當Client收到200的index成功響應,此時primary shard 已經將文檔 復制 到2個replica 上了嗎?這兩個 replica 已經將文檔刷新成 segment了嗎?還是說這兩個 replica 僅僅只是 將索引該文檔的 translog 刷新到磁盤上了?

ElasticSearch副本復制方式討論

ElasticSearch索引是一個邏輯概念,囊括現實世界中的數據。比如 定義一個 user 索引存儲所有的用戶資料信息。索引由若干個primary shard組成,就相當於把用戶資料信息 分開成 若干個部分存儲,每個primary shard存儲user索引中的一部分數據。為了保證數據可靠性不丟失,可以為每個primary shard配置副本(replica)。顯然,primary shard 和它對應的replica 是不會存儲在同一台機器(node)上的,因為如果該機器宕機了,那么primary shard 和 副本(replica) 都會丟失,那整個系統就丟失一部分數據了。

primary shard 和 replica 這種副本備份方案,稱為主從備份。primary shard是主(single leader),replica 是 從 (multiple replica)。由於是分布式環境,可能存在多個Client同時向ElasticSearch發起索引文檔的請求,這篇文檔會根據 文檔id 哈希到某個 primary shard,primary shard寫入該文檔 並分發給 replica 進行存儲。由於采用了哈希,這也是為什么 在定義索引的時候,需要指定primary shard個數,並且 primary shard個數一經指定后不可修改的原因。因為primary shard個數一旦改變,哈希映射 結果就變了。而采用這種主從副本備份方案,這也是為什么 索引操作(寫操作、update操作) 只能由 primary shard處理,而讀操作既可以從 primary shard讀取,也可以從 replica 讀取的原因。相對於文檔而言,primary shard是single leader,所有的文檔修改操作都統一由primary shard處理,能避免一些 並發修改 沖突。但是默認情況下,ElasticSearch 副本復制方式 是異步的,也正如前面 index.write.wait_for_active_shards討論,只要primary shard 是活躍的就可以進行索引操作,primary shard 將文檔 “ 存儲 ” 之后,就返回給client 響應,然后primary shard 再將該文檔同步給replicas,而這就是異步副本復制方式。在ElasticSearch官方討論論壇里面,也有關於副本復制方式的討論:這篇文章提出了一個問題:Client向primary shard寫入文檔成功,primary shard 是通過何種方式將該文檔同步到 replica的?

其實覺得這里的 異步復制 提法 有點不准確,不過放到這里,供大家討論參考吧

關於primary shard 將文檔同步給各個replica,涉及到 in-sync replica概念,在master節點中維護了一個 in-sync 副本列表。

Since replicas can be offline, the primary is not required to replicate to all replicas. Instead, Elasticsearch maintains a list of shard copies that should receive the operation. This list is called the in-sync copies and is maintained by the master node.

當index操作發送到 primary shard時,primary shard 並行轉發給 in-sync副本,等待各個in-sync副本給primary 響應。primary shard收到所有in-sync副本響應后,再給Client響應說:Index操作成功。由於副本可能會出故障,當沒有in-sync副本,只有primary shard在正常工作時,此時的index操作只在primary shard上執行成功就返回給Client了,這里就存在了所謂的“單點故障”,因為primary shard所在的節點掛了,那 就會丟失 index 操作的文檔了。這個時候, index.write.wait_for_active_shards 參數就起作用了,如果將 index.write.wait_for_active_shards 設置為2,那么 當沒有in-sync副本,只有primary shard在正常工作時,index 操作就會被拒絕。所以,在這里,index.write.wait_for_active_shards參數起到一個避免單點故障的功能。更具體的細節可參考data-replication model

采用異步副本復制方式帶來的一個問題是:讀操作能讀取最新寫入的文檔嗎?如果我們指定讀請求去讀primary shard(通過ElasticSearch 的路由機制),那么是能讀到最新數據的。但是如果讀請求是由某個 replica 接收處理,那也許就不能讀取到剛才最新寫入的文檔了。因此,從剛才Client 讀請求的角度來看,ElasticSearch能提供 哪種程度的 一致性呢?而出現這種一致性問題的原因在於:為了保證數據可靠性,采用了副本備份,引入了副本,導致副本和primary shard上的數據不一致,即:存在 replication lag 問題。由於這種副本復制延遲帶來的問題,系統需要給Client 某種數據一致性的 保證,比如說:

  • read your own write

    Client能夠讀取到它自己最新寫入的數據。比如用戶修改了昵稱,那TA訪問自己的主頁時,能看到自己修改了的昵稱,但是TA的好友 可能 並不能立即看到 TA 修改后的昵稱。好友請求的是某個 replica 上的數據,而 primary shard還未來得及把剛才修改的昵稱 同步 到 replica上。

  • Monotonic reads

    單調讀。每次Client讀取的值,是越來越新的值(站在Client角度來看的)。比如說NBA籃球比賽,Client每10分鍾讀一次比賽結果。第10分鍾讀取到的是 1:1,第20分鍾讀到的是2:2,第30分鍾讀到的是3:3,假設在第40分鍾時,實際比賽結果是4:4,Cleint在第40分鍾讀取的時候,讀到的值可以是3:3 這意味着未讀取到最新結果而已,讀到的值也可以是4:4, 但是不能是2:2 。

  • consistent prefix reads

    符合因果關系的一種讀操作。比如說,用戶1 和 用戶2 對話:

    用戶1:你現在干嘛?
    用戶2:寫代碼

    對於Client讀:應該是先讀取到“你現在干嘛?”,然后再讀取到 “寫代碼。如果讀取結果順序亂了,Client就會莫名其妙。

正是由於Client 有了系統給予的這種 一致性 保證,那么Client(或者說應用程序)就能基於這種保證 來開發功能,為用戶提供服務。

那系統又是如何提供這種一致性保證的呢?或者說ElasticSearch集群又提供了何種一致性保證?經常聽到的有:強一致性(linearizability)、弱一致性、最終一致性。對於強一致性,通俗的理解就是:實際上數據有多份(primary shard 以及多個 replica),但在Client看來,表現得就只有一份數據。在多個 client 並發讀寫情形下,某個Client在修改數據A,而又有多個Client在同時讀數據A,linearizability 就要保證:如果某個Client讀取到了數據A,那在該Client之后的讀取請求返回的結果都不能比數據A要 舊,至少是數據A的當前值(不能是數據A的舊值)。不說了,再說,我自己都不明白了。

至於系統如何提供這種一致性,會用到一些分布式共識算法,我也沒有深入地去研究過。關於副本復制方式的討論,也可參考這篇文章:分布式系統理論之Quorum機制

參考資料

原文:https://www.cnblogs.com/hapjin/p/9821073.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM