更好的樣式前往 我的Github筆記 查看 <md文檔排版不好> 數據模型 組合鍵:Table + HashKey + SortKey Table實現業務數據的隔離 HashKey決定數據在那個分片 SortKey決定數據在分片內的排序 一致性協議 使用PacificA協議,保證多副本數據的一致性。 ...
更好的樣式前往 我的Github筆記 查看
<md文檔排版不好>
組合鍵:Table + HashKey + SortKey
-
Table實現業務數據的隔離
-
-
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或者邏輯錯誤時候的狀態
-
寫流程
寫流程類似於兩段提交:
-
客戶端根據Key首先查詢MetaServer,查詢到這個Key對應的分片的對應的ReplicaServer。具體來說,客戶端需要的其實是分片Primary所在的ReplicaServer。
-
客戶端向Primary ReplicaServer發起寫請求。
-
Primary ReplicaServer向其對應的兩台或以上的Secondary ReplicaServer複製數據。
-
Secondary ReplicaServer將數據寫入成功後,Primary ReplicaServer向客戶端返回成功的響應。
可能導致ReplicaServer不能響應寫請求的原因有:
-
ReplicaServer無法向MetaServer持續彙報心跳,自動下線。
-
Replica在IO上發生了一些無法恢復的異常故障,自動下線。
-
MetaServer將Replica的Primary進行了遷移。
-
Primary在和MetaServer進行group成員變更的操作,拒絕寫。
-
當前Secondary個數太少,Replica出於安全性考慮拒絕寫。
-
出於流控考慮而拒絕寫。
讀流程
只從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 團隊尋求幫助,希望在機房故障時,服務能夠切換流量至備用機房而數據不致丟失。因為成本所限,在小米內部以雙機房為主。
通常解決該問題有幾種思路:
-
由 client 將數據同步寫至兩機房。這種方法較為低效,容易受跨機房專線帶寬影響,並且延時高,同機房 1ms 內的寫延時在跨機房下通常會放大到幾十毫秒,優點是一致性強,但需要 client 實現。服務端的複雜度小,客戶端的複雜度大。
-
使用 raft/paxos 協議進行 quorum write 實現機房間同步。這種做法需要至少 3 副本分別在 3 機房部署,延時較高但提供強一致性,因為要考慮跨集群的元信息管理,這是實現難度最大的一種方案。
-
在兩機房下分別部署兩個 pegasus 集群,集群間進行非同步複製。機房 A 的數據可能會在 1 分鐘後複製到機房 B,但 client 對此無感知,只感知機房 A。在機房 A 故障時,用戶可以選擇寫機房 B。這種方案適合 最終一致性/弱一致性 要求的場景。後面會講解我們如何實現 “最終一致性”。
基於實際業務需求考慮,我們選擇方案3。
即使同樣是做方案 3 的集群間非同步同步,業內的做法也有不同:
-
各集群單副本:這種方案考慮到多集群已存在冗餘的情況下,可以減少單集群內的副本數,同時既然一致性已沒有保證,大可以索性脫離一致性協議,完全依賴於穩定的集群間網路,保證即使單機房宕機,損失的數據量也是僅僅幾十毫秒內的請求量級。考慮機房數為 5 的時候,如果每個機房都是 3 副本,那麼全量數據就是 3*5=15 副本,這時候簡化為各集群單副本的方案就是幾乎最自然的選擇。
-
同步工具作為外部依賴使用:跨機房同步自然是儘可能不影響服務是最好,所以同步工具可以作為外部依賴部署,單純訪問節點磁碟的日誌(WAL)並轉發日誌。這個方案對日誌 GC 有前提條件,即日誌不可以在同步完成前被刪除,否則就丟數據了,但存儲服務日誌的 GC 是外部工具難以控制的。所以可以把日誌強行保留一周以上,但缺點是磁碟空間的成本較大。同步工具作為外部依賴的優點在於穩定性強,不影響服務,缺點在於對服務的控制能力差,很難處理一些瑣碎的一致性問題(後面會講到),難以實現最終一致性。
-
同步工具嵌入到服務內部:這種做法在工具穩定前會有一段陣痛期,即工具的穩定性影響服務的穩定性。但實現的靈活性肯定是最強的。
最初 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緩存的架構。
-
讀寫邏輯複雜
-
要特意維護數據一致性
-
服務可用性不高
-
機器成本高
問題:
讀取:先讀取緩存,如果緩存中不存在,那麼再讀取資料庫中的數據。
寫入:雙寫,既寫入緩存、也要寫入資料庫。
原先:Redis 作為緩存 + HBase/mysql/MongoDB 作為資料庫。
業務應用
HashKey | SortKey | Value | |
---|---|---|---|
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過期策略
-
線程安全
-
所有的介面都是線程安全的,不用擔心多線程的問題。
-
-
併發性能
-
客戶端底層使用非同步的方式實現,可以支持大的併發,不用擔心性能的問題。
-
-
Client單例
-
通過 getSingletonClient() 獲得的Client是單例, 可以重覆使用。
-
-
翻頁功能
-
通過客戶端提供的介面,能夠輕鬆實現數據翻頁功能 。
-
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 的熱備寫,則不會將它再發往 A。