區塊鏈性能騰飛:基於DAG的並行交易執行引擎

FISCO BCOS是完全開源的聯盟區塊鏈底層技術平臺,由金融區塊鏈合作聯盟(深圳)(簡稱金鍊盟)成立開源工作組通力打造。開源工作組成員包括博彥科技、華爲、深證通、神州數碼、四方精創、騰訊、微衆銀行、亦筆科技和越秀金科等金鍊盟成員機構。

代碼倉庫:https://github.com/FISCO-BCOS

 

在區塊鏈世界中,交易是組成事務的基本單元。交易吞吐量很大程度上能限制或拓寬區塊鏈業務的適用場景,愈高的吞吐量,意味着區塊鏈能夠支持愈廣的適用範圍和愈大的用戶規模。當前,反映交易吞吐量的TPS(Transaction per Second,每秒交易數量)是評估性能的熱點指標。爲了提高TPS,業界提出了層出不窮的優化方案,殊途同歸,各種優化手段的最終聚焦點,均是儘可能提高交易的並行處理能力,降低交易全流程的處理時間。

在多核處理器架構已經成爲主流的今天,利用並行化技術充分挖掘CPU潛力是行之有效的方案。FISCO BCOS 2.0 中設計了一種基於DAG模型的並行交易執行器(PTE,Parallel Transaction Executor)。

PTE能充分發揮多核處理器優勢,使區塊中的交易能夠儘可能並行執行;同時對用戶提供簡單友好的編程接口,使用戶不必關心繁瑣的並行實現細節。基準測試程序的實驗結果表明:相較於傳統的串行交易執行方案,理想狀況下4核處理器上運行的PTE能夠實現約200%~300%的性能提升,且計算方面的提升跟核數成正比,核數越多性能越高。

PTE爲助力FISCO BCOS性能騰飛奠定了堅實基礎,本文將全面介紹PTE的設計思路及實現方案,主要包括以下內容:

  • 背景:傳統方案的性能瓶頸與DAG並行模型的介紹

  • 設計思路:PTE應用到FISCO BCOS中時遇到的問題以及解決方案

  • 架構設計:應用PTE後FISCO BCOS的架構及核心流程

  • 核心算法:介紹主要用到的數據結構與主要算法

  • 性能測評:分別給出PTE的性能與可擴展性測試結果

 

背 景

FISCO BCOS交易處理模塊可以被抽象爲一個基於交易的狀態機。在FISCO BCOS中,『狀態』即是指區塊鏈中所有賬戶的狀態,而『基於交易』即是指FISCO BCOS將交易作爲狀態遷移函數,並根據交易內容從舊的狀態更新爲新的狀態。FISCO BCOS從創世塊狀態開始,不斷收集網絡上發生的交易並打包爲區塊,並在所有參與共識的節點間執行區塊中的交易。當一個區塊內的交易在多個共識節點上執行完成且狀態一致,則我們稱在該塊上達成了共識,並將該區塊永久記錄在區塊鏈中。

從上述區塊鏈的打包→共識→存儲過程中可以看到,執行區塊中的所有交易是區塊上鍊的必經之路。傳統交易執行方案是:執行單元從待共識的區塊逐條讀出交易,執行完每一筆交易後,狀態機都會遷移至下一個狀態,直到所有交易都被串行執行完成,如下圖所示:

顯而易見,這種交易執行方式對性能並不友好。即使兩筆交易沒有交集,也只能按照先後順序依次執行。就交易間的關係而言,既然一維的『線』結構有這般痛點,那何不把目光投向二維的『圖』結構呢?

在實際應用中,根據每筆交易執行時需要使用的互斥資源(互斥意味着對資源的排他性使用,比如在上述轉賬問題互斥資源中,指的就是各個賬戶的餘額狀態), 我們可以組織出一張交易依賴關係圖,爲防止交易依賴關係在圖中成環,我們可以規定交易列表中牽涉到相同的互斥資源,且排序靠後的交易,必須等待靠前的交易完成後才被執行,由此得到的輸出便是一張反映交易依賴關係的有向無環圖,即交易DAG。

如下圖所示,左側的6筆轉賬交易可以組織爲右側的DAG形式:

在交易DAG中,入度爲0的交易是沒有任何依賴項、可以被立即投入運行的就緒交易。當就緒交易的數量大於1時,就緒交易可以被分散至多個CPU核心上並行執行。當一筆交易執行完,依賴於該交易的所有交易的入度減1,隨着交易不斷被執行,就緒交易也源源不斷被產生。在極限情況下,假如構造出的交易DAG層數爲1 (即所有交易均是沒有依賴項的獨立交易),則交易整體執行速度的提升倍數將直接取決於處理器的核心數量n,此時若n大於區塊內的交易數,則區塊內所有交易的執行時間與單筆交易執行的時間相同。

理論上擁有如此讓人無法拒絕的優美特性的交易DAG模型,該如何應用至FISCO BCOS中?

 

設計思路

 

要應用交易DAG模型,我們面臨的首要問題便是:

對於同一個區塊,如何確保所有節點執行完後能夠達到同一狀態,

這是一個關乎到區塊鏈能否正常出塊的關鍵問題。

FISCO BCOS採用驗證(state root, transaction root, receipt root)三元組是否相等的方式,來判斷狀態是否達成一致。transaction root是根據區塊內的所有交易算出的一個哈希值,只要所有共識節點處理的區塊數據相同,則transaction root必定相同,這點比較容易保證,因此重點在於如何保證交易執行後生成的state和receipt root也相同。

衆所周知,對於在不同CPU核心上並行執行的指令,指令間的執行順序無法提前預測,並行執行的交易也存在同樣情況。在傳統的交易執行方案中,每執行一筆交易,state root便發生一次變遷,同時將變遷後的state root寫入交易回執中,所有交易執行完後,最終的state root就代表了當前區塊鏈的狀態,同時再根據所有交易回執計算出一個receipt root。

可以看出,在傳統的執行方案中,state root扮演着一個類似全局共享變量的角色。當交易被並行且亂序執行後,傳統計算state root的方式顯然不再適用,這是因爲在不同的機器上,交易的執行順序一般不同,此時無法保證最後的state root能夠一致,同理,receipt root也無法保證一致。

在FISCO BCOS中,我們採用的解決方案是先執行交易,將每筆交易對狀態的改變歷史記錄下來,待所有交易執行完後,再根據這些歷史記錄再算出一個state root,同時,交易回執中的state root,也全部變爲所有交易執行完後最終的state root,由此就可以保證即使並行執行交易,最後共識節點仍然能夠達成一致。

 

搞定狀態問題後,下一個問題便是:

如何判斷兩筆交易之間是否存在依賴關係?

若兩筆交易本來無依賴關係但被判定爲有,則會導致不必要的性能損失;反之,如果這兩筆交易會改寫同一個賬戶的狀態卻被並行執行了,則該賬戶最後的狀態可能是不確定的。因此,依賴關係的判定是影響性能甚至能決定區塊鏈能否正常工作的重要問題。

在簡單的轉賬交易中,我們可以根據轉賬的發送者和接受者的地址,來判斷兩筆交易是否有依賴關係,比如如下3筆轉賬交易:A→B,C→D,D→E,可以很容易看出,交易D→E依賴於交易C→D的結果,但是交易A→B和其他兩筆交易沒有什麼關係,因此可以並行執行。

這種分析在只支持簡單轉賬的區塊鏈中是正確的,但是一旦放到圖靈完備、運行智能合約的區塊鏈中,則可能不那麼準確,因爲我們無法準確知道用戶編寫的轉賬合約中到底有什麼操作,可能出現的情況是:A->B的交易看似與C、D的賬戶狀態無關,但是在用戶的底層實現中,A是特殊賬戶,通過A賬戶每轉出每一筆錢必須要先從C賬戶中扣除一定手續費。在這種場景下,3筆交易均有關聯,則它們之間無法使用並行的方式執行,若還按照先前的依賴分析方法對交易進行劃分,則必定會掉坑。

我們能否做到根據用戶的合約內容自動推導出交易中實際存在哪些依賴項?答案是不太靠譜。我們很難去追蹤用戶合約中到底操作了什麼數據,即使做到也需要花費不小的成本,這和我們優化性能的目標相去甚遠。

綜上,我們決定在FISCO BCOS中,將交易依賴關係的指定工作交給更熟悉合約內容的開發者。具體地說,交易依賴的互斥資源可以由一組字符串表示,FISCO BCOS暴露接口給到開發者,開發者以字符串形式定義交易依賴的資源,告知鏈上執行器,執行器則會根據開發者指定的交易依賴項,自動將區塊中的所有交易排列爲交易DAG。比如在簡單轉賬合約中,開發者僅需指定每筆轉賬交易的依賴項是{發送者地址+接收者地址}。進一步地,如開發者在轉賬邏輯中引入了另一個第三方地址,那麼依賴項就需要定義爲{發送者地址+接受者地址+第三方地址}了。

這種方式實現起來較爲直觀簡單,也比較通用,適用於所有智能合約,但也相應增加了開發者肩上的責任,開發者在指定交易依賴項時必須十分小心,如果依賴項沒有寫正確,後果無法預料。指定依賴項的相關接口會在後續文章中給出使用教程,本文暫且假定所有談論到的交易依賴項都是明確無誤的。

 

解決完上面兩個比較重要的問題後,還剩下一些較爲細節的工程問題:

比如並行交易能否和非並行交易混合到一起執行?怎麼保證資源字符串的全局唯一性?

答案也不復雜,前者可通過將非並行交易作爲屏障(barrier)插入到交易DAG中——即我們認爲,它即依賴於它的所有前序交易,同時又被它的所有後序交易依賴——來實現;後者可以通過在開發者指定的交易依賴項中,加入標識合約的特殊標誌位解決。由於這些問題並不影響PTE的根本設計,本文暫不展開。

萬事俱備,帶着全新交易執行引擎PTE的FISCO BCOS已經呼之欲出。

 

架構設計

 

搭載PTE的FISCO BCOS架構圖:

整個架構的核心流程如下:

用戶通過SDK等客戶端將交易發送至節點,此處的交易既可以是可並行執行的交易,也可以是不能並行執行的交易。隨後交易在節點間同步,同時擁有打包權的節點調用打包器(Sealer),從交易池(Tx Pool)中取出一定量交易並將其打包成一個區塊。此後,區塊被髮送至共識單元(Consensus)準備進行節點間共識。

共識前需要執行區塊中的交易,此處便是PTE施展威力之處。從架構圖中可以看到,PTE首先按序讀取區塊中的交易,並輸入到DAG構造器(DAG Constructor)中,DAG構造器會根據每筆交易的依賴項,構造出一個包含所有交易的交易DAG,PTE隨後喚醒工作線程池,使用多個線程並行執行交易DAG。匯合器(Joiner)負責掛起主線程,直到工作線程池中所有線程將DAG執行完畢,此時Joiner負責根據各個交易對狀態的修改記錄計算state root及receipt root,並將執行結果返回至上層調用者。

在交易執行完成後,若各個節點狀態一致,則達成共識,區塊隨即寫入底層存儲(Storage),被永久記錄於區塊鏈上。

 

核心算法

 

1.交易DAG的數據結構

交易DAG的數據結構如下圖所示:

Vertex類爲最基礎裏的類型,在交易DAG中,每一個Vertex實例都表徵一筆交易。Vertex類包含:

  • inDegree:表示該頂點的入度

  • outEdges:用於存儲該節點的出邊信息,即所有出邊所連頂點的ID列表

DAG類用於對DAG的頂點與邊關係進行封裝,並提供操作DAG的接口,其包含:

  • vtxs:Vertex數組

  • topLevel:包含所有入度爲0的頂點的隊列,由於在執行過程中topLevel會動態變化且會被多個線程訪問,因此其需要一個能夠支持線程安全訪問的容器

  • void init(int32_t size)接口:根據傳入的size初始化一個包含相應數量頂點的DAG結構

  • addEdge(ID from, ID to)接口:用於在頂點from和頂點to之間建立邊關係,具體地說,將頂點to的ID加入頂點from的outEdges中

  • void generate()接口:當所有的邊關係錄入完畢後,調用該方法以初始化topLevel成員

  • ID waitPop()接口:從topLevel中獲取一個入度爲0的頂點ID

     

TxDAG類是DAG類更上一層的封裝,是DAG與交易之間建立聯繫的橋樑,其包含:

  • dag:持有的DAG類實例

  • exeCnt:已執行過的交易總數

  • totalTxs:交易總數

  • txs:區塊中的交易列表

 

2.交易DAG的構造流程

DAG構造器在構造交易DAG時,會首先將totalTxs成員的值設置爲區塊中的交易總數,並依據交易總數對dag對象進行初始化,dag會在vtxs中爲每筆交易生成一個位置關係一一對應的頂點實例。隨後,初始化一個空的資源映射表criticalFields,並按序逐個掃描每筆交易。

對於某筆交易tx,DAG構造器會在其解析出該交易的所有依賴項,對於每個依賴項均會去criticalFields中查詢,如果對於某個依賴項d,有前序交易也依賴於該依賴項,則在這兩筆交易間建邊,並更新criticalFields中d的映射項爲tx的ID。

交易DAG構造流程的僞代碼如下所示:

 

3.交易DAG的執行流程

PTE在被創建時,會根據配置生成一個用於執行交易DAG的工作線程池,線程池的大小默認等於CPU的邏輯核心數,此線程池的生命週期與PTE的生命週期相同。工作線程會不斷調用dag對象的waitPop方法以取出入度爲0的就緒交易並執行,執行後該交易的所有後序依賴任務的入度減1,若有交易的入度被減至0,則將該交易加入到topLevel中。循環上述過程,直到交易DAG執行完畢。

交易DAG執行流程的僞代碼如下所示:

 

性能測評

 

我們選用了2個基準測試程序,用以測試PTE給FISCO BCOS的性能帶來了怎樣的變化,它們分別是基於預編譯框架實現的轉賬合約和基於Solidity語言編寫的轉賬合約,兩份合約代碼的路徑分別爲:

FISCO-BCOS/libprecompiled/extension/DagTransferPrecompiled.cpp

web3sdk/src/test/resources/contract/ParallelOk.sol

我們使用一條單節點鏈進行測試,因爲我們主要關注PTE的交易處理性能,因此並不考慮網絡、存儲的延遲帶來的影響。

測試環境的基本硬件信息如下表所示:

 

1.性能測試

性能測試部分,我們主要測試PTE和串行交易執行方式(Serial)在各個測試程序下的交易處理能力。可以看到,相對於串行執行方式,PTE從左至右分別實現了2.91和2.69倍的加速比。無論是對於預編譯合約還是Solidity合約,PTE均有着不俗的性能表現。

 

2.可擴展性測試

可擴展性測試部分,我們主要測試PTE在不同CPU核心數下的交易處理能力,使用的基準測試程序是基於預編譯框架實現的轉賬合約。可以看到,隨着核數增加,PTE的交易吞吐量呈近似線性遞增。但是同時也能看到,隨着核數在增加,性能增長的幅度在放緩,這是因爲隨着核數增加線程間調度及同步的開銷也會增大。

 

#寫在最後#

從列表到DAG,PTE賦予了FISCO BCOS新的進化。更高的TPS會將FISCO BCOS帶至更加廣闊的舞臺。予以長袖,FISCO BCOS必能善舞!

 


 

我們鼓勵機構成員、開發者等社區夥伴參與開源共建事業,有你在一起,會更了不起。多樣參與方式:

1 進入微信社羣,隨時隨地與圈內最活躍、最頂尖的團隊暢聊技術話題(進羣請添加小助手微信,微信ID:fiscobcosfan);

2 訂閱我們的公衆號:“FISCO BCOS開源社區”,我們爲你準備了開發資料庫、最新FISCO BCOS動態、活動、大賽等信息;

3 來Meetup與開發團隊面對面交流,FISCO BCOS正在全國舉辦巡迴Meetup,深圳、北京、上海、成都……歡迎您公衆號在菜單欄【找活動】中找到附近的Meetup,前往結識技術大咖,暢聊硬核技術;

4 參與代碼貢獻,您可以在Github提交Issue進行問題交流,歡迎向FISCO BCOS提交Pull Request,包括但不限於文檔修改、修復發現的bug、提交新的功能特性。

代碼貢獻指引:

https://github.com/FISCO-BCOS/FISCO-BCOS/blob/master/docs/CONTRIBUTING_CN.md

 

本文首發於公衆號【FISCO BCOS開源社區】,如轉載請註明出處,原創不易,謝謝珍惜

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章