1. 程式人生 > >小米開源資料庫<pegasus>簡介

小米開源資料庫<pegasus>簡介

更好的樣式前往 我的Github筆記 檢視

<md文件排版不好>

資料模型

組合鍵:Table + HashKey + SortKey

  1. Table實現業務資料的隔離

  2. HashKey決定資料在那個分片

  3. SortKey決定資料在分片內的排序

一致性協議

使用PacificA協議,保證多副本資料的一致性。

<!-- addition -->

機器

Pegasus分散式叢集至少需要準備這些機器:

  • MetaServer

    • link:Meta Server 的設計

    • 要求:2~3臺機器,無需SSD盤。

    • 作用:用來儲存表和表的分片資訊。

  • ReplicaServer:

    • link:Replica Server 的設計

    • 要求至少3臺機器,建議掛SSD盤。譬如一臺伺服器掛著8塊或者12塊SSD盤。這些機器要求是同構的,即具有相同的配置。

    • 作用:至少三臺ReplicaServer。每一個ReplicaServer都是由多個Replica組成的。每一個Replica表示的是一個數據分片的Primary或Secondary。

  • Collector:可選角色,1臺機器,無需SSD盤。該程序主要用於收集和彙總叢集的統計資訊,負載很小,建議放在MetaServer的其中一臺機器上。

Replica

  • 一個數據分片對應3個Replica。

  • Replica有兩個種類:Primary和Secondary。

  • 在一個數據分片對應的 3 個或以上的Replica中,只有1個Primary,其餘的均是Secondary。

  • 很多個Replica組成一個ReplicaServer,同一個ReplicaServer中的Replica種類不相同。

    • 同一個ReplicaServer中的Replica不一定全是Primary,也不一定全是Secondary。

  • 一個最基本的Pegasus叢集,最少需要 3 個ReplicaServer。

在Pegasus中,一個Replica有如下幾種狀態:

  • Primary

  • Secondary

  • PotentialSecondary(learner):

    • 當group中新新增一個成員時,在它補全完資料成為Secondary之前的狀態

  • Inactive:

    • 和MetaServer斷開連線時候的狀態,或者在向MetaServer請求修改group的PartitionConfiguration時的狀態

  • Error:

    • 當Replica發生IO或者邏輯錯誤時候的狀態

寫流程

寫流程類似於兩段提交:

  1. 客戶端根據Key首先查詢MetaServer,查詢到這個Key對應的分片的對應的ReplicaServer。具體來說,客戶端需要的其實是分片Primary所在的ReplicaServer。

  2. 客戶端向Primary ReplicaServer發起寫請求。

  3. Primary ReplicaServer向其對應的兩臺或以上的Secondary ReplicaServer複製資料。

  4. Secondary ReplicaServer將資料寫入成功後,Primary ReplicaServer向客戶端返回成功的響應。

可能導致ReplicaServer不能響應寫請求的原因有:

  1. ReplicaServer無法向MetaServer持續彙報心跳,自動下線。

  2. Replica在IO上發生了一些無法恢復的異常故障,自動下線。

  3. MetaServer將Replica的Primary進行了遷移。

  4. Primary在和MetaServer進行group成員變更的操作,拒絕寫。

  5. 當前Secondary個數太少,Replica出於安全性考慮拒絕寫。

  6. 出於流控考慮而拒絕寫。

讀流程

只從Primary讀。

宕機恢復

  • MetaServer和所有的ReplicaServer維持心跳。

  • 通過心跳來實現失敗檢測。

  • 宕機恢復的幾種情況:

    • Primary Failover:如果某個分割槽的Primary所在的ReplicaServer宕機了,那麼MetaServer 就會選擇一個Secondary成為Primary。過後再新增Secondary。

    • Secondary Failover:如果某個分割槽的Secondary所在的ReplicaServer宕機了,那麼暫時使用一主一副的機構繼續提供服務。過後再新增Secondary。

    • MetaServer Failover:主MetaServer宕機了,備用的MetaServer通過zookeeper搶主成為新的主MetaServer。從zookeeper恢復狀態,然後重新和所有ReplicaServer建立心跳。

  • 宕機恢復過程中,儘量避免資料的跨節點複製。

zookeeper搶主

<!-- zookeeper搶主 -->

link:zookeeper搶主

單機儲存

一個ReplicaServer包括多個Replica,Replica使用RocksDB作為儲存引擎:

  • 關閉掉了rocksdb的WAL。

  • PacificA對每條寫請求都編了SequenceID,RocksDB對寫請求也有內部的SequenceID。Pegasus對二者做了融合,來支援自定義的checkpoint的生成。

  • Pegasus給RocksDB添加了一些compaction filter以支援Pegasus的語義:例如某個value的TTL。

和很多一致性協議的實現一樣,Pegasus中PacificA的實現也是和儲存引擎解耦的。

RocksDB

<!-- RocksDB -->

link:RocksDB

資料安全

  • Table軟刪除

    • Table刪除後,資料會保留一段時間,防止誤刪除

  • 元資料恢復

    • Zookeeper損壞時,從各ReplicaServer收集並重建元資料

  • 遠端冷備份

    • 資料定期備份到異地,譬如HDFS或者金山雲 • 在需要的時候可快速恢復

  • 跨機房同步

    • 在多個機房部署叢集

    • 採用非同步複製的方式同步資料

冷備份

Pegasus的冷備份功能用來將Pegasus中的資料定期生成快照檔案,並備份到其他儲存介質上,從而為資料容災多提供一層保障。但由於備份的是某個時間點的資料快照檔案,所以冷備份並不保證可以保留所有最新的資料,也就是說,恢復的時候可能會丟失最近一段時間的資料。

具體來看,冷備份過程要涉及到如下一些引數:

  • 儲存介質(backup_provider):

    • 指其他的檔案儲存系統或服務,如本地檔案系統或者HDFS。

  • 資料冷備份的週期(backup_interval):

    • 週期的長短決定了備份資料的覆蓋範圍。如果週期是1個月,那麼恢復資料時,就可能只恢復一個月之前的資料。但如果週期設的太短,備份就會太頻繁,從而使得備份開銷很大。在小米內部,冷備份的週期通常是1天。

  • 保留的冷備份個數(backup_history_count):

    • 保留的備份個數越多,儲存的空間開銷就越大。在小米內部,一般保留最近的3個冷備份。

  • 進行冷備份的表的集合(backup_app_ids):

    • 並不是所有的表都值得進行冷備份。在小米內部,對於經常重灌全量資料的表,我們是不進行冷備份的。

在Pegasus中,以上這幾個引數的組合稱為一個冷備份策略(backup_policy)。資料的冷備份就行按照policy為單位進行的。

跨機房同步

link:跨機房同步文件

小米內部有些業務對服務可用性有較高要求,但又不堪每年數次機房故障的煩惱,於是向 pegasus 團隊尋求幫助,希望在機房故障時,服務能夠切換流量至備用機房而資料不致丟失。因為成本所限,在小米內部以雙機房為主。

通常解決該問題有幾種思路:

  1. 由 client 將資料同步寫至兩機房。這種方法較為低效,容易受跨機房專線頻寬影響,並且延時高,同機房 1ms 內的寫延時在跨機房下通常會放大到幾十毫秒,優點是一致性強,但需要 client 實現。服務端的複雜度小,客戶端的複雜度大。

  2. 使用 raft/paxos 協議進行 quorum write 實現機房間同步。這種做法需要至少 3 副本分別在 3 機房部署,延時較高但提供強一致性,因為要考慮跨叢集的元資訊管理,這是實現難度最大的一種方案。

  3. 在兩機房下分別部署兩個 pegasus 叢集,叢集間進行非同步複製。機房 A 的資料可能會在 1 分鐘後複製到機房 B,但 client 對此無感知,只感知機房 A。在機房 A 故障時,使用者可以選擇寫機房 B。這種方案適合 最終一致性/弱一致性 要求的場景。後面會講解我們如何實現 “最終一致性”。

基於實際業務需求考慮,我們選擇方案3。

即使同樣是做方案 3 的叢集間非同步同步,業內的做法也有不同:

  1. 各叢集單副本:這種方案考慮到多叢集已存在冗餘的情況下,可以減少單叢集內的副本數,同時既然一致性已沒有保證,大可以索性脫離一致性協議,完全依賴於穩定的叢集間網路,保證即使單機房宕機,損失的資料量也是僅僅幾十毫秒內的請求量級。考慮機房數為 5 的時候,如果每個機房都是 3 副本,那麼全量資料就是 3*5=15 副本,這時候簡化為各叢集單副本的方案就是幾乎最自然的選擇。

  2. 同步工具作為外部依賴使用:跨機房同步自然是儘可能不影響服務是最好,所以同步工具可以作為外部依賴部署,單純訪問節點磁碟的日誌(WAL)並轉發日誌。這個方案對日誌 GC 有前提條件,即日誌不可以在同步完成前被刪除,否則就丟資料了,但儲存服務日誌的 GC 是外部工具難以控制的。所以可以把日誌強行保留一週以上,但缺點是磁碟空間的成本較大。同步工具作為外部依賴的優點在於穩定性強,不影響服務,缺點在於對服務的控制能力差,很難處理一些瑣碎的一致性問題(後面會講到),難以實現最終一致性。

  3. 同步工具嵌入到服務內部:這種做法在工具穩定前會有一段陣痛期,即工具的穩定性影響服務的穩定性。但實現的靈活性肯定是最強的。

最初 Pegasus 的熱備份方案借鑑於 HBase Replication,基本只考慮了第三種方案。而事實證明這種方案更容易保證 Pegasus 儲存資料不丟的屬性。

每個 replica (這裡特指每個分片的 primary,注意 secondary 不負責熱備份複製)獨自複製自己的 private log 到遠端,replica 之間互不影響。複製直接通過 pegasus client 來完成。每一條寫入 A 的記錄(如 set / multiset)都會通過 pegasus client 複製到 B。為了將熱備份的寫與常規寫區別開,我們這裡定義 duplicate_rpc 表示熱備寫。

A->B 的熱備寫,B 也同樣會經由三副本的 PacificA 協議提交,並且寫入 private log 中。這裡有一個問題是,在 A,B 互相同步的場景,一份寫操作將形成迴圈:A->B->A,同樣的寫會無數次地被重放。為了避免迴圈寫,我們引入 cluster id 的概念,每條 duplicate_rpc 都會標記傳送者的 cluster id。

[duplication-group]
A=1
B=2
void set(String tableName, byte[] hashKey, byte[] sortKey, byte[] value, int ttlSeconds)

直接使用pegasus優化,直接對pegasus讀、寫。可以替代redis快取的架構。

  1. 讀寫邏輯複雜

  2. 要特意維護資料一致性

  3. 服務可用性不高

  4. 機器成本高

問題:

讀取:先讀取快取,如果快取中不存在,那麼再讀取資料庫中的資料。

寫入:雙寫,既寫入快取、也要寫入資料庫。

原先:Redis 作為快取 + HBase/mysql/MongoDB 作為資料庫。

業務應用

 HashKeySortKeyValue
map MapId key value
set SetId key null
list ListId index value

Pegasus本身不支援容器型別,但是其HashKey + SortKey的資料模型可以模擬容器。

容器支援

對HashKey或者SortKey進行字串匹配, 只有符合條件的結果才會返回。對HashKey或者SortKey進行字串匹配,只有符合條件的

條件過濾

同一個HashKey的資料寫入同一個Replica,同一個Replica的操作,在同一個執行緒中序列執行。這樣就避免了同步的問題。

對同一個HashKey的寫操作,保證總是原子的,包括set、multiSet、del、multiDel、incr、 checkAndSet。

單行事務

支援對資料指定過期時間, 資料過期後就無法讀取到。

TTL過期策略
  1. 執行緒安全

    • 所有的介面都是執行緒安全的,不用擔心多執行緒的問題。

  2. 併發效能

    • 客戶端底層使用非同步的方式實現,可以支援大的併發,不用擔心效能的問題。

  3. Client單例

    • 通過 getSingletonClient() 獲得的Client是單例, 可以重複使用。

  4. 翻頁功能

    • 通過客戶端提供的介面,能夠輕鬆實現資料翻頁功能 。

Java客戶端

叢集使用falcon進行監控。

叢集監控

使用

熱備份同時也需要容忍在 replica 主備切換下複製的進度不會丟失,例如當前 replica1 複製到日誌 decree=5001,此時發生主備切換,我們不想看到 replica1 從 0 開始,所以為了能夠支援 斷點續傳,我們引入 confirmed_decree。replica 定期向 meta 彙報當前進度(如 confirmed_decree = 5001),一旦meta將該進度持久化至 zookeeper,當replica故障恢復時即可安全地從 5001重新開始熱備份。

所以當 B 重放某條 duplicate_rpc 時,發現其 cluster_id = 1,識別到這是一條發自 A 的熱備寫,則不會將它再發往