真的好久没有写博客了,正好最近在研究PBFT,那就从PBFT开始写起吧!
先奉上大佬@byron1st写的PBFT代码:https://github.com/bigpicturelabs/simple_pbft
我会带着大家鹿一遍源代码!因为看到网上好多的博客都是互相抄袭,对大家一点帮助没有
整个代码逻辑都是围绕这张图来的!
PBFT算法基础理论部分:https://www.jianshu.com/p/cf1010f39b84
func main() {
nodeID := os.Args[1] // 这边就是传进来的公司的名称
server := network.NewServer(nodeID)
server.Start()
}
这是整个PBFT的启动主函数,可以看到会获取用户输入的数值作为nodeID
,然后调用network包中的NewServer函数定义一个服务(可以理解成一个节点的client窗口),然后开始启动server.Start()
这里会给完整的流程图,等我写完所有的部分会重新画的
func NewServer(nodeID string) *Server {
// 根据传进来的NodeID新建了一个节点,节点的默认视图是1000000,并且该节点启动了三个协程:dispatchMsg、alarmToDispatcher、resolveMsg
node := NewNode(nodeID)
// 启动服务
server := &Server{node.NodeTable[nodeID], node}
// 设置路由
server.setRoute()
return server
}
我们可以看到NewServer函数主要干了三件事:
我们先来看第一件事,就是创建一个node,进入node类,看一下node到底在干嘛!
func NewNode(nodeID string) *Node {
const viewID = 10000000000 // temporary.
node := &Node{
// Hard-coded for test.
NodeID: nodeID,
NodeTable: map[string]string{
"Apple": "localhost:1111",
"MS": "localhost:1112",
"Google": "localhost:1113",
"IBM": "localhost:1114",
},
View: &View{
ID: viewID,
Primary: "Apple", // 主节点是Apple公司
},
// Consensus-related struct
CurrentState: nil,
CommittedMsgs: make([]*consensus.RequestMsg, 0), // 被提交的信息
MsgBuffer: &MsgBuffer{
ReqMsgs: make([]*consensus.RequestMsg, 0),
PrePrepareMsgs: make([]*consensus.PrePrepareMsg, 0),
PrepareMsgs: make([]*consensus.VoteMsg, 0),
CommitMsgs: make([]*consensus.VoteMsg, 0),
},
// Channels
MsgEntrance: make(chan interface{}), // 无缓冲的信息接收通道
MsgDelivery: make(chan interface{}), // 无缓冲的信息发送通道
Alarm: make(chan bool),
}
// 启动消息调度器
go node.dispatchMsg()
// Start alarm trigger
go node.alarmToDispatcher()
// 开始信息表决
go node.resolveMsg()
return node
}
这边的NewNode其实就是新建了一个节点,并且定义了node结构体中的一些属性信息,node结构体如下:
// 节点
type Node struct {
NodeID string
NodeTable map[string]string // key=nodeID, value=url
View *View
CurrentState *consensus.State
CommittedMsgs []*consensus.RequestMsg // kinda block.
MsgBuffer *MsgBuffer
MsgEntrance chan interface{}
MsgDelivery chan interface{}
Alarm chan bool
}
我们一步步剖析NewNode
const viewID = 10000000000
属性 | 值 | 说明 |
---|---|---|
NodeID | nodeID | 节点ID |
NodeTable | map[string]string | 节点索引表 |
View | &View | 设置视图编号和主节点 |
CurrentState | nil | 默认当前节点的状态为nil |
CommittedMsgs | make([]*consensus.RequestMsg, 0) | 被提交的信息 |
MsgBuffer | &MsgBuffer | 四种消息类型缓冲列表 |
MsgEntrance | make(chan interface{}) | 无缓冲的信息接收通道 |
MsgDelivery | make(chan interface{}) | 无缓冲的信息发送通道 |
Alarm | make(chan bool) | 警告通道 |
goroutine
func (node *Node) dispatchMsg() {
for {
select {
case msg := <-node.MsgEntrance: // 如果MsgEntrance通道有消息传送过来,拿到msg
err := node.routeMsg(msg) // 进行routeMsg
if err != nil {
fmt.Println(err)
// TODO: send err to ErrorChannel
}
case <-node.Alarm:
err := node.routeMsgWhenAlarmed()
if err != nil {
fmt.Println(err)
// TODO: send err to ErrorChannel
}
}
}
}
这里需要你对
for select
多路复用有所了解!
我们可以从dispatchMsg
的代码中看到,只要MsgEnrance
通道中有值,就会传递给一个中间变量msg
,然后进行消息路由转发routeMsg
。
func (node *Node) routeMsg(msg interface{}) []error {
switch msg.(type) {
case *consensus.RequestMsg:
if node.CurrentState == nil {
// Copy buffered messages first.
msgs := make([]*consensus.RequestMsg, len(node.MsgBuffer.ReqMsgs))
copy(msgs, node.MsgBuffer.ReqMsgs)
// Append a newly arrived message.
msgs = append(msgs, msg.(*consensus.RequestMsg))
// Empty the buffer.
node.MsgBuffer.ReqMsgs = make([]*consensus.RequestMsg, 0)
// Send messages.
node.MsgDelivery <- msgs
} else {
node.MsgBuffer.ReqMsgs = append(node.MsgBuffer.ReqMsgs, msg.(*consensus.RequestMsg))
}
... 其他类型的信息数据
return nil
}
我们刚开始先以RequestMsg为例,因为其他类型的消息数据的执行逻辑与RequestMsg几乎是一致的!这是节点会面临两种选择:
resolveMsg
会立马接受这个通道中的信息)func (node *Node) resolveMsg() {
for {
// 从调度器中获取缓存信息
msgs := <-node.MsgDelivery
switch msgs.(type) {
case []*consensus.RequestMsg:
// 节点表决决策信息
errs := node.resolveRequestMsg(msgs.([]*consensus.RequestMsg))
if len(errs) != 0 {
for _, err := range errs {
fmt.Println(err)
}
// TODO: send err to ErrorChannel
}
... 替他类型的信息数据
}
}
}
这边我也先只以RequestMsg进行讲解!我们可以清楚的看到node.MsgDelivery这边在等着调度器dispatchMsg那边传递消息过来,因为两个缓冲通道都是无缓冲的,没有消息会一直阻塞在这边!
下面就是执行resolveRequestMsg
函数了:
// 节点表决请求阶段的信息
func (node *Node) resolveRequestMsg(msgs []*consensus.RequestMsg) []error {
errs := make([]error, 0)
// 表决信息
for _, reqMsg := range msgs {
err := node.GetReq(reqMsg)
if err != nil {
errs = append(errs, err)
}
}
if len(errs) != 0 {
return errs
}
return nil
}
在resolveRequestMsg代码块中,我们可以看到主要的执行逻辑就是调用了GetReq
函数
// 主节点开始全局的共识
func (node *Node) GetReq(reqMsg *consensus.RequestMsg) error {
LogMsg(reqMsg)
// Create a new state for the new consensus.
err := node.createStateForNewConsensus()
if err != nil {
return err
}
// 开始共识程序
prePrepareMsg, err := node.CurrentState.StartConsensus(reqMsg)
if err != nil {
return err
}
LogStage(fmt.Sprintf("Consensus Process (ViewID:%d)", node.CurrentState.ViewID), false)
// 发现getPrePrepare信息
// 这边主节点开始向其他节点发送预准备消息了
if prePrepareMsg != nil {
node.Broadcast(prePrepareMsg, "/preprepare")
LogStage("Pre-prepare", true)
}
return nil
}
createStateForNewConsensus
StartConsensus
prePrepareMsg
阶段的信息!这边先去看第3章共识过程
func (server *Server) setRoute() {
http.HandleFunc("/req", server.getReq)
http.HandleFunc("/preprepare", server.getPrePrepare)
http.HandleFunc("/prepare", server.getPrepare)
http.HandleFunc("/commit", server.getCommit)
http.HandleFunc("/reply", server.getReply)
}
这张流程图是为了方便大家理解,等你梳理完整个流程,再回过头来重新看我画的这个流程图,思路应该会更清晰一点!
一只安慕嘻是我的B站账号,大家也可以关注 o(´^`)o
func (node *Node) createStateForNewConsensus() error {
// Check if there is an ongoing consensus process.
if node.CurrentState != nil {
return errors.New("another consensus is ongoing")
}
// Get the last sequence ID
var lastSequenceID int64
if len(node.CommittedMsgs) == 0 {
lastSequenceID = -1
} else {
lastSequenceID = node.CommittedMsgs[len(node.CommittedMsgs) - 1].SequenceID
}
// Create a new state for this new consensus process in the Primary
node.CurrentState = consensus.CreateState(node.View.ID, lastSequenceID)
LogStage("Create the replica status", true)
return nil
}
lastSequenceID
设置为-1,否则我们取出上一个序列号CreateState
,主要是初始化State之后返回func CreateState(viewID int64, lastSequenceID int64) *State {
return &State{
ViewID: viewID,
MsgLogs: &MsgLogs{
ReqMsg:nil,
PrepareMsgs:make(map[string]*VoteMsg),
CommitMsgs:make(map[string]*VoteMsg),
},
LastSequenceID: lastSequenceID,
CurrentStage: Idle,
}
}
// 主节点开始共识
func (state *State) StartConsensus(request *RequestMsg) (*PrePrepareMsg, error) {
// 消息的序号为sequenceID,也就是当前时间
sequenceID := time.Now().UnixNano()
// Find the unique and largest number for the sequence ID
if state.LastSequenceID != -1 {
for state.LastSequenceID >= sequenceID {
sequenceID += 1
}
}
// Assign a new sequence ID to the request message object.
request.SequenceID = sequenceID
// Save ReqMsgs to its logs.
state.MsgLogs.ReqMsg = request
// 得到客户端请求消息requestMsg的摘要
digest, err := digest(request)
if err != nil {
fmt.Println(err)
return nil, err
}
// 将当前阶段转换到PrePrepared阶段
state.CurrentStage = PrePrepared
// 这边其实就是主节点向其他节点发送消息的格式:视图ID,请求的序列号,请求信息和请求信息的摘要
return &PrePrepareMsg{
ViewID: state.ViewID,
SequenceID: sequenceID,
Digest: digest,
RequestMsg: request,
}, nil
}
SequenceID
的更新,获取请求信息的摘要digest(request)
,将当前状态转化为PrePrepared
// 节点广播函数
func (node *Node) Broadcast(msg interface{}, path string) map[string]error {
errorMap := make(map[string]error)
for nodeID, url := range node.NodeTable {
// 因为不需要向自己进行广播了,所以就直接跳过
if nodeID == node.NodeID {
continue
}
// 将msg信息编码成json格式
jsonMsg, err := json.Marshal(msg)
if err != nil {
errorMap[nodeID] = err
continue
}
// 将json格式传送给其他的节点
send(url + path, jsonMsg) // url localhost:1111 path:/prepare等等
// send函数:http.Post("http://"+url, "application/json", buff)
}
if len(errorMap) == 0 {
return nil
} else {
return errorMap
}
}
node.NodeTable
json.Marshal(msg)
,然后发送给其他节点send(url + path, jsonMsg)
func send(url string, msg []byte) {
buff := bytes.NewBuffer(msg)
_, _ = http.Post("http://"+url, "application/json", buff)
}
顺便帮助大家回忆一下,在resolveMsg
相应的goroutine中会判断传进来的
信息类型,前面一直说的是request类型,现在我们开始鹿PrePrepare的代码
case []*consensus.PrePrepareMsg:
errs := node.resolvePrePrepareMsg(msgs.([]*consensus.PrePrepareMsg))
if len(errs) != 0 {
for _, err := range errs {
fmt.Println(err)
}
// TODO: send err to ErrorChannel
}
调用resolvePrePrepareMsg
函数,开启prePrepareMsg的共识
func (node *Node) resolvePrePrepareMsg(msgs []*consensus.PrePrepareMsg) []error {
errs := make([]error, 0)
// Resolve messages
for _, prePrepareMsg := range msgs {
err := node.GetPrePrepare(prePrepareMsg)
if err != nil {
errs = append(errs, err)
}
}
if len(errs) != 0 {
return errs
}
return nil
}
对于每一个prePrepareMsg 信息会调用GetPrePrepare
函数,下面正式开始共识!
// 到了预准备阶段,所有节点开始参与共识
func (node *Node) GetPrePrepare(prePrepareMsg *consensus.PrePrepareMsg) error {
LogMsg(prePrepareMsg)
// Create a new state for the new consensus.
err := node.createStateForNewConsensus()
if err != nil {
return err
}
prePareMsg, err := node.CurrentState.PrePrepare(prePrepareMsg)
if err != nil {
return err
}
if prePareMsg != nil {
// Attach node ID to the message
prePareMsg.NodeID = node.NodeID
LogStage("Pre-prepare", true)
node.Broadcast(prePareMsg, "/prepare")
LogStage("Prepare", false)
}
return nil
}
这边跟request阶段是一样的,所以就省略啦!
// 预准备阶段的共识
func (state *State) PrePrepare(prePrepareMsg *PrePrepareMsg) (*VoteMsg, error) {
// Get ReqMsgs and save it to its logs like the primary.
state.MsgLogs.ReqMsg = prePrepareMsg.RequestMsg
// 验证视图ID、消息序列号和信息摘要是否正确
if !state.verifyMsg(prePrepareMsg.ViewID, prePrepareMsg.SequenceID, prePrepareMsg.Digest) {
return nil, errors.New("当前节点预准备阶段消息是错误的")
}
// Change the stage to pre-prepared.
state.CurrentStage = PrePrepared
return &VoteMsg{
ViewID: state.ViewID,
SequenceID: prePrepareMsg.SequenceID,
Digest: prePrepareMsg.Digest,
MsgType: PrepareMsg,
}, nil
}
PrePrepared
// 验证
func (state *State) verifyMsg(viewID int64, sequenceID int64, digestGot string) bool {
// Wrong view. That is, wrong configurations of peers to start the consensus.
if state.ViewID != viewID {
return false
}
// Check if the Primary sent fault sequence number. => Faulty primary.
// TODO: adopt upper/lower bound check.
if state.LastSequenceID != -1 {
if state.LastSequenceID >= sequenceID {
return false
}
}
digest, err := digest(state.MsgLogs.ReqMsg)
if err != nil {
fmt.Println(err)
return false
}
// Check digest.
if digestGot != digest {
return false
}
return true
}
广播和request阶段也是一样的,所以也忽略啦!