stateIs0 / lu-raft-kv

this is raft java project. raft-kv-storage
https://thinkinjava.cn/2019/01/12/2019/2019-01-12-lu-raft-kv/
Apache License 2.0
771 stars 211 forks source link

优化日志提交逻辑 & Bug修复 & 全新Raft客户端 #19

Closed leakey0626 closed 1 year ago

leakey0626 commented 1 year ago

非常感谢莫那·鲁道给我们提供了如此优秀的raft开源项目!
我是raft的初学者大东,在学习您的项目的过程中,我发现了该项目存在一定的优化空间,于是我对代码进行了一些增改,希望能达到锦上添花的效果。下面是我对优化点的说明:

日志提交

问题描述

在原项目中,跟随者接收到 append entry 请求后,会立即将日志内容应用到状态机,个人认为这种做法有风险。

假设日志还未分发到多数节点时,【领导者A】宕机,并且下一任【领导者B】恰好不含该日志,那么该日志将会在【领导者B】发出它的 append entry 请求时被清除。但原先持有该日志的节点已经将其应用到状态机了,并且无法撤销 apply。所以,该机制会导致状态机数据与日志数据的不一致。

这种不一致一方面会导致我们无法通过日志准确地恢复出状态机的数据,不利于备份和分析;另一方面,若后期对项目进行扩展,让跟随者承担一部分“读操作”的职责时,会产生严重的数据不一致问题。

优化

跟随者提交日志的逻辑修改为:

  1. 收到 append entry 请求
  2. 若不是心跳请求,则写入本地日志
  3. 与领导者同步 commit index,将 index ≤ commit index 的日志应用至状态机
  4. 同意 append entry 请求

no-op 空日志

问题描述

image-20230213201539539
  1. 结论①:新领导者上任后不可直接提交旧领导者未提交的日志,即使其已被复制到多数节点;只能在提交自己任期的日志时间接提交旧日志
  2. 论证①(反证法):上图描述了一种可能会破坏数据持久性的情况。任期为2的领导者S1将日志复制到S2后宕机。紧接着任期为3的领导者S5上台,在本地写入了若干日志,然而还没来得及将日志复制到其它节点就宕机了。S1重新上线并恰好当选任期为4的领导者。在没有结论①约束的情况下,S1可以将任期为2的日志复制到S3并提交。如果此时S1宕机,那么S5必然会当选任期为5的领导者,因为它的日志任期是最大的。S5在将它的第3至第5条日志复制到S1~S3时,这些节点里任期为2的日志会被覆盖——这不符合已提交的日志不能被修改的要求。因此需要引入结论①来避免这种情况,该结论在部分raft资料里被称为“延迟提交”
  3. 补充①:引入结论①看似很安全,但如果任期为4的领导者S1在上任后没有收到任何客户端的写请求,那么它将没有机会提交任期为2的日志,发出该写请求的客户端会因此而阻塞——这不符合raft的可用性要求

优化

引入 no-op 空日志来解决补充①里提到的可用性问题:

  1. 新领导者上任后,立即向 raft 集群写入一条 command 为 null 的日志并提交
  2. 这条日志只有索引和任期信息,不会改变状态机的数据
  3. 空日志属于领导者任期的日志,其提交之后,可以确保在此之前的日志都被正确提交。既能满足结论①的约束,又可以解决上述的可用性问题

客户端

优化

  1. 实现了基于命令行交互的客户端:解析用户输入并返回处理结果 客户端