因为郁白以前写的关于Multi-Paxos 的文章流传很是广, 具体地址: http://oceanbase.org.cn/?p=111原文提出了一个叫"幽灵复现" 的问题, 认为这个是一个很诡异的问题, 后续和不少人交流关于一致性协议的时候, 也常常会提起这个问题, 可是其实这个问题我认为就是常见的"第三态"问题加了一层包装而已.git
幽灵复现问题github
来自郁白的博客:数据库
使用Paxos协议处理日志的备份与恢复,能够保证确认造成多数派的日志不丢失,可是没法避免一种被称为“幽灵复现”的现象,以下图所示:网络
Leader | A | B | C | |
---|---|---|---|---|
第一轮 | A | 1-10 | 1-5 | 1-5 |
第二轮 | B | 宕机 | 1-6,20 | 1-6,20 |
第三轮 | A | 1-20 | 1-20 | 1-20 |
对于将Paxos协议应用在数据库日志同步场景的状况,幽灵复现问题是不可接受,一个简单的例子就是转帐场景,用户转帐时若是返回结果超时,那么每每会查询一下转帐是否成功,来决定是否重试一下。若是第一次查询转帐结果时,发现未生效而重试,而转帐事务日志做为幽灵复现日志从新出现的话,就形成了用户重复转帐。机器学习
为了处理“幽灵复现”问题,咱们在每条日志的内容中保存一个generateID,leader在生成这条日志时以当前的leader ProposalID做为generateID。按logID顺序回放日志时,由于leader在开始服务以前必定会写一条StartWorking日志,因此若是出现generateID相对前一条日志变小的状况,说明这是一条“幽灵复现”日志(它的generateID会小于StartWorking日志),要忽略掉这条日志。分布式
第三态问题工具
第三态问题也是咱们以前常常讲的问题, 其实在网络系统里面, 对于一个请求都有三种返回结果学习
前面两种状态因为服务端都有明确的返回结果, 因此很是好处理, 可是若是是第三种状态的返回, 因为是超时状态, 因此服务端可能对于这个命令是请求是执行成功, 也有多是执行失败的, 因此若是这个请求是一个写入操做, 那么下一次的读取请求可能读到这个结果, 也可能读到的结果是空的优化
就像在 raft phd 那个论文里面说的, 这个问题实际上是和 raft/multi-paxos 协议无关的内容, 只要在分布式系统里面都会存在这个问题, 因此大部分的解决方法是两个3d
那么对应于raft 中的第三态问题是, 当最后log Index 为4 的请求超时的时候, 状态机中出现的两种场景都是可能的
因此下一次读取的时候有可能读到log Index 4 的内容, 也有可能读不到, 因此若是在发生了超时请求之后, 默认client 须要进行重试直到这个操做成功之后, 接下来才能够保证读到的写入结果. 这也是工程实现里面常见的作法
对应于幽灵问题, 实际上是因为6-10 的操做产生了超时操做, 因为产生了超时操做之后, client 并无对这些操做进行确认, 而是接下来去读取这个结果, 那么读取不到这个里面的内容, 因为后续的写入和切主操做有从新可以读取到这个6-10 的内容了, 形成了幽灵复现, 致使这个问题的缘由仍是由于没有进行对超时操做的重确认.
回到幽灵复现问题
那么Raft 有没有可能出现这个幽灵复现问题呢?
其实在早期Raft 没有引入新的Leader 须要写入一个包含本身的空的Entry 的时候也同样会出现这个问题
Log Index 4,5 客户端超时未给用户返回, 存在如下日志场景
而后 (a) 节点宕机, 这个时候client 是查询不到 Log entry 4, 5 里面的内容
在(b)或(c) 成为Leader 期间, 没有写入任何内容, 而后(a) 又恢复, 而且又从新选主, 那么就存在一下日志, 这个时候client 再查询就查询到Log entry 4,5 里面的内容了
那么Raft 里面加入了新Leader 必须写入一条当前Term 的Log Entry 就能够解决这个问题, 其实和以前郁白提到的写入一个StartWorking 日志是同样的作法, 因为(b), (c) 有一个Term 3的日志, 就算(a) 节点恢复过来, 也没法成了Leader, 那么后续的读也就不会读到Log Entry 4, 5 里面的内容
那么这个问题的本质是什么呢?
其实这个问题的本质是对于一致性协议在recovery 的不一样作法产生的. 关于一致性协议在不一样阶段的作法能够看这个文章 http://baotiao.github.io/2018/01/02/consensus-recovery/
也就是说对于一个在多副本里面未达成一致的Log entry, 在Recovery 须要如何处理这一部分未达成一致的log entry.
对于这一部分log entry 其实能够是提交, 也能够是不提交, 由于会产生这样的log entry, 必定是以前对于这个client 的请求超时返回了.
常见的Multi-Paxos 在对这一部分日志进行重确认的时候, 默认是将这部分的内容提交的, 也就是经过重确认的过程默认去提交这些内容
而Raft 的实现是默认对这部分的内容是不提交的, 也就是增长了一个当前Term 的空的Entry, 来把以前leader 多余的log 默认不提交了, 幽灵复现里面其实也是经过增长一个空的当前Leader 的Proposal ID 来把以前的Log Entry 默认不提交
因此这个问题只是对于返回超时, 未达成一致的Log entry 的不一样的处理方法形成的.
在默认去提交这些日志的场景, 在写入超时之后读取不到内容, 可是经过recovery 之后又可以读取到这个内容, 就产生了幽灵复现的问题
可是其实之因此会出现幽灵复现的问题是由于在有了一个超时的第三态的请求之后, 在没有处理好这个第三态请求以前, 出现成功和失败都是有可能的.
因此本质是在Multi-Paxos 实现中, 在recovery 阶段, 将未达成一致的Log entry 提交形成的幽灵复现的问题, 本质是没有处理好这个第三态的请求.
一站式开发者服务,海量学习资源0元起!
阿里热门开源项目、机器学习干货、开发者课程/工具、小微项目、移动研发等海量资源;更有开发者福利Kindle、技术图书幸运抽奖,100%中--》https://www.aliyun.com/acts/product-section-2019/developer?utm_content=g_1000047140