拜占庭PBFT简单实现

PBFT(拜占庭容错)

基于拜占庭将军问题,一致性的确保主要分为这三个阶段:预准备(pre-prepare)、准备(prepare)和确认(commit)。流程如下图所示:

image

其中C为发送请求端,0123为服务端,3为宕机的服务端,具体步骤如下:

  1. Request:请求端C发送请求到任意一节点,这里是0
  2. Pre-Prepare:服务端0收到C的请求后进行广播,扩散至123
  3. Prepare:123,收到后记录并再次广播,1->023,2->013,3因为宕机无法广播
  4. Commit:0123节点在Prepare阶段,若收到超过一定数量的相同请求,则进入Commit阶段,广播Commit请求
    5.Reply:0123节点在Commit阶段,若收到超过一定数量的相同请求,则对C进行反馈

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package main

import (
"os"
"fmt"
"net/http"
"io"
)

//声明节点信息,代表各个小国家
type nodeInfo struct {
//标示
id string
//准备访问的方法
path string
//服务器做出的相应
writer http.ResponseWriter

}

//存放四个国家的地址
var nodeTable = make(map[string]string)

//拜占庭在Fabric中的使用
func main() {

//获取执行的参数
userId :=os.Args[1]//获取执行的第一个参数
fmt.Println(userId)

//./main Apple

//创建四个国家的地址
nodeTable = map[string]string {
"Apple":"localhost:1111",
"MS":"localhost:1112",
"Google":"localhost:1113",
"IBM":"localhost:1114",
}

node:=nodeInfo{userId,nodeTable[userId],nil}
fmt.Println(node)

//http协议的回调函数
//http://localhost:1111/req?warTime=8888
http.HandleFunc("/req",node.request)
http.HandleFunc("/prePrepare",node.prePrepare)
http.HandleFunc("/prepare",node.prepare)
http.HandleFunc("/commit",node.commit)

//启动服务器
if err:=http.ListenAndServe(node.path,nil);err!=nil {
fmt.Print(err)
}



}

//此函数是http访问时候req命令的请求回调函数
func (node *nodeInfo)request(writer http.ResponseWriter,request *http.Request){
//设置允许解析参数
request.ParseForm()
//如果有参数值,则继续处理
if (len(request.Form["warTime"])>0){
node.writer = writer
//激活主节点后,广播给其他节点,通过Apple向其他节点做广播
node.broadcast(request.Form["warTime"][0],"/prePrepare")

}


}


//由主节点向其他节点做广播
func (node *nodeInfo)broadcast(msg string ,path string ){
//遍历所有的国家
for nodeId,url:=range nodeTable {

if nodeId == node.id {
continue
}
//调用Get请求
//http.Get("http://localhost:1112/prePrepare?warTime=8888&nodeId=Apple")
http.Get("http://"+url+path+"?warTime="+msg+"&nodeId="+node.id)
}

}

func (node *nodeInfo)prePrepare(writer http.ResponseWriter,request *http.Request) {
request.ParseForm()
//fmt.Println("hello world")
//在做分发
if(len(request.Form["warTime"])>0){
//分发给其他三个人
node.broadcast(request.Form["warTime"][0],"/prepare")
}

}

func (node *nodeInfo)prepare(writer http.ResponseWriter,request *http.Request){

request.ParseForm()
//调用验证
if len(request.Form["warTime"])>0{
fmt.Println(request.Form["warTime"][0])
}
if len(request.Form["nodeId"])>0 {
fmt.Println(request.Form["nodeId"][0])
}

node.authentication(request)
}


var authenticationsuccess = true
var authenticationMap = make(map[string]string)
//获得除了本节点外的其他节点数据
func (node *nodeInfo)authentication(request *http.Request) {

//接收参数
request.ParseForm()

if authenticationsuccess!=false {
if len(request.Form["nodeId"])>0 {
authenticationMap[request.Form["nodeId"][0]]="ok"
}
}

if len(authenticationMap)>len(nodeTable)/3 {
//则拜占庭原理实现,通过commit反馈给浏览器
node.broadcast(request.Form["warTime"][0],"/commit")

}
}


func (node *nodeInfo)commit(writer http.ResponseWriter,request *http.Request){

//给浏览器反馈相应
io.WriteString(node.writer,"ok")

}

如何运行:开启4个终端,eg:go run main.go Apple …
然后在浏览器输入:http://localhost:1112/req?warTime=1234

坚持原创技术分享,您的支持将鼓励我继续创作!