本文旨在對比Elasticsearch和MongoDB高可用和分片的實現機制。
Elasticsearch
ES天生就是分布式的,那她又是如何做到天生分布式的?
通過ES官方指南我們可以知道:
一個運行中的 Elasticsearch 實例稱為一個 節點,而集群是由一個或者多個擁有相同 cluster.name 配置的節點組成, 它們共同承擔數據和負載的壓力。當有節點加入集群中或者從集群中移除節點時,集群將會重新平均分布所有的數據。
當一個節點被選舉成為主節點時, 它將負責管理集群范圍內的所有變更,例如增加、刪除索引,或者增加、刪除節點等。 而主節點並不需要涉及到文檔級別的變更和搜索等操作,所以當集群只擁有一個主節點的情況下,即使流量的增加它也不會成為瓶頸。 任何節點都可以成為主節點。我們的示例集群就只有一個節點,所以它同時也成為了主節點。
作為用戶,我們可以將請求發送到 集群中的任何節點 ,包括主節點。 每個節點都知道任意文檔所處的位置,並且能夠將我們的請求直接轉發到存儲我們所需文檔的節點。 無論我們將請求發送到哪個節點,它都能負責從各個包含我們所需文檔的節點收集回數據,並將最終結果返回給客戶端。 Elasticsearch 對這一切的管理都是透明的。
Elasticsearch 是利用分片將數據分發到集群內各處的。分片是數據的容器,文檔保存在分片內,分片又被分配到集群內的各個節點里。 當你的集群規模擴大或者縮小時, Elasticsearch 會自動的在各節點中遷移分片,使得數據仍然均勻分布在集群里。
一個分片可以是主分片或者副本分片。 索引內任意一個文檔都歸屬於一個主分片,所以主分片的數目決定着索引能夠保存的最大數據量。
我們在創建一個索引的時候,可以定義其主分片數量和副本分片數量:
PUT /blogs
{
"settings" : {
"number_of_shards" : 3,
"number_of_replicas" : 1
}
}
如果主分片和副本分片都集中在一個節點上,那是沒辦法做到高可用的。ES的集群監控狀態會返回yellow。因此,我們需要啟動更多的節點來承載副本分片。
此時,如果再增加一個節點至集群,Node 1 和 Node 2 上各有一個分片被遷移到了新的 Node 3 節點,現在每個節點上都擁有2個分片,而不是之前的3個。
如果NODE1宕機了,ES會選舉一個新的主節點,並將R1、R2分片提升為主分片。以此來達到高可用的目的。
現在我們大致知道了ES的高可用和分片的方式,但是幾個細節任然需要繼續深入:
- ES是通過hash(文檔ID) % 主分片數來確認分片的位置的,因為ES的主分片數量不可變
- 主分片在每次文檔的寫操作執行前,都會確認大多數副本分片處於存活狀態。我們可以通過配置參數調整為只要主分片狀態 ok 就允許執行寫操作或必須要主分片和所有副本分片的狀態沒問題才允許執行寫操作
- 跨分片查詢時,客戶端發送一個 search 請求到 Node 3 , Node 3 會創建一個大小為 from + size 的空優先隊列。
Node 3 將查詢請求轉發到索引的每個主分片或副本分片中。每個分片在本地執行查詢並添加結果到大小為 from + size 的本地有序優先隊列中。
每個分片返回各自優先隊列中所有文檔的 ID 和排序值給協調節點,也就是 Node 3 ,它合並這些值到自己的優先隊列中來產生一個全局排序后的結果列表。 - Elasticsearch 增加了一個 translog ,或者叫事務日志,在每一次對 Elasticsearch 進行操作時均進行了日志記錄。一個文檔被索引之后,就會被添加到內存緩沖區,並且 追加到了 translog。translog 提供所有還沒有被刷到磁盤的操作的一個持久化紀錄。當 Elasticsearch 啟動的時候, 它會從磁盤中使用最后一個提交點去恢復已知的段,並且會重放 translog 中所有在最后一次提交后發生的變更操作。
- Elasticsearch使用了類bully的算法來實現選主。對所有可以成為master的節點根據nodeId排序,每次選舉每個節點都把自己所知道節點排一次序,然后選出第一個(第0位)節點,暫且認為它是master節點。
如果對某個節點的投票數達到一定的值(可以成為master節點數n/2+1)並且該節點自己也選舉自己,那這個節點就是master。否則重新選舉。
MongoDB
MongoDB通過復制集(Replica Set)來實現高可用。
復制集提供了數據的冗余備份,並在多個服務器上存儲數據副本,提高了數據的可用性, 並可以保證數據的安全性。
復制還允許您從硬件故障和服務中斷中恢復數據。
主節點負責所有的寫操作,從節點同步主節點的數據。仲裁節點不維護數據集,只參與選主過程。
MongoDB是通過oplog來實現復制集間的數據同步。當主節點完成寫操作后,從節點會檢查自己的local數據上的oplog集合,找出最近一條記錄的時間戳。然后查詢主節點上的oplog集合,找出大於此時間戳的記錄。最后將這些oplog查到到本地集合中並執行oplog中的操作。
MongoDB實例每個兩秒就會向其他成員發送一個心跳包來判斷其他成員的存活狀態。如果復制集的主節點不可用了,那么系統就會觸發一次選主。
選主需要時間,在選主的過程中,復制集是沒有主節點的,所有的成員都變成只讀狀態。
MongoDB也是采用Bully算法選主,選主時,有資格成為主節點的副本節點就會向其他節點發起一個選舉,希望別的節點選擇其作為主節點。若贊成票過半則設置自己為主節點。有反對票時,保持自己為從節點。
復制集中的其他成員在收到選主請求時,會判斷發起節點的數據版本是否過低。如過低則投反對票。
MongoDB分片時,需要引入路由服務器(mongos)和配置服務器(config servers)。配置服務器是一個獨立的mongod進程,保存集群和分片的元數據,即各分片包含了哪些數據的信息。路由服務器起到一個路由的功能,供程序連接。本身不保存數據,在啟動時從配置服務器加載集群信息。
MongoDB通過分片鍵(Shard Keys)對集合進行划分。每個分片集合只能有一個分片鍵,分片后分片鍵不可修改。目前支持兩種分片策略,范圍分片和hash分片。一旦分片鍵選擇完成,數據就以 數據塊(chunk) 為單位(默認64MB)根據分片鍵分散到后端1或多個分片上。mongos記錄每個塊中的數據量,達到某個閾值,就檢查是否需要拆分塊。如拆分塊,mongos更新config server的塊元數據,config server誕生新塊,修改舊塊的范圍(拆分點)。
查詢時,查詢請求不包含shard key,則mongos必須將查詢分發到所有的shard,然后合並查詢結果返回給客戶端。查詢請求包含shard key,則直接根據shard key計算出需要查詢的chunk,向對應的shard發送查詢請求。
插入時,必須包含shard key,mongos根據shard key算出文檔應該存儲到哪個chunk,然后將寫請求發送到chunk所在的shard。更新、刪除請求的查詢條件必須包含shard key或者_id,如果是包含shard key,則直接路由到指定的chunk,如果只包含_id,則需將請求發送至所有的shard。
參考文獻
https://www.elastic.co/guide/en/elasticsearch/guide/master/distributed-cluster.html
https://docs.mongodb.com/manual/core/replica-set-elections/
https://docs.mongodb.com/manual/sharding/