• 待解决问题


    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)
    
    >**&ensp;&ensp;&ensp;建了个QQ群:722124200     有问题可以加群互相讨论   :)** 
    >**&ensp;&ensp;&ensp;邮箱:mikesen1994@gmail.com  &ensp;&ensp;&ensp; 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
    }
    >**&ensp;&ensp;&ensp;邮箱:mikesen1994@gmail.com   &ensp;&ensp;&ensp;&ensp;  &ensp;&ensp; &ensp;&ensp; 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
  • 相关阅读:
    数据中台实战(六):交易分析
    数据中台实战(五):自助分析平台(产品设计篇)
    数据中台实战(四):商品分析(产品设计篇)
    数据中台实战(三):用户分析(产品设计篇)
    数据中台实战(二):基于阿里OneData的数据指标管理体系
    数据中台实战(一):以B2B电商亿订为例,谈谈产品经理视角下的数据埋点
    LeetCode82. 删除排序链表中的重复元素 II
    LeetCode203. 移除链表元素
    LeetCode445. 两数相加 II
    LeetCode2. 两数相加
  • 原文地址:https://www.cnblogs.com/jiangwangxiang/p/13126824.html
Copyright © 2020-2023  润新知