1、最长回文子串
给定一个字符串 s
,找到 s
中最长的回文子串。你可以假设 s
的最大长度为 1000。
示例 1:
输入: "babad" 输出: "bab" 注意: "aba" 也是一个有效答案。
示例 2:
输入: "cbbd" 输出: "bb"
class Solution {
public String longestPalindrome(String s) {
}
}
2、分数排名
编写一个 SQL 查询来实现分数排名。
如果两个分数相同,则两个分数排名(Rank)相同。请注意,平分后的下一个名次应该是下一个连续的整数值。换句话说,名次之间不应该有“间隔”。
+----+-------+
| Id | Score |
+----+-------+
| 1 | 3.50 |
| 2 | 3.65 |
| 3 | 4.00 |
| 4 | 3.85 |
| 5 | 4.00 |
| 6 | 3.65 |
+----+-------+
例如,根据上述给定的 Scores 表,你的查询应该返回(按分数从高到低排列):
+-------+------+
| Score | Rank |
+-------+------+
| 4.00 | 1 |
| 4.00 | 1 |
| 3.85 | 2 |
| 3.65 | 3 |
| 3.65 | 3 |
| 3.50 | 4 |
+-------+------+
重要提示:对于 MySQL 解决方案,如果要转义用作列名的保留字,可以在关键字之前和之后使用撇号。例如 `Rank`
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/rank-scores
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
两种解法,排序后添加序号,或自连接
解法1,自连接
SELECT
s1.Score,
COUNT(DISTINCT s2.Score) + 1 Rank
FROM
Scores s1
LEFT JOIN Scores s2 ON s2.Score > s1.Score
GROUP BY
s1.id,
s1.Score
ORDER BY
s1.Score DESC
解法2,排序后添加序号
SELECT
t.Score,
CAST(t.Rank AS SIGNED) Rank
FROM
(
SELECT
s.Score,
IF(s.Score = @pre_s, @rank := @rank, @rank := @rank + 1) Rank,
@pre_s := s.Score pre_s
FROM
Scores s,
(SELECT @pre_s := NULL, @rank := 0) t
ORDER BY
Score DESC
) t
-------------------------------------------------
dpos
package main import ( "crypto/rand" "crypto/sha256" "encoding/hex" "fmt" "log" "math/big" "sort" "strconv" "time" ) const ( voteNodeNum = 100 superNodeNum = 10 mineSuperNodeNum = 3 ) type block struct { //上一个块的hash prehash string //本块hash hash string //时间戳 timestamp string //区块内容 data string //区块高度 height int //挖出本块的节点地址 address string } //用于存储区块链 var blockchain []block //普通节点 type node struct { //代币数量 votes int //节点地址 address string } //竞选节点 type superNode struct { node } //投票节点池 var voteNodesPool []node //竞选节点池 var starNodesPool []superNode //存放可以挖矿的超级节点池 var superStarNodesPool []superNode //生成新的区块 func generateNewBlock(oldBlock block, data string, address string) block { newBlock := block{} newBlock.prehash = oldBlock.hash newBlock.data = data newBlock.timestamp = time.Now().Format("2006-01-02 15:04:05") newBlock.height = oldBlock.height + 1 newBlock.address = address newBlock.getHash() return newBlock } //对自身进行散列 func (b *block) getHash() { sumString := b.prehash + b.timestamp + b.data + b.address + strconv.Itoa(b.height) hash := sha256.Sum256([]byte(sumString)) b.hash = hex.EncodeToString(hash[:]) } //投票 func voting() { for _, v := range voteNodesPool { rInt, err := rand.Int(rand.Reader, big.NewInt(superNodeNum+1)) if err != nil { log.Panic(err) } starNodesPool[int(rInt.Int64())].votes += v.votes } } //对挖矿节点进行排序 func sortMineNodes() { sort.Slice(starNodesPool, func(i, j int) bool { return starNodesPool[i].votes > starNodesPool[j].votes }) superStarNodesPool = starNodesPool[:mineSuperNodeNum] } //初始化 func init() { //初始化投票节点 for i := 0; i <= voteNodeNum; i++ { rInt, err := rand.Int(rand.Reader, big.NewInt(10000)) if err != nil { log.Panic(err) } voteNodesPool = append(voteNodesPool, node{int(rInt.Int64()), "投票节点" + strconv.Itoa(i)}) } //初始化竞选节点 for i := 0; i <= superNodeNum; i++ { starNodesPool = append(starNodesPool, superNode{node{0, "超级节点" + strconv.Itoa(i)}}) } } func main() { fmt.Println("初始化", voteNodeNum, "个投票节点...") fmt.Println(voteNodesPool) fmt.Println("当前存在的", superNodeNum, "个竞选节点") fmt.Println(starNodesPool) fmt.Println("投票节点们开始进行投票...") voting() fmt.Println("结束投票,查看竞选节点们获得票数...") fmt.Println(starNodesPool) fmt.Println("对竞选节点按获得票数排序,前", mineSuperNodeNum, "名,当选超级节点") sortMineNodes() fmt.Println(superStarNodesPool) fmt.Println("开始挖矿...") genesisBlock := block{"0000000000000000000000000000000000000000000000000000000000000000", "", time.Now().Format("2006-01-02 15:04:05"), "我是创世区块", 1, "000000000"} genesisBlock.getHash() blockchain = append(blockchain, genesisBlock) fmt.Println(blockchain[0]) i, j := 0, 0 for { time.Sleep(time.Second) newBlock := generateNewBlock(blockchain[i], "我是区块内容", superStarNodesPool[j].address) blockchain = append(blockchain, newBlock) fmt.Println(blockchain[i+1]) i++ j++ j = j % len(superStarNodesPool) //超级节点轮循获得出块权 } }
>股份授权证明机制是POS的一个变种,简单来说就是你手里有选票(币就相当于选票)。这时一些正在竞选超级节点的大节点们说把票都投给我把,等我当选了超级节点,我吃肉你喝汤,岂不美哉?然后你就信了,把票投给了竞选节点,这些节点竞选成功成为超级节点后会轮循的获得出块权。旷工费、通胀放出的代币也就都到了他们手里了。比较中心化的一种共识机制,但是TPS很高。 <br> 区块结构: ```go type block struct { //上一个块的hash prehash string //本块hash hash string //时间戳 timestamp string //区块内容 data string //区块高度 height int //挖出本块的节点地址 address string } ``` ```go //用于存储区块链 var blockchain []block //普通节点 type node struct{ //代币数量 votes int //节点地址 address string } //竞选节点 type superNode struct { node } //投票节点池 var voteNodesPool []node //竞选节点池 var starNodesPool []superNode //存放可以挖矿的超级节点池 var superStarNodesPool []superNode ``` 初始化: 生成投票节点池并随机赋予代币数量,同时为竞选节点池生成竞选节点 ```go //初始化 func init() { //初始化投票节点 for i:=0;i<=voteNodeNum;i++ { rInt,err:=rand.Int(rand.Reader,big.NewInt(10000)) if err != nil { log.Panic(err) } voteNodesPool = append(voteNodesPool,node{int(rInt.Int64()),"投票节点"+strconv.Itoa(i)}) } //初始化超级节点 for i:=0;i<=superNodeNum;i++ { starNodesPool = append(starNodesPool,superNode{node{0,"超级节点"+strconv.Itoa(i)}}) } } ``` 模拟普通节点投票(随机的对竞选节点投票) ```go //投票 func voting() { for _, v := range voteNodesPool { rInt, err := rand.Int(rand.Reader, big.NewInt(superNodeNum+1)) if err != nil { log.Panic(err) } starNodesPool[int(rInt.Int64())].votes += v.votes } } ``` 对竞选节点根据得票数排序,前几名成为超级节点 ```go //对挖矿节点进行排序 func sortMineNodes() { sort.Slice(starNodesPool, func(i, j int) bool { return starNodesPool[i].votes > starNodesPool[j].votes }) superStarNodesPool = starNodesPool[:mineSuperNodeNum] } ``` 主函数 ```go func main() { fmt.Println("初始化", voteNodeNum, "个投票节点...") fmt.Println(voteNodesPool) fmt.Println("当前存在的", superNodeNum, "个竞选节点") fmt.Println(starNodesPool) fmt.Println("投票节点们开始进行投票...") voting() fmt.Println("结束投票,查看竞选节点们获得票数...") fmt.Println(starNodesPool) fmt.Println("对竞选节点按获得票数排序,前", mineSuperNodeNum, "名,当选超级节点") sortMineNodes() fmt.Println(superStarNodesPool) fmt.Println("开始挖矿...") genesisBlock := block{"0000000000000000000000000000000000000000000000000000000000000000", "", time.Now().Format("2006-01-02 15:04:05"), "我是创世区块", 1, "000000000"} genesisBlock.getHash() blockchain = append(blockchain, genesisBlock) fmt.Println(blockchain[0]) i, j := 0, 0 for { time.Sleep(time.Second) newBlock := generateNewBlock(blockchain[i], "我是区块内容", superStarNodesPool[j].address) blockchain = append(blockchain, newBlock) fmt.Println(blockchain[i+1]) i++ j++ j = j % len(superStarNodesPool) } } ``` 运行结果: ![在这里插入图片描述](https://img-blog.csdnimg.cn/20191211162452121.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM1OTExMTg0,size_16,color_FFFFFF,t_70)
pbft
2.1、client.go
package main import ( "bufio" "crypto/rand" "encoding/json" "fmt" "log" "math/big" "os" "strings" "time" ) func clientSendMessageAndListen() { //开启客户端的本地监听(主要用来接收节点的reply信息) go clientTcpListen() fmt.Printf("客户端开启监听,地址:%s ", clientAddr) fmt.Println(" ---------------------------------------------------------------------------------") fmt.Println("| 已进入PBFT测试Demo客户端,请启动全部节点后再发送消息! :) |") fmt.Println(" ---------------------------------------------------------------------------------") fmt.Println("请在下方输入要存入节点的信息:") //首先通过命令行获取用户输入 stdReader := bufio.NewReader(os.Stdin) for { data, err := stdReader.ReadString(' ') if err != nil { fmt.Println("Error reading from stdin") panic(err) } r := new(Request) r.Timestamp = time.Now().UnixNano() r.ClientAddr = clientAddr r.Message.ID = getRandom() //消息内容就是用户的输入 r.Message.Content = strings.TrimSpace(data) br, err := json.Marshal(r) if err != nil { log.Panic(err) } fmt.Println(string(br)) content := jointMessage(cRequest, br) //默认N0为主节点,直接把请求信息发送至N0 tcpDial(content, nodeTable["N0"]) } } //返回一个十位数的随机数,作为msgid func getRandom() int { x := big.NewInt(10000000000) for { result, err := rand.Int(rand.Reader, x) if err != nil { log.Panic(err) } if result.Int64() > 1000000000 { return int(result.Int64()) } } }
2.2、cmd.go
package main import ( "crypto/sha256" "encoding/hex" "encoding/json" "log" ) //<REQUEST,o,t,c> type Request struct { Message Timestamp int64 //相当于clientID ClientAddr string } //<<PRE-PREPARE,v,n,d>,m> type PrePrepare struct { RequestMessage Request Digest string SequenceID int Sign []byte } //<PREPARE,v,n,d,i> type Prepare struct { Digest string SequenceID int NodeID string Sign []byte } //<COMMIT,v,n,D(m),i> type Commit struct { Digest string SequenceID int NodeID string Sign []byte } //<REPLY,v,t,c,i,r> type Reply struct { MessageID int NodeID string Result bool } type Message struct { Content string ID int } const prefixCMDLength = 12 type command string const ( cRequest command = "request" cPrePrepare command = "preprepare" cPrepare command = "prepare" cCommit command = "commit" ) //默认前十二位为命令名称 func jointMessage(cmd command, content []byte) []byte { b := make([]byte, prefixCMDLength) for i, v := range []byte(cmd) { b[i] = v } joint := make([]byte, 0) joint = append(b, content...) return joint } //默认前十二位为命令名称 func splitMessage(message []byte) (cmd string, content []byte) { cmdBytes := message[:prefixCMDLength] newCMDBytes := make([]byte, 0) for _, v := range cmdBytes { if v != byte(0) { newCMDBytes = append(newCMDBytes, v) } } cmd = string(newCMDBytes) content = message[prefixCMDLength:] return } //对消息详情进行摘要 func getDigest(request Request) string { b, err := json.Marshal(request) if err != nil { log.Panic(err) } hash := sha256.Sum256(b) //进行十六进制字符串编码 return hex.EncodeToString(hash[:]) }
2.3、main.go
package main import ( "log" "os" ) const nodeCount = 4 //客户端的监听地址 var clientAddr = "127.0.0.1:8888" //节点池,主要用来存储监听地址 var nodeTable map[string]string func main() { //为四个节点生成公私钥 genRsaKeys() nodeTable = map[string]string{ "N0": "127.0.0.1:8000", "N1": "127.0.0.1:8001", "N2": "127.0.0.1:8002", "N3": "127.0.0.1:8003", } if len(os.Args) != 2 { log.Panic("输入的参数有误!") } nodeID := os.Args[1] if nodeID == "client" { clientSendMessageAndListen() //启动客户端程序 } else if addr, ok := nodeTable[nodeID]; ok { p := NewPBFT(nodeID, addr) go p.tcpListen() //启动节点 } else { log.Fatal("无此节点编号!") } select {} }
2.4、pbft.go
package main import ( "encoding/hex" "encoding/json" "fmt" "io/ioutil" "log" "strconv" "sync" ) //本地消息池(模拟持久化层),只有确认提交成功后才会存入此池 var localMessagePool = []Message{} type node struct { //节点ID nodeID string //节点监听地址 addr string //RSA私钥 rsaPrivKey []byte //RSA公钥 rsaPubKey []byte } type pbft struct { //节点信息 node node //每笔请求自增序号 sequenceID int //锁 lock sync.Mutex //临时消息池,消息摘要对应消息本体 messagePool map[string]Request //存放收到的prepare数量(至少需要收到并确认2f个),根据摘要来对应 prePareConfirmCount map[string]map[string]bool //存放收到的commit数量(至少需要收到并确认2f+1个),根据摘要来对应 commitConfirmCount map[string]map[string]bool //该笔消息是否已进行Commit广播 isCommitBordcast map[string]bool //该笔消息是否已对客户端进行Reply isReply map[string]bool } func NewPBFT(nodeID, addr string) *pbft { p := new(pbft) p.node.nodeID = nodeID p.node.addr = addr p.node.rsaPrivKey = p.getPivKey(nodeID) //从生成的私钥文件处读取 p.node.rsaPubKey = p.getPubKey(nodeID) //从生成的私钥文件处读取 p.sequenceID = 0 p.messagePool = make(map[string]Request) p.prePareConfirmCount = make(map[string]map[string]bool) p.commitConfirmCount = make(map[string]map[string]bool) p.isCommitBordcast = make(map[string]bool) p.isReply = make(map[string]bool) return p } func (p *pbft) handleRequest(data []byte) { //切割消息,根据消息命令调用不同的功能 cmd, content := splitMessage(data) switch command(cmd) { case cRequest: p.handleClientRequest(content) case cPrePrepare: p.handlePrePrepare(content) case cPrepare: p.handlePrepare(content) case cCommit: p.handleCommit(content) } } //处理客户端发来的请求 func (p *pbft) handleClientRequest(content []byte) { fmt.Println("主节点已接收到客户端发来的request ...") //使用json解析出Request结构体 r := new(Request) err := json.Unmarshal(content, r) if err != nil { log.Panic(err) } //添加信息序号 p.sequenceIDAdd() //获取消息摘要 digest := getDigest(*r) fmt.Println("已将request存入临时消息池") //存入临时消息池 p.messagePool[digest] = *r //主节点对消息摘要进行签名 digestByte, _ := hex.DecodeString(digest) signInfo := p.RsaSignWithSha256(digestByte, p.node.rsaPrivKey) //拼接成PrePrepare,准备发往follower节点 pp := PrePrepare{*r, digest, p.sequenceID, signInfo} b, err := json.Marshal(pp) if err != nil { log.Panic(err) } fmt.Println("正在向其他节点进行进行PrePrepare广播 ...") //进行PrePrepare广播 p.broadcast(cPrePrepare, b) fmt.Println("PrePrepare广播完成") } //处理预准备消息 func (p *pbft) handlePrePrepare(content []byte) { fmt.Println("本节点已接收到主节点发来的PrePrepare ...") // //使用json解析出PrePrepare结构体 pp := new(PrePrepare) err := json.Unmarshal(content, pp) if err != nil { log.Panic(err) } //获取主节点的公钥,用于数字签名验证 primaryNodePubKey := p.getPubKey("N0") digestByte, _ := hex.DecodeString(pp.Digest) if digest := getDigest(pp.RequestMessage); digest != pp.Digest { fmt.Println("信息摘要对不上,拒绝进行prepare广播") } else if p.sequenceID+1 != pp.SequenceID { fmt.Println("消息序号对不上,拒绝进行prepare广播") } else if !p.RsaVerySignWithSha256(digestByte, pp.Sign, primaryNodePubKey) { fmt.Println("主节点签名验证失败!,拒绝进行prepare广播") } else { //序号赋值 p.sequenceID = pp.SequenceID //将信息存入临时消息池 fmt.Println("已将消息存入临时节点池") p.messagePool[pp.Digest] = pp.RequestMessage //节点使用私钥对其签名 sign := p.RsaSignWithSha256(digestByte, p.node.rsaPrivKey) //拼接成Prepare pre := Prepare{pp.Digest, pp.SequenceID, p.node.nodeID, sign} bPre, err := json.Marshal(pre) if err != nil { log.Panic(err) } //进行准备阶段的广播 fmt.Println("正在进行Prepare广播 ...") p.broadcast(cPrepare, bPre) fmt.Println("Prepare广播完成") } } //处理准备消息 func (p *pbft) handlePrepare(content []byte) { //使用json解析出Prepare结构体 pre := new(Prepare) err := json.Unmarshal(content, pre) if err != nil { log.Panic(err) } fmt.Printf("本节点已接收到%s节点发来的Prepare ... ", pre.NodeID) //获取消息源节点的公钥,用于数字签名验证 MessageNodePubKey := p.getPubKey(pre.NodeID) digestByte, _ := hex.DecodeString(pre.Digest) if _, ok := p.messagePool[pre.Digest]; !ok { fmt.Println("当前临时消息池无此摘要,拒绝执行commit广播") } else if p.sequenceID != pre.SequenceID { fmt.Println("消息序号对不上,拒绝执行commit广播") } else if !p.RsaVerySignWithSha256(digestByte, pre.Sign, MessageNodePubKey) { fmt.Println("节点签名验证失败!,拒绝执行commit广播") } else { p.setPrePareConfirmMap(pre.Digest, pre.NodeID, true) count := 0 for range p.prePareConfirmCount[pre.Digest] { count++ } //因为主节点不会发送Prepare,所以不包含自己 specifiedCount := 0 if p.node.nodeID == "N0" { specifiedCount = nodeCount / 3 * 2 } else { specifiedCount = (nodeCount / 3 * 2) - 1 } //如果节点至少收到了2f个prepare的消息(包括自己),并且没有进行过commit广播,则进行commit广播 p.lock.Lock() //获取消息源节点的公钥,用于数字签名验证 if count >= specifiedCount && !p.isCommitBordcast[pre.Digest] { fmt.Println("本节点已收到至少2f个节点(包括本地节点)发来的Prepare信息 ...") //节点使用私钥对其签名 sign := p.RsaSignWithSha256(digestByte, p.node.rsaPrivKey) c := Commit{pre.Digest, pre.SequenceID, p.node.nodeID, sign} bc, err := json.Marshal(c) if err != nil { log.Panic(err) } //进行提交信息的广播 fmt.Println("正在进行commit广播") p.broadcast(cCommit, bc) p.isCommitBordcast[pre.Digest] = true fmt.Println("commit广播完成") } p.lock.Unlock() } } //处理提交确认消息 func (p *pbft) handleCommit(content []byte) { //使用json解析出Commit结构体 c := new(Commit) err := json.Unmarshal(content, c) if err != nil { log.Panic(err) } fmt.Printf("本节点已接收到%s节点发来的Commit ... ", c.NodeID) //获取消息源节点的公钥,用于数字签名验证 MessageNodePubKey := p.getPubKey(c.NodeID) digestByte, _ := hex.DecodeString(c.Digest) if _, ok := p.prePareConfirmCount[c.Digest]; !ok { fmt.Println("当前prepare池无此摘要,拒绝将信息持久化到本地消息池") } else if p.sequenceID != c.SequenceID { fmt.Println("消息序号对不上,拒绝将信息持久化到本地消息池") } else if !p.RsaVerySignWithSha256(digestByte, c.Sign, MessageNodePubKey) { fmt.Println("节点签名验证失败!,拒绝将信息持久化到本地消息池") } else { p.setCommitConfirmMap(c.Digest, c.NodeID, true) count := 0 for range p.commitConfirmCount[c.Digest] { count++ } //如果节点至少收到了2f+1个commit消息(包括自己),并且节点没有回复过,并且已进行过commit广播,则提交信息至本地消息池,并reply成功标志至客户端! p.lock.Lock() if count >= nodeCount/3*2 && !p.isReply[c.Digest] && p.isCommitBordcast[c.Digest] { fmt.Println("本节点已收到至少2f + 1 个节点(包括本地节点)发来的Commit信息 ...") //将消息信息,提交到本地消息池中! localMessagePool = append(localMessagePool, p.messagePool[c.Digest].Message) info := p.node.nodeID + "节点已将msgid:" + strconv.Itoa(p.messagePool[c.Digest].ID) + "存入本地消息池中,消息内容为:" + p.messagePool[c.Digest].Content fmt.Println(info) fmt.Println("正在reply客户端 ...") tcpDial([]byte(info), p.messagePool[c.Digest].ClientAddr) p.isReply[c.Digest] = true fmt.Println("reply完毕") } p.lock.Unlock() } } //序号累加 func (p *pbft) sequenceIDAdd() { p.lock.Lock() p.sequenceID++ p.lock.Unlock() } //向除自己外的其他节点进行广播 func (p *pbft) broadcast(cmd command, content []byte) { for i := range nodeTable { if i == p.node.nodeID { continue } message := jointMessage(cmd, content) go tcpDial(message, nodeTable[i]) } } //为多重映射开辟赋值 func (p *pbft) setPrePareConfirmMap(val, val2 string, b bool) { if _, ok := p.prePareConfirmCount[val]; !ok { p.prePareConfirmCount[val] = make(map[string]bool) } p.prePareConfirmCount[val][val2] = b } //为多重映射开辟赋值 func (p *pbft) setCommitConfirmMap(val, val2 string, b bool) { if _, ok := p.commitConfirmCount[val]; !ok { p.commitConfirmCount[val] = make(map[string]bool) } p.commitConfirmCount[val][val2] = b } //传入节点编号, 获取对应的公钥 func (p *pbft) getPubKey(nodeID string) []byte { key, err := ioutil.ReadFile("Keys/" + nodeID + "/" + nodeID + "_RSA_PUB") if err != nil { log.Panic(err) } return key } //传入节点编号, 获取对应的私钥 func (p *pbft) getPivKey(nodeID string) []byte { key, err := ioutil.ReadFile("Keys/" + nodeID + "/" + nodeID + "_RSA_PIV") if err != nil { log.Panic(err) } return key }
2.5、rsa.go
package main import ( "crypto" "crypto/rand" "crypto/rsa" "crypto/sha256" "crypto/x509" "encoding/pem" "errors" "fmt" "log" "os" "strconv" ) //如果当前目录下不存在目录Keys,则创建目录,并为各个节点生成rsa公私钥 func genRsaKeys() { if !isExist("./Keys") { fmt.Println("检测到还未生成公私钥目录,正在生成公私钥 ...") err := os.Mkdir("Keys", 0644) if err != nil { log.Panic() } for i := 0; i <= 4; i++ { if !isExist("./Keys/N" + strconv.Itoa(i)) { err := os.Mkdir("./Keys/N"+strconv.Itoa(i), 0644) if err != nil { log.Panic() } } priv, pub := getKeyPair() privFileName := "Keys/N" + strconv.Itoa(i) + "/N" + strconv.Itoa(i) + "_RSA_PIV" file, err := os.OpenFile(privFileName, os.O_RDWR|os.O_CREATE, 0644) if err != nil { log.Panic(err) } defer file.Close() file.Write(priv) pubFileName := "Keys/N" + strconv.Itoa(i) + "/N" + strconv.Itoa(i) + "_RSA_PUB" file2, err := os.OpenFile(pubFileName, os.O_RDWR|os.O_CREATE, 0644) if err != nil { log.Panic(err) } defer file2.Close() file2.Write(pub) } fmt.Println("已为节点们生成RSA公私钥") } } //生成rsa公私钥 func getKeyPair() (prvkey, pubkey []byte) { // 生成私钥文件 privateKey, err := rsa.GenerateKey(rand.Reader, 1024) if err != nil { panic(err) } derStream := x509.MarshalPKCS1PrivateKey(privateKey) block := &pem.Block{ Type: "RSA PRIVATE KEY", Bytes: derStream, } prvkey = pem.EncodeToMemory(block) publicKey := &privateKey.PublicKey derPkix, err := x509.MarshalPKIXPublicKey(publicKey) if err != nil { panic(err) } block = &pem.Block{ Type: "PUBLIC KEY", Bytes: derPkix, } pubkey = pem.EncodeToMemory(block) return } //判断文件或文件夹是否存在 func isExist(path string) bool { _, err := os.Stat(path) if err != nil { if os.IsExist(err) { return true } if os.IsNotExist(err) { return false } fmt.Println(err) return false } return true } //数字签名 func (p *pbft) RsaSignWithSha256(data []byte, keyBytes []byte) []byte { h := sha256.New() h.Write(data) hashed := h.Sum(nil) block, _ := pem.Decode(keyBytes) if block == nil { panic(errors.New("private key error")) } privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) if err != nil { fmt.Println("ParsePKCS8PrivateKey err", err) panic(err) } signature, err := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA256, hashed) if err != nil { fmt.Printf("Error from signing: %s ", err) panic(err) } return signature } //签名验证 func (p *pbft) RsaVerySignWithSha256(data, signData, keyBytes []byte) bool { block, _ := pem.Decode(keyBytes) if block == nil { panic(errors.New("public key error")) } pubKey, err := x509.ParsePKIXPublicKey(block.Bytes) if err != nil { panic(err) } hashed := sha256.Sum256(data) err = rsa.VerifyPKCS1v15(pubKey.(*rsa.PublicKey), crypto.SHA256, hashed[:], signData) if err != nil { panic(err) } return true }
2.6、tcp.go
package main import ( "fmt" "io/ioutil" "log" "net" ) //客户端使用的tcp监听 func clientTcpListen() { listen, err := net.Listen("tcp", clientAddr) if err != nil { log.Panic(err) } defer listen.Close() for { conn, err := listen.Accept() if err != nil { log.Panic(err) } b, err := ioutil.ReadAll(conn) if err != nil { log.Panic(err) } fmt.Println(string(b)) } } //节点使用的tcp监听 func (p *pbft) tcpListen() { listen, err := net.Listen("tcp", p.node.addr) if err != nil { log.Panic(err) } fmt.Printf("节点开启监听,地址:%s ", p.node.addr) defer listen.Close() for { conn, err := listen.Accept() if err != nil { log.Panic(err) } b, err := ioutil.ReadAll(conn) if err != nil { log.Panic(err) } p.handleRequest(b) } } //使用tcp发送消息 func tcpDial(context []byte, addr string) { conn, err := net.Dial("tcp", addr) if err != nil { log.Println("connect error", err) return } _, err = conn.Write(context) if err != nil { log.Fatal(err) } conn.Close() }
>参考资料: > - https://www.jianshu.com/p/fb5edf031afd > - https://www.cnblogs.com/gexin/p/10242161.html <br> 本demo为pbft共识算法的代码实现,如果想了解pbft的详细信息请自行浏览参考资料 本demo展示了pbft的部分功能(没有写主节点轮循机制),写的并不严谨,仅作为对pbft的了解用途 <br> ![在这里插入图片描述](images/流程图.webp) ## 实现功能: >pbft公式: n>=3f + 1 其中n为全网总节点数量,f为最多允许的作恶、故障节点 数据从客户端输入,到接收到节点们的回复共分为5步 1. 客户端向主节点发送请求信息 2. 主节点N0接收到客户端请求后将请求数据里的主要信息提出,并向其余节点进行preprepare发送 3. 从节点们接收到来自主节点的preprepare,首先利用主节点的公钥进行签名认证,其次将消息进行散列(消息摘要,以便缩小信息在网络中的传输大小)后,向其他节点广播prepare 4. 节点接收到2f个prepare信息(包含自己),并全部签名验证通过,则可以进行到commit步骤,向全网其他节点广播commit 5. 节点接收到2f+1个commit信息(包含自己),并全部签名验证通过,则可以把消息存入到本地,并向客户端返回reply消息 <br> ## 运行步骤: <br> ##### 1.下载/编译 ```shell git clone https://github.com/corgi-kx/blockchain_consensus_algorithm.git ``` ```shell cd blockchain_consensus_algorithm/pbft ``` ```go go build -o pbft.exe ``` ##### 2.开启五个端口(一个客户端,四个节点) 客户端执行pbft.exe client 其他四个节点依次执行 pbft.exe N0 pbft.exe N1 pbft.exe N2 pbft.exe N3 ![在这里插入图片描述](images/启动.png) ##### 3.输入一段信息,看看节点之间的同步过程 ![在这里插入图片描述](images/启动后.png) ##### 4.关闭一个节点(代表作恶、故障节点),再次输入信息,看看是否还会接收到reply 可以看到,客户端依然会接收到reply,因为根据公式 n >= 3f+1 ,就算宕机一个节点,系统依然能顺利运行 ![](images/掉了一个节点后.png) ##### 4.关闭两个节点(代表作恶、故障节点),再次输入信息,看看是否还会接收到reply 可以看到,关闭两个节点后,故障节点已经超出了pbft的允许数量,消息进行到Prepare阶段由于接收不到满足数量的信息,固系统不再进行commit确认,客户端也接收不到reply ![在这里插入图片描述](images/关闭两个节点.png) >**   建了个QQ群:722124200 有问题可以加群互相讨论 :)** >**   邮箱:mikesen1994@gmail.com     vx:965952482**
pos
package main import ( "crypto/rand" "crypto/sha256" "encoding/hex" "fmt" "log" "math/big" "strconv" "time" ) type block struct { //上一个块的hash prehash string //本块hash hash string //时间戳 timestamp string //区块内容 data string //区块高度 height int //挖出本块的地址 address string } //用于存储区块链 var blockchain []block //代表挖矿节点 type node struct { //代币数量 tokens int //质押时间 days int //节点地址 address string } //挖矿节点 var mineNodesPool []node //概率节点池 var probabilityNodesPool []node //初始化 func init() { //手动添加两个节点 mineNodesPool = append(mineNodesPool, node{1000, 1, "AAAAAAAAAA"}) mineNodesPool = append(mineNodesPool, node{100, 3, "BBBBBBBBBB"}) //初始化随机节点池(挖矿概率与代币数量和币龄有关) for _, v := range mineNodesPool { for i := 0; i <= v.tokens*v.days; i++ { probabilityNodesPool = append(probabilityNodesPool, v) } } } //生成新的区块 func generateNewBlock(oldBlock block, data string, address string) block { newBlock := block{} newBlock.prehash = oldBlock.hash newBlock.data = data newBlock.timestamp = time.Now().Format("2006-01-02 15:04:05") newBlock.height = oldBlock.height + 1 newBlock.address = getMineNodeAddress() newBlock.getHash() return newBlock } //对自身进行散列 func (b *block) getHash() { sumString := b.prehash + b.timestamp + b.data + b.address + strconv.Itoa(b.height) hash := sha256.Sum256([]byte(sumString)) b.hash = hex.EncodeToString(hash[:]) } //随机得出挖矿地址(挖矿概率跟代币数量与币龄有关) func getMineNodeAddress() string { bInt := big.NewInt(int64(len(probabilityNodesPool))) //得出一个随机数,最大不超过随机节点池的大小 rInt, err := rand.Int(rand.Reader, bInt) if err != nil { log.Panic(err) } return probabilityNodesPool[int(rInt.Int64())].address } func main() { //创建创世区块 genesisBlock := block{"0000000000000000000000000000000000000000000000000000000000000000", "", time.Now().Format("2006-01-02 15:04:05"), "我是创世区块", 1, "0000000000"} genesisBlock.getHash() //把创世区块添加进区块链 blockchain = append(blockchain, genesisBlock) fmt.Println(blockchain[0]) i := 0 for { time.Sleep(time.Second) newBlock := generateNewBlock(blockchain[i], "我是区块内容", "00000") blockchain = append(blockchain, newBlock) fmt.Println(blockchain[i+1]) i++ } }
>权益证明机制最开始是由点点币提出并应用(出块概率=代币数量 * 币龄) 简单来说谁的币多,谁就有更大的出块概率。但是深挖下去,这个出块概率谁来计算?碰到无成本利益关系问题怎么办?这个共识算法初看很简单,实际有很多问题需要解决,且看以太坊什么时候能完全转换到POS机制吧 <br> 区块结构 ```go type block struct { //上一个块的hash prehash string //本块hash hash string //时间戳 timestamp string //区块内容 data string //区块高度 height int //挖出本块的地址 address string } ``` 声明两个节点池 mineNodesPool 用来存放指定的挖矿节点 probabilityNodesPool 用于存入挖矿节点的代币数量*币龄获得的概率 ```go //用于存储区块链 var blockchain []block //代表挖矿节点 type node struct{ //代币数量 tokens int //质押时间 days int //节点地址 address string } //挖矿节点 var mineNodesPool []node //概率节点池 var probabilityNodesPool []node ``` 初始化节点池: ```go func init () { //手动添加两个节点 mineNodesPool = append(mineNodesPool,node{1000,1,"AAAAAAAAAA"}) mineNodesPool = append(mineNodesPool,node{100,3,"BBBBBBBBBB"}) //初始化随机节点池(挖矿概率与代币数量和币龄有关) for _,v:=range mineNodesPool{ for i:=0;i<=v.tokens * v.days; i ++ { randNodesPool = append(randNodesPool,v) } } } ``` 每次挖矿都会从概率节点池中随机选出获得出块权的节点地址 ```go //随机得出挖矿地址(挖矿概率跟代币数量与币龄有关) func getMineNodeAddress() string{ bInt:=big.NewInt(int64(len(randNodesPool))) //得出一个随机数,最大不超过随机节点池的大小 rInt,err:=rand.Int(rand.Reader,bInt) if err != nil { log.Panic(err) } return randNodesPool[int(rInt.Int64())].address } ``` ```go func main() { //创建创世区块 genesisBlock := block{"0000000000000000000000000000000000000000000000000000000000000000","",time.Now().Format("2006-01-02 15:04:05"),"我是创世区块",1,"0000000000"} genesisBlock.getHash() //把创世区块添加进区块链 blockchain = append(blockchain,genesisBlock) fmt.Println(blockchain[0]) i:=0 for { time.Sleep(time.Second) newBlock:=generateNewBlock(blockchain[i],"我是区块内容","00000") blockchain = append(blockchain,newBlock) fmt.Println(blockchain[i + 1]) i++ } } ``` 运行结果: ![在这里插入图片描述](https://img-blog.csdnimg.cn/20191211154915783.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM1OTExMTg0,size_16,color_FFFFFF,t_70)
pow
package main import ( "crypto/sha256" "encoding/hex" "encoding/json" "fmt" "log" "math/big" "strconv" "time" ) //用于存放区块,以便连接成区块链 var blockchain []block //挖矿难度值 var diffNum uint = 17 type block struct { //上一个区块的Hash Lasthash string //本区块Hash Hash string //区块存储的数据(比如比特币UTXO模型 则此处可用于存储交易) Data string //时间戳 Timestamp string //区块高度 Height int //难度值 DiffNum uint //随机数 Nonce int64 } //区块挖矿(通过自身递增nonce值计算hash) func mine(data string) block { if len(blockchain) < 1 { log.Panic("还未生成创世区块!") } lastBlock := blockchain[len(blockchain)-1] //制造一个新的区块 newBlock := new(block) newBlock.Lasthash = lastBlock.Hash newBlock.Timestamp = time.Now().String() newBlock.Height = lastBlock.Height + 1 newBlock.DiffNum = diffNum newBlock.Data = data var nonce int64 = 0 //根据挖矿难度值计算的一个大数 newBigint := big.NewInt(1) newBigint.Lsh(newBigint, 256-diffNum) //相当于左移 1<<256-diffNum for { newBlock.Nonce = nonce newBlock.getHash() hashInt := big.Int{} hashBytes, _ := hex.DecodeString(newBlock.Hash) hashInt.SetBytes(hashBytes) //把本区块hash值转换为一串数字 //如果hash小于挖矿难度值计算的一个大数,则代表挖矿成功 if hashInt.Cmp(newBigint) == -1 { break } else { nonce++ //不满足条件,则不断递增随机数,直到本区块的散列值小于指定的大数 } } return *newBlock } func (b *block) serialize() []byte { bytes, err := json.Marshal(b) if err != nil { log.Panic(err) } return bytes } func (b *block) getHash() { result := sha256.Sum256(b.serialize()) b.Hash = hex.EncodeToString(result[:]) } func main() { //制造一个创世区块 genesisBlock := new(block) genesisBlock.Timestamp = time.Now().String() genesisBlock.Data = "我是创世区块!" genesisBlock.Lasthash = "0000000000000000000000000000000000000000000000000000000000000000" genesisBlock.Height = 1 genesisBlock.Nonce = 0 genesisBlock.DiffNum = 0 genesisBlock.getHash() fmt.Println(*genesisBlock) //将创世区块添加进区块链 blockchain = append(blockchain, *genesisBlock) for i := 0; i < 10; i++ { newBlock := mine("天气不错" + strconv.Itoa(i)) blockchain = append(blockchain, newBlock) fmt.Println(newBlock) } }
<br> >工作量证明机制的核心在于不断hash区块自身,将hash值与根据难度值计算出的一串大数对比,如果自身hash小于大数则说明挖矿成功,否则变化自身随机数重新计算。并且程序会随着出块间隔时间动态调节难度值(比如比特币) <br> 区块结构 ```go type block struct { //上一个区块的Hash Lasthash string //本区块Hash Hash string //区块存储的数据(比如比特币UTXO模型 则此处可用于存储交易) Data string //时间戳 Timestamp string //区块高度 Height int //难度值 DiffNum uint //随机数 Nonce int64 } ``` 挖矿函数: 使用math/big包,根据全局变量的难度值diffNum计算出用于实际比较的一串大数newBigint ,并同时将区块hash转换为大数hashInt 两个大数进行数值比较,如果hashInt小于newBigint 则代表挖矿成功 ```go //区块挖矿(通过自身递增nonce值计算hash) func mine(data string) block { if len(blockchain) < 1 { log.Panic("还未生成创世区块!") } lastBlock := blockchain[len(blockchain)-1] //制造一个新的区块 newBlock := new(block) newBlock.Lasthash = lastBlock.Hash newBlock.Timestamp = time.Now().String() newBlock.Height = lastBlock.Height + 1 newBlock.DiffNum = diffNum newBlock.Data = data var nonce int64 = 0 //根据挖矿难度值计算的一个大数 newBigint := big.NewInt(1) newBigint.Lsh(newBigint, 256-diffNum) //相当于左移 1<<256-diffNum for { newBlock.Nonce = nonce newBlock.getHash() hashInt := big.Int{} hashBytes, _ := hex.DecodeString(newBlock.Hash) hashInt.SetBytes(hashBytes) //把本区块hash值转换为一串数字 //如果hash小于挖矿难度值计算的一个大数,则代表挖矿成功 if hashInt.Cmp(newBigint) == -1 { break } else { nonce++ //不满足条件,则不断递增随机数,直到本区块的散列值小于指定的大数 } } return *newBlock } ``` ```go func main() { //制造一个创世区块 genesisBlock := new(block) genesisBlock.Timestamp = time.Now().String() genesisBlock.Data = "我是创世区块!" genesisBlock.Lasthash = "0000000000000000000000000000000000000000000000000000000000000000" genesisBlock.Height = 1 genesisBlock.Nonce = 0 genesisBlock.DiffNum = 0 genesisBlock.getHash() fmt.Println(*genesisBlock) //将创世区块添加进区块链 blockchain = append(blockchain, *genesisBlock) for i := 0; i < 10; i++ { newBlock := mine("天气不错"+strconv.Itoa(i)) blockchain = append(blockchain, newBlock) fmt.Println(newBlock) } ``` 运行结果: ![在这里插入图片描述](https://img-blog.csdnimg.cn/20191211145732513.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM1OTExMTg0,size_16,color_FFFFFF,t_70)
raft
5.1、http.go
package main import ( "crypto/rand" "fmt" "log" "math/big" "net/http" "net/rpc" ) //等待节点访问 func (rf *Raft) getRequest(writer http.ResponseWriter, request *http.Request) { request.ParseForm() //http://localhost:8080/req?message=ohmygod if len(request.Form["message"]) > 0 && rf.currentLeader != "-1" { message := request.Form["message"][0] m := new(Message) m.MsgID = getRandom() m.Msg = message //接收到消息后,直接转发到领导者 fmt.Println("http监听到了消息,准备发送给领导者,消息id:", m.MsgID) port := nodeTable[rf.currentLeader] rp, err := rpc.DialHTTP("tcp", "127.0.0.1"+port) if err != nil { log.Panic(err) } b := false err = rp.Call("Raft.LeaderReceiveMessage", m, &b) if err != nil { log.Panic(err) } fmt.Println("消息是否已发送到领导者:", b) writer.Write([]byte("ok!!!")) } } func (rf *Raft) httpListen() { //创建getRequest()回调方法 http.HandleFunc("/req", rf.getRequest) fmt.Println("监听8080") if err := http.ListenAndServe(":8080", nil); err != nil { fmt.Println(err) return } } //返回一个十位数的随机数,作为消息idgit func getRandom() int { x := big.NewInt(10000000000) for { result, err := rand.Int(rand.Reader, x) if err != nil { log.Panic(err) } if result.Int64() > 1000000000 { return int(result.Int64()) } } }
5.2、main.go
package main import ( "fmt" "log" "os" "time" ) //定义节点数量 var raftCount = 3 //节点池 var nodeTable map[string]string //选举超时时间(单位:秒) var timeout = 3 //心跳检测超时时间 var heartBeatTimeout = 7 //心跳检测频率(单位:秒) var heartBeatTimes = 3 //用于存储消息 var MessageStore = make(map[int]string) func main() { //定义三个节点 节点编号 - 监听端口号 nodeTable = map[string]string{ "A": ":9000", "B": ":9001", "C": ":9002", } //运行程序时候 指定节点编号 if len(os.Args) < 1 { log.Fatal("程序参数不正确") } id := os.Args[1] //传入节点编号,端口号,创建raft实例 raft := NewRaft(id, nodeTable[id]) //启用RPC,注册raft go rpcRegister(raft) //开启心跳检测 go raft.heartbeat() //开启一个Http监听 if id == "A" { go raft.httpListen() } Circle: //开启选举 go func() { for { //成为候选人节点 if raft.becomeCandidate() { //成为后选人节点后 向其他节点要选票来进行选举 if raft.election() { break } else { continue } } else { break } } }() //进行心跳检测 for { //0.5秒检测一次 time.Sleep(time.Millisecond * 5000) if raft.lastHeartBeartTime != 0 && (millisecond()-raft.lastHeartBeartTime) > int64(raft.timeout*1000) { fmt.Printf("心跳检测超时,已超过%d秒 ", raft.timeout) fmt.Println("即将重新开启选举") raft.reDefault() raft.setCurrentLeader("-1") raft.lastHeartBeartTime = 0 goto Circle } } }
5.3、raft.go
package main import ( "fmt" "math/rand" "sync" "time" ) //声明raft节点类型 type Raft struct { node *NodeInfo //本节点获得的投票数 vote int //线程锁 lock sync.Mutex //节点编号 me string //当前任期 currentTerm int //为哪个节点投票 votedFor string //当前节点状态 //0 follower 1 candidate 2 leader state int //发送最后一条消息的时间 lastMessageTime int64 //发送最后一条消息的时间 lastHeartBeartTime int64 //当前节点的领导 currentLeader string //心跳超时时间(单位:秒) timeout int //接收投票成功通道 voteCh chan bool //心跳信号 heartBeat chan bool } type NodeInfo struct { ID string Port string } type Message struct { Msg string MsgID int } func NewRaft(id, port string) *Raft { node := new(NodeInfo) node.ID = id node.Port = port rf := new(Raft) //节点信息 rf.node = node //当前节点获得票数 rf.setVote(0) //编号 rf.me = id //给0 1 2三个节点投票,给谁都不投 rf.setVoteFor("-1") //0 follower rf.setStatus(0) //最后一次心跳检测时间 rf.lastHeartBeartTime = 0 rf.timeout = heartBeatTimeout //最初没有领导 rf.setCurrentLeader("-1") //设置任期 rf.setTerm(0) //投票通道 rf.voteCh = make(chan bool) //心跳通道 rf.heartBeat = make(chan bool) return rf } //修改节点为候选人状态 func (rf *Raft) becomeCandidate() bool { r := randRange(1500, 5000) //休眠随机时间后,再开始成为候选人 time.Sleep(time.Duration(r) * time.Millisecond) //如果发现本节点已经投过票,或者已经存在领导者,则不用变身候选人状态 if rf.state == 0 && rf.currentLeader == "-1" && rf.votedFor == "-1" { //将节点状态变为1 rf.setStatus(1) //设置为哪个节点投票 rf.setVoteFor(rf.me) //节点任期加1 rf.setTerm(rf.currentTerm + 1) //当前没有领导 rf.setCurrentLeader("-1") //为自己投票 rf.voteAdd() fmt.Println("本节点已变更为候选人状态") fmt.Printf("当前得票数:%d ", rf.vote) //开启选举通道 return true } else { return false } } //进行选举 func (rf *Raft) election() bool { fmt.Println("开始进行领导者选举,向其他节点进行广播") go rf.broadcast("Raft.Vote", rf.node, func(ok bool) { rf.voteCh <- ok }) for { select { case <-time.After(time.Second * time.Duration(timeout)): fmt.Println("领导者选举超时,节点变更为追随者状态 ") rf.reDefault() return false case ok := <-rf.voteCh: if ok { rf.voteAdd() fmt.Printf("获得来自其他节点的投票,当前得票数:%d ", rf.vote) } if rf.vote > raftCount/2 && rf.currentLeader == "-1" { fmt.Println("获得超过网络节点二分之一的得票数,本节点被选举成为了leader") //节点状态变为2,代表leader rf.setStatus(2) //当前领导者为自己 rf.setCurrentLeader(rf.me) fmt.Println("向其他节点进行广播...") go rf.broadcast("Raft.ConfirmationLeader", rf.node, func(ok bool) { fmt.Println(ok) }) //开启心跳检测通道 rf.heartBeat <- true return true } } } } //心跳检测方法 func (rf *Raft) heartbeat() { //如果收到通道开启的信息,将会向其他节点进行固定频率的心跳检测 if <-rf.heartBeat { for { fmt.Println("本节点开始发送心跳检测...") rf.broadcast("Raft.HeartbeatRe", rf.node, func(ok bool) { fmt.Println("收到回复:", ok) }) rf.lastHeartBeartTime = millisecond() time.Sleep(time.Second * time.Duration(heartBeatTimes)) } } } //产生随机值 func randRange(min, max int64) int64 { //用于心跳信号的时间 rand.Seed(time.Now().UnixNano()) return rand.Int63n(max-min) + min } //获取当前时间的毫秒数 func millisecond() int64 { return time.Now().UnixNano() / int64(time.Millisecond) } //设置任期 func (rf *Raft) setTerm(term int) { rf.lock.Lock() rf.currentTerm = term rf.lock.Unlock() } //设置为谁投票 func (rf *Raft) setVoteFor(id string) { rf.lock.Lock() rf.votedFor = id rf.lock.Unlock() } //设置当前领导者 func (rf *Raft) setCurrentLeader(leader string) { rf.lock.Lock() rf.currentLeader = leader rf.lock.Unlock() } //设置当前领导者 func (rf *Raft) setStatus(state int) { rf.lock.Lock() rf.state = state rf.lock.Unlock() } //投票累加 func (rf *Raft) voteAdd() { rf.lock.Lock() rf.vote++ rf.lock.Unlock() } //设置投票数量 func (rf *Raft) setVote(num int) { rf.lock.Lock() rf.vote = num rf.lock.Unlock() } //恢复默认设置 func (rf *Raft) reDefault() { rf.setVote(0) //rf.currentLeader = "-1" rf.setVoteFor("-1") rf.setStatus(0) }
5.4、rpc.go
package main import ( "fmt" "log" "net/http" "net/rpc" "time" ) //rpc服务注册 func rpcRegister(raft *Raft) { //注册一个服务器 err := rpc.Register(raft) if err != nil { log.Panic(err) } port := raft.node.Port //把服务绑定到http协议上 rpc.HandleHTTP() //监听端口 err = http.ListenAndServe(port, nil) if err != nil { fmt.Println("注册rpc服务失败", err) } } func (rf *Raft) broadcast(method string, args interface{}, fun func(ok bool)) { //设置不要自己给自己广播 for nodeID, port := range nodeTable { if nodeID == rf.node.ID { continue } //连接远程rpc rp, err := rpc.DialHTTP("tcp", "127.0.0.1"+port) if err != nil { fun(false) continue } var bo = false err = rp.Call(method, args, &bo) if err != nil { fun(false) continue } fun(bo) } } //投票 func (rf *Raft) Vote(node NodeInfo, b *bool) error { if rf.votedFor != "-1" || rf.currentLeader != "-1" { *b = false } else { rf.setVoteFor(node.ID) fmt.Printf("投票成功,已投%s节点 ", node.ID) *b = true } return nil } //确认领导者 func (rf *Raft) ConfirmationLeader(node NodeInfo, b *bool) error { rf.setCurrentLeader(node.ID) *b = true fmt.Println("已发现网络中的领导节点,", node.ID, "成为了领导者! ") rf.reDefault() return nil } //心跳检测回复 func (rf *Raft) HeartbeatRe(node NodeInfo, b *bool) error { rf.setCurrentLeader(node.ID) rf.lastHeartBeartTime = millisecond() fmt.Printf("接收到来自领导节点%s的心跳检测 ", node.ID) fmt.Printf("当前时间为:%d ", millisecond()) *b = true return nil } //追随者节点用来接收消息,然后存储到消息池中,待领导者确认后打印 func (rf *Raft) ReceiveMessage(message Message, b *bool) error { fmt.Printf("接收到领导者节点发来的信息,id为:%d ", message.MsgID) MessageStore[message.MsgID] = message.Msg *b = true fmt.Println("已回复接收到消息,待领导者确认后打印") return nil } //追随者节点的反馈得到领导者节点的确认,开始打印消息 func (rf *Raft) ConfirmationMessage(message Message, b *bool) error { go func() { for { if _, ok := MessageStore[message.MsgID]; ok { fmt.Printf("raft验证通过,可以打印消息,id为:%d ", message.MsgID) fmt.Println("消息为:", MessageStore[message.MsgID], " ") rf.lastMessageTime = millisecond() break } else { //如果没有此消息,等一会看看!!! time.Sleep(time.Millisecond * 10) } } }() *b = true return nil } //领导者接收到,追随者节点转发过来的消息 func (rf *Raft) LeaderReceiveMessage(message Message, b *bool) error { fmt.Printf("领导者节点接收到转发过来的消息,id为:%d ", message.MsgID) MessageStore[message.MsgID] = message.Msg *b = true fmt.Println("准备将消息进行广播...") num := 0 go rf.broadcast("Raft.ReceiveMessage", message, func(ok bool) { if ok { num++ } }) for { //自己默认收到了消息,所以减去一 if num > raftCount/2-1 { fmt.Printf("全网已超过半数节点接收到消息id:%d raft验证通过,可以打印消息,id为:%d ", message.MsgID, message.MsgID) fmt.Println("消息为:", MessageStore[message.MsgID], " ") rf.lastMessageTime = millisecond() fmt.Println("准备将消息提交信息发送至客户端...") go rf.broadcast("Raft.ConfirmationMessage", message, func(ok bool) { }) break } else { //休息会儿 time.Sleep(time.Millisecond * 100) } } return nil }
>**   邮箱:mikesen1994@gmail.com            vx:965952482** <hr> 本demo为raft的代码实现,如果想了解raft的详细信息请自行浏览参考资料<br> 本demo展示了raft的部分功能,写的并不严谨,仅作为对raft的了解用途 <br> ## 实现功能: - 节点状态分为Leader(领导者)、Follower(追随者)、Candidate(候选人) - 节点间随机成为candidate状态并选举出Leader,且同时仅存在一个Leader - Leader节点定时发送心跳检测至其他Follower节点 - Follower节点们超过一定时间未收到心跳检测,则Follower节点们重新开启选举 - 客户端通过http发送消息到节点A,如果A不是Leader则转发至Leader节点 - Leader收到客户端的消息后向Follower节点进行广播 - Follower节点收到消息,反馈给Leader,等待Leader确认 - Leader收到全网超过二分之一的反馈后,本地进行打印,然后将确认收到反馈的信息提交到Follower节点 - Follower节点收到确认提交信息后,打印消息 <br> ## 运行步骤: <br> ##### 1.下载/编译 ```shell git clone https://github.com/corgi-kx/blockchain_consensus_algorithm.git ``` ```shell cd blockchain_consensus_algorithm/raft ``` ```go go build -o raft.exe ``` ##### 2.开启三个端口,并分别执行raft.exe A 、raft.exe B 、 raft.exe C,代表开启三个节点(初始状态为追随者) ![在这里插入图片描述](images/开启端口.png) ##### 3.三个节点会随机选举出领导者(其中A节点默认监听来自http的访问),成功的节点会发送心跳检测到其他两个节点 ![在这里插入图片描述](images/选举成功.png) ##### 4.此时打开浏览器用http访问本地节点8080端口,带上节点需要同步打印的消息,比如: `http://localhost:8080/req?message=噢,我的上帝呀` ![在这里插入图片描述](images/打印消息.png) 可以看到三个节点同时打印了消息,本段数据同步步骤可以用下图进行理解(不过缺少了4.1步骤) ![在这里插入图片描述](images/消息同步.png) ##### 5.如果领导者节点宕机了怎么办呢,我们尝试关闭领导者节点B ![在这里插入图片描述](images/领导者节点宕机.png) 可以发现关闭领导者B后,节点间有个超时机制,如果超过一定时间没有收到心跳检测,则会自动开始重新进行选举,此时A当选了新的领导者 ##### 6.再次打开浏览器用http访问本地节点8080端口,带上节点需要同步打印的消息,看看还能同步打印吗 `http://localhost:8080/req?message=天气不错` ![在这里插入图片描述](images/残缺打印.png) 结果发现可以打印的,因为新的领导者A、追随者C并没有宕机,A收到了C的回馈(2>3/2)超过了全网一半的节点,所以是可以进行打印数据的 ##### 7.重新启动节点B,B自动变为追随者状态,并接收来自新的领导者A的心跳检测 ![在这里插入图片描述](images/重启B.png) <hr> >参考资料: > - http://thesecretlivesofdata.com/raft/ > - https://www.cnblogs.com/mindwind/p/5231986.html > - https://blog.csdn.net/s15738841819/article/details/84286276