背景
因爲Libra學習rust,想用rust寫些東西,而剛好看區塊鏈共識機制的PBFT,就用原生Rust實現一個PBFT的分佈式系統,業餘中間斷斷續續寫,代碼比較零散。 中間也有些邏輯值得記錄, 主要在Rust的多線程,網絡通信的處理上。
總體架構
PBFT 節點實現
Node: 節點服務,提供PBFT的能力,
核心功能:處理client request, pre-prepare, prepare,commit,消息,最後reply給client,
其他:視圖轉換, check point創建,節點管理,消息簽名與驗籤。
主業務流程
這裏不一一講解每個階段,其中一階段代碼,有興趣可以clone下來看。
```cpp
pub fn receiveCommit(& mut self, msg:Bft_Commit_Message, mut executor:&mut Command_Executor) {
// check sign for node
let mut sign_msg = msg.clone();
let msg_digest = msg.get_msg_digest();
sign_msg.set_msg_digest(String::new());
let msg_node_id = sign_msg.get_node_id();
let pub_key_result = self.get_node_pub_key(&msg_node_id);
if pub_key_result.is_none() {
error!("can not found node id for {}", sign_msg.get_node_id());
return;
}
let signMsgStr = json::encode(&sign_msg).unwrap();
if !Bft_Signtor::check_sign(signMsgStr.as_str(), pub_key_result.unwrap().as_str(), msg_digest.as_str()) {
error!("commit msg sign not pass for {}", sign_msg.get_node_id());
return;
}
info!("check pass, process commit msg");
// check pass add to prepare cache;
if self.commit_cache.contains_key(&msg.get_sequence_num()) {
let list = self.commit_cache.get_mut(&msg.get_sequence_num()).unwrap();
list.push(msg.clone());
} else {
let mut commit_msg_list = Vec::new();
commit_msg_list.push(msg.clone());
self.commit_cache.insert(msg.get_sequence_num(), commit_msg_list);
}
self.doReplay(msg.get_sequence_num(), executor);
}
通信模塊
對P2P的網絡不熟悉,根據自己理解,實現了一個非阻塞式的網絡通信模塊,用於Node跟其他Node或者Client之間的通信。
非阻塞式通信
這裏實現的是一個非阻塞的通信服務, 可以用於跟其他節點或者Client來接發消息,基於Rust原生的TCP(TcpListener, TcpStream)實現。每個網絡連接TCPStream都分配一個線程作爲worker,處理連接進來的作爲reader worker,只用於讀,而外發到其他則創建一個TCPStream用於寫,並起一個write worker。
通信模塊只暴露startListener, sendmssage方法, 都支持非阻塞。 非阻塞通過Rust的channel來實現。
Read worker 線程一直嘗試讀取TCPStream消息,當讀取消息後,完成消息協議格式處理,把消息扔到channel。
Write worker 線程一直監聽channel是否有消息進來,有消息則把消息轉成String後,通過TCPStream發送出去。
message parser: 作爲一個獨立線程,因爲rust的channel支持一對多,等待所有worker讀的消息,根據消息類型,調對應外部PBFT Node的方法處理消息。
同步消息通信
當然通信模塊也支持同步發送消息,阻塞等待對方返回, 但同步同樣基於上訴的異步過程,但在消息協議中會註明是同步,並分配一個唯一id, 爲同步消息專門開通一個channel,當writer發送完消息,就通過channel的receiver阻塞,等待reply,而message parser收到消息,發現是reply並且是同步的,直接扔到同步的channel中。
通信消息協議
因爲基於TCP實現的網絡通信,這裏定義了一個簡單協議,通信模塊的worker負責協議的解析和文本序列化。
協議格式:
command version sync id length
body
body 爲文本, 格式內容由上層業務決定,這裏的PBFT格式爲消息的Json文本。
Command 預留reply,作爲同步消息的同步響應消息, 其他上層業務定義的業務命令,這裏是PBFT的receive, pre-prepare, prepare, commit
業務執行模塊
這裏的業務執行模塊,只是做了簡單的key-value緩存,支持 put key=value, get key, delete key 命令,業務器解析命令, 通過Rust原生Map緩存鍵值對。支持持久化,在checkpoint階段,把未持久化的值放入文本。 另外實時記錄執行的命令,理論支持上checkpoint恢復後再通過實時命令恢復緩存。
就是基於PBFT實現了一個分佈式的緩存系統, 而command executor爲接口,可以切換其他的實現把這分佈系統實現其他能力。
Message parse
消息解析模塊,根據command來解析PBFT消息格式,做業務路由,創建一個新線程處理消息。 這裏涉及Rust的多線程編程。
```cpp
//code
let node_mutex: Arc<Mutex<Btf_Node>> = Arc::new(Mutex::new(node));
let mutex = Arc::clone(&node_mutex);
thread::Builder::new().name(i.to_string()).spawn(move|| {
let mut node = mutex.lock().unwrap();
let node_msg = node_msg_result.unwrap();
let result = node.doPrepare(node_msg, &mut executor);
if result.is_some() {
let (view_num, sequece_num) = result.unwrap();
}
……
});
Rust多線程編程:
Rust都是通過thread創建新的線程,而rust沒有自己的runtime線程模型,使用的系統的的線程, 線程間有幾種通信或併發控制方式。
1、 mutex用於共享一個變量,使用變量前先獲得鎖,因爲rust有ownership的原因,每個變量只能有一個owner,而多個線程持有一個變量,需要通過Arc實現,通過Arc讓多線程同時持有mutex。Metux再由 lock then use的機制,併發操作共享變量。
2、 線程間通信,通過channel來實現,有點類似q + wait-notify的作用,做系統間的協同操作,上文的通信模塊就多處使用channel。 一個channel能有多個sender但只能有一個receiver,receiver可以阻塞等待消息,也可以非阻塞。
這裏創建讀網絡線程,讀線程讀到網絡數據後,通過sender通知業務線程。
```rust
let (msg_sender, msg_receiver) = channel();
thread::Builder::new().name("bft_node_listener".to_string()).spawn(move|| {
let mut thread_index = 0;
for stream in listener.incoming() {
info!("receive one connection");
let mut stream = stream.unwrap();
let mut msg_sender_reader = Sender::clone(&msg_sender_sub);
Default_TCP_Communication::create_new_reader(msg_sender_reader, stream);
}
});
// 阻塞式獲取讀線程通過channel投遞的消息對象, 轉交給message parser.
let msg_result = msg_receiver.recv();
if msg_result.is_ok() {
let msg_box:Box<BftCommunicationMsg> = msg_result.unwrap();
……
3、 Send ,Sync接口,對多線程編程,這兩個是特殊接口,兩個接口都不需要手工實現,只要struct的成員都實現了, 對象就默認實現,而基礎類型都實現了這兩個接口。
Send: 實現了該接口的,可以在線程之間傳送變量的ownership。
Sync: 實現了該接口, 可以在線程之間出送變量的引用。
總結
至此說明鏈原生Rust寫PBFT過程中重點處理的邏輯,按PBFT論文實現消息的多階段狀態機處理,checkpoint處理,這裏沒具體提到PBFT邏輯,具體可以參考之前多文章“區塊鏈四” 另外重點提到原生多通信模塊,具備簡單異步同步通信能力; 另外Rust多線程特性也在此次實現中多次用到。
往期:
區塊鏈四:共識機制——PBFT算法深入講解
區塊鏈三:深入解析比特幣交易原理
區塊鏈二:比特幣的區塊數據結構
區塊鏈一 :區塊鏈應用介紹和展望