跳到主要內容

區塊鏈和資料庫:致虛極,守靜篤

陈天(ArcBlock 区块基石研发副总裁)
區塊鏈資料庫

作者: 陳天(ArcBlock 區塊基石研發副總裁)

前言:為紀念中本聰發表比特幣白皮書 10 週年,ArcBlock 研發副總裁陳天特撰此文闡述他對區塊鏈與資料庫關係的獨特觀察和思考。

10 月 31 日,陳天發朋友圈感慨自己投身區塊鏈創業「半年過去了,無數打了雞血的三點鐘群已經悄無聲息,一票在泡沫裡仰泳的投機者消失了,而我,像鑽進了兔子洞的愛麗絲,徜徉在這個奇妙的世界裡……」

如果說牛頓的經典物理是愛因斯坦相對論在低速環境下的一種體現的話,我們所熟知的資料庫技術,可以認為是區塊鏈技術在弱分散式環境下的一個特例。

「弱分散式」環境是我胡扯的一個詞,你可以將其理解為節點數量極其有限,執行環境高度可控的一種分散式環境。一個資料庫叢集執行在同一資料中心,或者不同資料中心,只要是同一個管理者,那麼這就是可控的執行環境。在可控的執行環境下,預設不存在「作惡」的節點,也就無所謂 BFT(Byzantine Fault Tolerance),因此我們不需要複雜的共識演算法(consensus algorithm),一般兩階段提交(two phase commit)或者 paxos / raft 就能收斂共識,滿足需要。所以,資料庫的共識演算法是區塊鏈共識演算法的特例。

在區塊鏈的世界裡,交易(transaction)和交易產生的狀態(state)是嚴格分離的。若干交易由被共識演算法選擇出來的礦工驗證並打包成為區塊(block)並廣播出去,然後網路裡其他參與者驗證區塊中每個交易的合法性,並寫入自己的 state db。在 bitcoin 裡,state db 是 UTXO,在 ethereum 裡,則是其 world state。

資料庫的世界裡彷彿沒有類似於區塊鏈的交易記錄(資料庫中的 transaction 是另一個概念),但仔細想想,它的交易歷史其實就是 WAL(Write-Ahead Logging)。從外界接收到的請求,資料庫會先將其寫入 WAL,確保其進入持久化儲存,才會往它自己的 “states” 裡面寫入。從這個角度,我們可以認為 WAL 裡的每一個記錄,就對應區塊鏈裡的一個交易,它是區塊鏈交易的特例(當然你也可以 argue 兩個 checkpoint 之間可以算作一個區塊)。

如果你再認真思考一下,WAL,blockchain,以及 Martin Fowler 很早就提倡的 CQRS(Command Query Responsibility Segregation)在這個層面上其實都是「一中各表」:大家都強調「事件」和「狀態」的分離,透過前一個狀態 + 當前事件,可以推演出當前狀態。這樣,我們只要有一個初始的「狀態」,然後記錄系統發生過的所有「事件」,就可以復原任意一個時刻的「狀態」(如果你對 CQRS 感興趣,不要錯過我們的 arcblock tech learning series: CQRS & Commanded)。

我們回到交易和容納交易的「區塊」。你會發現,「區塊」是一個怪怪的存在,為什麼資料庫不需要「區塊」這樣的概念作為容器裝載「交易」,而區塊鏈卻需要呢?我們知道,在區塊鏈的世界裡,不確定性和確定性彷彿一對孿生兄弟,確定的是規則,不確定的是規則的執行者。所謂礦工輪流做,下回到我家,那怎麼定義一個「回合」呢?為了回答這個問題,我們需要某種機制明確一個回合礦工地位的起止 —— 這個起止就是一個「區塊」。不僅如此,在一個物理時鐘並不一致的分散式環境下,「區塊」還承載著全域時鐘的功能,滴答滴答將整個網路往前推進。「區塊」的概念是如此重要,以至於它當仁不讓地成為共識演算法的基礎 —— 大家先得對下一個要出的區塊序號達成共識,否則這個遊戲無法進行。反觀資料庫系統,在一個資料庫叢集中,master(等價於礦工)是固定的,master 令旗一揮,slave 就迅速跟進,指哪打哪,不存在輪流坐莊,也就無所謂回合,所以其實每個「交易」就是一個「區塊」。所以在資料庫的世界裡,邏輯上每個交易,或者說 WAL 的每個記錄,自成一個隱性的「區塊」。

我們從另一個角度來探討這個結論。「區塊」的另一個重要作用是 crash recovery。在一個區塊鏈網路中,某個節點無論是斷網還是崩潰,其狀態和網路中達成共識的狀態必不一致,那麼,如何從這種不一致的狀態恢復同步的狀態呢?答案是「區塊」,因為它是唯一明確的共識產物。節點總是能夠找到最近的 commit 的和網路中一致的區塊高度,然後從這個高度往後一個區塊一個區塊同步,依次執行區塊中包含的所有交易並更新本機的狀態,最終可以保證和網路中的狀態達成一致。在這裡,「區塊」就是檢測和達成狀態一致的最小單元。而在資料庫系統中,在崩潰發生後,系統會從其他節點同步最新的 WAL,並從上次 commit 的 WAL 的位置往後一個記錄一個記錄執行命令,直到所有記錄執行完畢,這時資料庫狀態恢復到叢集的當前狀態。在這裡,WAL 的記錄是檢測和達成一致的最小單元,所以我們稱其為隱性的「區塊」,沒毛病。

在區塊鏈的世界裡,一筆交易需要被驗證。這裡的驗證有兩重含義:1) 身分驗證 —— 交易是由其發起人正確簽署的;2) 完整性驗證 —— 交易對狀態的變更是合法的。身分驗證好理解,你用自己錢包的私鑰簽署給我轉 1 個 ABT 的交易,系統會驗證你的確是你;完整性驗證則是指在 states db 裡,你的帳號下的確有超過 1 個 ABT 的 token,才能發起這個交易。在資料庫的世界裡,身分驗證直接赤果果用諸如 RBAC(Role Based Access Control)的存取控制系統解決了,而完整性驗證和區塊鏈類似。

接下來我們看看確定性(deterministic)。所謂確定性,就是在同一個狀態 Sn-1 下,大家拿著同樣一筆交易,不依賴任何第三方資訊獨立執行,執行的結果完全一致。這一點僅就純粹的從交易到 states db 的處理來說,區塊鏈和資料庫是完全一致的,大家都能保證確定性。然而,如果某個區塊鏈要支援交易中攜帶額外資訊,這些資訊觸發某些鏈上部署好的程式碼執行(比如說,smart contract),那麼,我們就得注意程式碼本身需要具備確定性。所謂確定性,無非是:

程式碼中不要使用不確定的亂數產生器 —— 比如使用電腦的時鐘作為種子產生亂數,這就是不確定的。因為交易在被執行的那一刻,我們無法保證所有參與者的時鐘是精確同步的。

  • 程式碼避免使用多執行緒。多執行緒引發的 race condition 具有不確定性。
  • 不要使用系統時鐘。不解釋。
  • 不要使用未初始化的記憶體。鬼知道上面是陳冠希還是諸子百家。
  • 不要使用浮點數 —— 這個很奇葩,因為不同的 CPU arch,編譯器,甚至不同 CPU 型號間,由於支援的浮點數指令集不同,會導致結果不同。
  • 不要使用程式語言中可能有隨機行為的資料結構。比如遍歷一個 map

基本上避免了這些,程式碼就具備了確定性,可以在區塊鏈上執行。那麼,為什麼資料庫中的預存程序可以允許沒有確定性的程式碼執行?比如,一個預存程序裡可以使用當前時間插入一筆記錄?我們如果再回歸本源,從「交易」的角度看待問題,可以發現,預存程序類似於 “off-chain” 執行的程式碼,它雖然植根於資料庫之中,但其實是「交易」的源頭,預存程序的執行產生真正的交易,也就是 WAL 記錄,然後同步給其他節點。所以預存程序可以 non-deterministic,因為其產生的 WAL 記錄已經是 deterministic 的 —— 新增一筆帶當前時間的記錄這件事情,在 master 執行時,已經將取「當前時間」這個動作完成並得到一個確定的值,攜帶於 WAL 之中。這跟區塊鏈的 smart contract 概念有本質的區別,這也是為什麼預存程序可以不必具備確定性,而 “on-chain” 執行的 smart contract 需要確定性。從這個角度來講,資料庫系統也是一個弱化的區塊鏈系統。

既然區塊鏈和資料庫儲存的對象都是資料,那麼,提過了資料的完整性和確定性,接下來就是資料的一致性。區塊鏈顯然是最終一致性的典範 —— 網路越大,參與的節點越多,區塊的擴散就越慢,任何時刻在不同的節點上讀取狀態就很大機率出現不一致的情況。然而,只要節點能同步到最新的區塊,整個網路的狀態是收斂的,最終大家能夠得到一個一致的狀態資料。其實,按照這個道理,所有使用 WAL、CQRS 思想的分散式系統,其資料的狀態都是最終一致的 —— 這似乎和我們對經典資料庫強一致性的印象不匹配。然而,如果我們把視角拉到資料庫內部,可以發現,強一致性只不過是最終一致性之上添加了一些條件,是個特例。如果我們假定一個區塊鏈滿足下面的條件:

1.任何節點收到新的區塊,必須在交易執行完成,寫入 states db 中之後,給礦工節點(其實就是當前的 master)發送確認

2.礦工節點在收到所有確認之後廣播給網路中所有節點這個區塊大家已經 commit 成功

3.在一個區塊沒有收到礦工節點的上述廣播之前(寫入完成之前),用戶端發送來的查詢進入佇列排隊(其實就是 read-write lock)

那麼在外界看來,它也是強一致性。當然,第三點有些過於苛刻,一般的資料庫實作都會採用 MVCC(Multiversion concurrency control),讓每個 client 看到當前狀態的一個 snapshot,因而存在一個很小的窗口,大家看到的資料是不一致的。如果較真,MVCC 不算強一致性,當然沒人會這麼認為。

透過上面的規則,資料庫可以透過犧牲一些效能來打造對外而言的強一致性。但有時候,為了一些崇(wo)高(chuo)的理想,資料庫系統也可以打破這些規則來號稱更高的效能。mongdb 可以在 cluster 的環境下,寫入操作不需要節點確認即可返回,於是有了美其名曰的、如薛丁格的貓一般的「弱一致性」。

一個區塊鏈網路理論上可以透過上述規則把自己營造成對外強一致性的感覺,但這實際上沒有可操作性。網路越大,延遲越大,所以,實際可操作的強一致性只能發生在節點數量很少,且節點都在同一個 data center 的環境下。從資料一致性的角度來說,資料庫也是區塊鏈在特殊場景下的一個特例。

最後說說效能。效能這事,和網路規模成反比。兩個主要原因:1) 節點越多,達成共識的難度就越大。2) 節點越多,「交易」在網路中傳播所需的時間就越長。那想要達到宇宙無敵的 TPS(Transactions Per Second)怎麼辦?其實不難 —— 既然資料庫是一個弱分散式環境下的特例,那麼,咱就把區塊鏈往資料庫的方向退化就好。PoW 說「王侯將相,寧有種乎」,讓全網參與鐵王座的競爭,PoS 就讓「一小部分人先富起來」,DPoS 再進一步,「讓領導先走」,也許不久的將來,有人會再憋出終極大招,全網就一個九五之尊,把資料庫裡能用的招數,replicaSet(總還得 HA 一下嘛),Sharding 等等統統用上,再使用兵法中不戰而屈人之兵之術:「今治水軍八十萬眾,方與將軍會獵於吳」……於是,可以名正言順地搶下效能的桂冠。

只不過……就像《非誠勿擾》裡車曉問葛優:那事兒,就那麼有意思嗎?

本頁涉及

術語

  • 區塊鏈

    透過密碼學連接記錄,並按共同的驗證與共識規則確定交易歷史的分散式帳本。