Yugabyte DB 是一個全球部署的分布式資料庫,和國内的 TiDB 和國外的 CockroachDB 類似,也是受到 Spanner 論文啟發,是以在很多地方這幾個資料庫存在不少相似之處。
與 Cockroach 類似,Yugabyte 也主打全球分布式的事務資料庫——不僅能把節點部署到全球各地,還能完整支援 ACID 事務,這是他最大的賣點。除此以外還有一些獨特的特性,比如支援文檔資料庫接口。如果我猜的沒錯,Yugabyte 早期被設計成一個文檔資料庫,後來才調整技術路線開始主打 SQL 接口。
本文資訊主要來自于 Yugabyte 的官方文檔:
https://docs.yugabyte.com/以及其 GitHub 首頁:
https://github.com/yugabyte/yugabyte-db系統架構
邏輯上,Yugabyte 采用兩層架構:查詢層和存儲層。不過這個架構僅僅是邏輯上的,部署結構中,這兩層都位于 TServer 程序中。這一點和 TiDB 不同。
Yugabyte 的查詢層支援同時 SQL 和 CQL 兩種 API,其中 CQL 是相容 Cassandra 的一種方言文法,對應于文檔資料庫的存儲模型;而 SQL API 是直接基于 PostgresQL 魔改的,能比較好地相容 PG 文法,據官方說這樣可以更友善地跟随 PG 新特性,有沒有官方說的這麼美好我們就不得而知了。
Yugabyte 的存儲層才是重頭戲。其中 TServer 負責存儲 tablet,每個 tablet 對應一個 Raft Group,分布在三個不同的節點上,以此保證高可用性。Master 負責中繼資料管理,除了 tablet 的位置資訊,還包括表結構等資訊。Master 本身也依靠 Raft 實作高可用。
基于 Tablet 的分布式存儲
這一部分是 HBase/Spanner 精髓部分,Cockroach/TiDB 的做法幾乎也是一模一樣的。如下圖所示,每張表被分成很多個 tablet,tablet 是資料分布的最小單元,通過在節點間搬運 tablet 以及 tablet 的分裂與合并,就可以實作幾乎無上限的 scale out。每個 tablet 有多個副本,形成一個 Raft Group,通過 Raft 協定保證資料的高可用和持久性,Group Leader 負責處理所有的寫入負載,其他 Follower 作為備份。
下圖是一個例子:一張表被分成 16 個 tablet,tablet 的副本和 Raft Group leader 均勻分布在各個節點上,分别保證了資料的均衡和負載的均衡。
和其他産品一樣,Master 節點會負責協調 tablet 的搬運、分裂等操作,保證叢集的負載均衡。這些操作是直接基于 Raft Group 實作的。這裡就不再展開了。
有趣的是,Yugabyte 采用哈希和範圍結合的分區方式:可以隻有哈希分區、也可以隻有範圍分區、也可以先按哈希再按範圍分區。之是以這麼設計,猜測也是因為 Cassandra 的影響。相比之下,TiDB 和 Cockroach 都隻支援範圍分區。
哈希分區的方式是将 key 哈希映射到 2 位元組的空間中(即 0x0000 到 0xFFFF),這個空間又被劃分成多個範圍,比如下圖的例子中被劃分為 16 個範圍,每個範圍的 key 落在一個 tablet 中。理論上說最多可能有 64K 個 tablet,這對實際使用足夠了。
哈希分區的好處是插入資料(尤其是從尾部 append 資料)時不會出現熱點;壞處是對于小範圍的範圍掃描(例如 pk BETWEEN 1 AND 10)性能會比較吃虧。
基于 RocksDB 的本地存儲
每個 TServer 節點上的本地存儲稱為 DocDB。和 TiDB/Cockroach 一樣,Yugabyte 也用 RocksDB 來做本地存儲。這一層需要将關系型 tuple 以及文檔編碼為 key-value 儲存到 RocksDB 中,下圖是對文檔資料的編碼方式,其中有不少是為了相容 Cassandra 設計的,我們忽略這些,主要關注以下幾個部分:
key 中包含
16-bit hash:依靠這個值才能做到哈希分區
主鍵資料(對應圖中 hash/range columns)
column ID:因為每個 tuple 有多個列,每個列在這裡需要用一個 key-value 來表示
hybrid timestamp:用于 MVCC 的時間戳
value 中包含
column 的值
如果撇開文檔模型,key-value 的設計很像 Cockroach:每個 cell (一行中的一列資料)對應一個 key-value。而 TiDB 是每個 tuple 打包成一個 key-value。個人比較偏好 TiDB 的做法。
分布式事務:2PC & MVCC
和 TiDB/Cockroach 一樣,Yugabyte 也采用了 MVCC 結合 2PC 的事務實作。
時間戳
時間戳是分布式事務的關鍵選型之一。Yugabyte 和 Cockroach 一樣選擇的是 Hybrid Logical Clock (HLC)。
HLC 将時間戳分成實體(高位)和邏輯(低位)兩部分,實體部分對應 UNIX 時間戳,邏輯部分對應 Lamport 時鐘。在同一毫秒以内,實體時鐘不變,而邏輯時鐘就和 Lamport 時鐘一樣處理——每當發生資訊交換(RPC)就需要更新時間戳,進而確定操作與操作之間能夠形成一個偏序關系;當下一個毫秒到來時,邏輯時鐘部分歸零。
不難看出,HLC 的正确性其實是由 Logical Clock 來保證的:它相比 Logical Clock 隻是在每個毫秒引入了一個額外的增量,顯然這不會破壞 Logical Clock 的正确性。但是,實體部分的存在将原本無意義的時間戳賦予了實體意義,提高了實用性。
個人認為,HLC 是除了 TrueTime 以外最好的時間戳實作了,唯一的缺點是不能提供真正意義上的外部一緻性,僅僅能保證相關事務之間的“外部一緻性”。另一種方案是引入中心授時節點(TSO),也就是 TiDB 使用的方案。TSO 方案要求所有事務必須從 TSO 擷取時間戳,實作相對簡單,但引入了更多的網絡 RPC,而且 TSO 過于關鍵——短時間的不可用也是極為危險的。
HLC 的實作中有一些很 tricky 的地方,比如文檔中提到的 Safe timestamp assignment for a read request。對于同一事務中的多次 read,問題還要更複雜,有興趣的讀者可以看 Cockroach 團隊的這篇部落格 Living Without Atomic Clocks:
https://www.cockroachlabs.com/blog/living-without-atomic-clocks/)。
事務送出
毫不驚奇,Yugabyte 的分布式事務同樣是基于 2PC 的。他的做法接近 Cockroach。事務送出過程中,他會在 DocDB 存儲裡面寫入一些臨時的記錄(provisional records),包括以下三種類型:
Primary provisional records:還未送出完成的資料,多了一個事務ID,也扮演鎖的角色
Transaction metadata:事務狀态所在的 tablet ID。因為事務狀态表很特殊,不是按照 hash key 分片的,是以需要在這裡記錄一下它的位置。
Reverse Index:所有本事務中的 primary provisional records,便于恢複使用
事務的狀态資訊儲存在另一個 tablet 上,包括三種可能的狀态:Pending、Committed 或 Aborted。事務從 Pending 狀态開始,終結于 Committed 或 Aborted。
事務狀态就是 Commit Point 的那個“開關”,當事務狀态切換到 Commited 的一瞬間,就意味着事務的成功送出。這是保證整個事務原子性的關鍵。
完整的送出流程如下圖所示:
另外,Yugabyte 文檔中提到它除了 Snapshot Isolation 還支援 Serializable 隔離級别,但是似乎沒有看到他是如何規避 Write Skew 問題的。從 Release Notes 看來這應該是 2.0 GA 中新增加的功能,等更多資訊放出後再研究吧!
競品對比
以下表格摘自 Compare YugabyteDB to other databases:
https://docs.yugabyte.com/latest/comparisons/References
https://www.yugabyte.com/ https://www.cockroachlabs.com/blog/living-without-atomic-clocks/