天天看點

全球部署的分布式資料庫 YugabyteDB,了解一下?

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 實作高可用。

全球部署的分布式資料庫 YugabyteDB,了解一下?

基于 Tablet 的分布式存儲

這一部分是 HBase/Spanner 精髓部分,Cockroach/TiDB 的做法幾乎也是一模一樣的。如下圖所示,每張表被分成很多個 tablet,tablet 是資料分布的最小單元,通過在節點間搬運 tablet 以及 tablet 的分裂與合并,就可以實作幾乎無上限的 scale out。每個 tablet 有多個副本,形成一個 Raft Group,通過 Raft 協定保證資料的高可用和持久性,Group Leader 負責處理所有的寫入負載,其他 Follower 作為備份。

下圖是一個例子:一張表被分成 16 個 tablet,tablet 的副本和 Raft Group leader 均勻分布在各個節點上,分别保證了資料的均衡和負載的均衡。

全球部署的分布式資料庫 YugabyteDB,了解一下?

和其他産品一樣,Master 節點會負責協調 tablet 的搬運、分裂等操作,保證叢集的負載均衡。這些操作是直接基于 Raft Group 實作的。這裡就不再展開了。

有趣的是,Yugabyte 采用哈希和範圍結合的分區方式:可以隻有哈希分區、也可以隻有範圍分區、也可以先按哈希再按範圍分區。之是以這麼設計,猜測也是因為 Cassandra 的影響。相比之下,TiDB 和 Cockroach 都隻支援範圍分區。

哈希分區的方式是将 key 哈希映射到 2 位元組的空間中(即 0x0000 到 0xFFFF),這個空間又被劃分成多個範圍,比如下圖的例子中被劃分為 16 個範圍,每個範圍的 key 落在一個 tablet 中。理論上說最多可能有 64K 個 tablet,這對實際使用足夠了。

全球部署的分布式資料庫 YugabyteDB,了解一下?

哈希分區的好處是插入資料(尤其是從尾部 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 的值

全球部署的分布式資料庫 YugabyteDB,了解一下?

如果撇開文檔模型,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,便于恢複使用

全球部署的分布式資料庫 YugabyteDB,了解一下?

事務的狀态資訊儲存在另一個 tablet 上,包括三種可能的狀态:Pending、Committed 或 Aborted。事務從 Pending 狀态開始,終結于 Committed 或 Aborted。

事務狀态就是 Commit Point 的那個“開關”,當事務狀态切換到 Commited 的一瞬間,就意味着事務的成功送出。這是保證整個事務原子性的關鍵。

完整的送出流程如下圖所示:

全球部署的分布式資料庫 YugabyteDB,了解一下?

另外,Yugabyte 文檔中提到它除了 Snapshot Isolation 還支援 Serializable 隔離級别,但是似乎沒有看到他是如何規避 Write Skew 問題的。從 Release Notes 看來這應該是 2.0 GA 中新增加的功能,等更多資訊放出後再研究吧!

競品對比

以下表格摘自 Compare YugabyteDB to other databases:

https://docs.yugabyte.com/latest/comparisons/
全球部署的分布式資料庫 YugabyteDB,了解一下?

References

https://www.yugabyte.com/ https://www.cockroachlabs.com/blog/living-without-atomic-clocks/