实验课程之raft协议理解实现

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第17天,点击查看活动详情 Raft协议

要求

image.png
假设cluster中有S1-S7,各日志如下图所示,在term8时s1选举成功后,c1向s2发出了write x的请求,此时s1、s2、s3、s4、s6都能正常工作。请模拟从选举成功,到c1的写操作完成的过程。\

解答:

一:日志同步

1.         在term8时s1选举成功后,S1向其他所有server发送日志同步广播,要求同步index为10,term为6的日志,即【index:10,term:6】

2.         S2收到广播,发现缺失index10日志,返回失败,s1收到消息后,减少索引,要求同步【index9,term6】,发现s2【index9,term6】相同,直接同步【index:10,term:6】,

3.         S3收到广播,发现缺失index10日志,返回日志同步失败,s1收到该拒绝消息之后,将降低index值,下一次发送【index:9,term:6】,直到发现【index:4,term:4】相等,记录index+1,下一次同步index5-10的数据

4.         S4收到广播,对比【index:10,term:6】和【index:10,term:6】,发现任期相同,index也相同10,直接删除index 11的数据,同步结束

5.         S5故障,收不到广播

6.         S6收到广播,对比【index:7,term:4】和【index:10,term:6】,发现任期不相同,index也不同,返回日志同步失败,s1收到该拒绝消息之后,将降低index值,直到【index:5,term:4】 ,将同步index6后的数据给s6,并覆盖s6中原来index6后的数据

二:写x

1.         C1向S2发送write x命令

2.         S2收到信息,S2不是领导 返回拒绝和leader S1的地址

3.         C1根据S2返回的地址发送信息到leader S1

4.         S1收到write x命令

5.         S1 首先检查命令ID是否是新命令,如果是旧命令返回命令ID对应的日志操作的结果,如果命令还在执行中,需要等待完成后在返回

6.         检查完毕,是新命令,先写本地日志,不提交

7.         S1开始广播给其他节点,当收到1/2节点都记录日志成功,认为命令已经成功复制,此时S1提交本地日志,然后返回客户端C1,写write x成功

8.         S1将在下一次同步中,告诉其他节点,write x已经提交

伪代码及模拟过程


client{

    writeCommands(network ip,id md5id)

        //id需要递增

        mad5id++

        sendMsg(ip,md5id)

}

followerentrie{

    followid //追随者ID

    current_index//下一次同步的索引

}

serverLog{

    index//索引

    term//任期

}

server{

    nextIndex:11//下一次同步的索引 选举成功后 默认为11

    leaderid:null//leaderID

    serverLogs:[]//leader 本地日志

    followerentries:[]//追随者日志同步记录

    //记录日志

    logIndex(mad5id,term,writex)

        writelogid(mad5id,term,writex)

    //收到命令

    receiveCommands(md5id,writex):

        if(leaderid):

         //检查是否是新命令

        checkObj=termCheckCommandID(md5id)

            if(checkObj)

                //存在 返回该日志id对应的操作的结果 如果old 命令还在执行 需要等待完成后在返回

                 return checkObj.result;

            else

                 //记录日志 但是不commit

                 logIndex(mad5id,term,writex)

                 //广播

                 int i=0

                 for(s in cluster)

                     i+=s.followersAppendEntries(mad5id,term,writex,lastcommittedIndex==null)

                     //一半节点记录日志成功,认为成功

                     if(len(cluster)/2>i)

                     //提交本地日志

                     committedLog(writex)

                     //认为成功 返回通知客户端c1

                     return "ok"

                     break

        else:

            //不是领导返回 领导的地址

            return(false,leader.s1.ip)

    //检查命令是否是本地已经执行过的命令

    termCheckCommandID(md5id):

        if(isExist(md5id))  

            return result

        else

            return null

    //提交本地日志

    committedLog(mad5id,term,writex):

        writestr(mad5id,term,writex)

        return committedIndex

    //追随者附加日志

    followersAppendEntries(mad5id,term,writex,committedIndex):

        //下一次存在提交日志 follow也提交

        if(committedIndex)  

            committedLog(mad5id,term,writex)

        else

            return logindex(mad5id,term,writex)  

    //日志同步

    logSynchronization(nowIndex)

        //广播要求追随者同步

        for(follwer in followerentries)

            //从0开始 到目标nowIndex同步 nextIndex = matchIndex + 1

            while(follwer.current_index+1<nextIndex)

                nowLog=serverLogs[findex.current_index]

                sendServerLogs=serverLogs.splic(findex.current_index:last) //获取指定索引以后的日志

                bool isok=followerslogSynchronization(findex.current_index,nowLog.index,nowLog.term,sendServerLogs)

                if (!isok){

                    follwer.current_index--;

                }

    //跟随节点日志同步

    followerslogSynchronization(current_index,logindex,logterm,sendServerLogs)

        if (this.serverLog[current_index].index==logindex)//index 相同

            //判断term当前学期是否一致

            if(this.serverLog[current_index].index==logterm)

                //copy数据 同步索引下一位后的全部日志

                this.serverLog[current_index+1:last]=sendServerLogs

                //如果跟着者未提交日志过多

                if(this.serverLog.maxIndex>sendServerLogs.maxIndex)

                //删除未提交数据将的节点

                this.serverLog.delete(this.serverLog.maxIndex)

                return true

            else

             //term不一致 失败 服务端会减少index

            return false,

        else if (this.serverLog[current_index].index>logindex)

            //失败 服务端会减少index

            return false,this.serverLog.current_index

        else if (this.serverLog[current_index].index<logindex)

            //失败 服务端会减少index

            return false,this.serverLog.current_index

   

}

//实验四

//日志同步覆盖

s1.logSynchronization(8)

//c1发送信息 s2

c1.writeCommands(s2.ip,writex)

//s2收到信息 s2不是领导 返回拒绝和leader的地址

s2.receiveCommands(md5id,writex)

   return(false,leader.s1.ip)

//c1发送信息到leader s1

c1.writeCommands(s1.ip,writex)  

//s1收到信息

s1.receiveCommands(md5id,writex)

复制代码

猜你喜欢

转载自juejin.im/post/7108372535867080734