持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第17天,点击查看活动详情 Raft协议
要求
假设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)
复制代码