知识卡片

"幽灵复现"现象:乱序确认带来的日志诡异重现,及StartWorking日志的解决方案

结构图卡

内容

Multi-Paxos允许多条日志乱序确认(不要求Leader必须按顺序拿到每条日志的多数派确认才能继续写后续日志),这个宽松性带来了一种被称为”幽灵复现”的诡异现象:第一轮A当选Leader,写下1~10号日志,其中1~5号已形成多数派并回复了客户端,6~10号客户端超时没等到应答;第二轮A宕机,B当选新Leader,B和C此时已知的最大LogID都是5,所以B判断不需要去重新确认6~10号日志,而是直接从6号开始写全新的日志内容——这时如果客户端来查询,是查不到上一轮6~10号日志的(它们看起来”消失”了);第二轮又写入了6~20号新日志,但只有6号和20号真正形成了多数派;第三轮A重新当选Leader,从多数派里查到的最大LogID是20,因此要对7~20号日志重新执行”重确认”,这其中恰好包含了A在第一轮写下、后来”消失”的7~10号日志——一旦这些日志重确认成功,如果客户端再来查询,会发现上次查不到的7~10号日志像幽灵一样重新出现了。这个现象的根源在于:日志的LogID是连续分配的,但”这个LogID到底对应哪一轮Leader写下的哪个具体内容”在乱序确认的机制下是可能发生变化的,同一个LogID位置在不同轮次里可能被不同的内容占据过。解决方案是引入一条StartWorking日志:新任Leader在完成日志重确认、开始写入全新Redo Log之前,先写出一条StartWorking日志,记录当前Leader的EpochID(可以直接用ProposalID的值),此后Leader写的每条日志都要携带当前的EpochID;回放时一旦经过了一条StartWorking日志,后续再遇到EpochID比它更小的日志就直接忽略——这样即使某条日志的内容曾经在乱序确认的过程中发生过变化,回放逻辑也能通过EpochID的比较,准确识别出哪个版本才是真正应该生效的最终内容,不会被”幽灵复现”的旧版本内容干扰。

结构图

sequenceDiagram
    participant A as Leader A(第1、3轮)
    participant B as Leader B(第2轮)
    participant Log as 日志状态(LogID 6~10)

    Note over A: 第1轮:A写1~10号日志<br/>1~5号形成多数派并应答客户端<br/>6~10号超时未应答
    A->>Log: 写入6~10号(未确认)
    Note over B: 第2轮:A宕机,B当选<br/>B/C已知最大LogID=5
    B->>Log: 判断无需重确认6~10<br/>直接覆盖写全新的6~20号
    Note over Log: 客户端此时查不到<br/>原6~10号内容("消失")
    Note over A: 第3轮:A重新当选<br/>多数派最大LogID=20
    A->>Log: 对7~20号执行"重确认"
    Note over Log: 原A写的7~10号内容<br/>竟被重新确认("幽灵复现")
    Note over A: 解法:写StartWorking日志<br/>携带EpochID,回放时<br/>忽略EpochID更小的日志

参考来源

- 位置:《高可用架构(第1卷)》第2章《高可用架构原理与分布式实践》"2.10 架构师需要了解的Paxos原理、历程及实战"节,"2.10.4 Multi Paxos的实际应用"(源文件:_epub-src/OEBPS/Text/Chapter2_10_5.xhtml) - 结论依据:原文完整描述了三轮Leader切换导致的"幽灵复现"过程("第2轮……客户端来查询,是查询不到上一轮6~10号日志内容的……第3轮……7~10日志又像幽灵一样重新出现了"),并说明解决方案"写出一条被称为StartWorking的日志,这条日志记录了当前Leader的EpochID……回放时,经过了一条StartWorking日志之后,再遇到EpochID比它小的日志,就直接忽略掉",共同支撑本卡片结论与结构图。 - 原始内容:第2轮……B和C的最大的LogID都是5,因此B不会去重确认6~10号日志,而是从6开始写新的日志……第3轮……会发现上次查询不到的7~10日志又像幽灵一样重新出现了……需要依赖新任Leader……写出一条被称为StartWorking的日志……再遇到EpochID比它小的日志,就直接忽略掉。