點擊關注公衆號,Java乾貨及時送達
作者:Eric Fu
鏈接:https://ericfu.me/yugabyte-db-introduction/
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 的分佈式存儲
每個 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/
-
https://www.yugabyte.com/ -
https://docs.yugabyte.com/ -
https://www.cockroachlabs.com/blog/living-without-atomic-clocks/
關注Java技術棧看更多幹貨
本文分享自微信公衆號 - Java技術棧(javastack)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。