知识卡片

用日志解决最终一致性系统的CAS冲突

结构图卡

内容

日志是一组只追加、严格有序的记录序列,它能把一个原本复杂的分布式一致性问题变得简单——Twitter的Manhattan(最终一致性分布式key/value数据库)在这个问题上是一个典型案例。Manhattan由Client、co-ordinator、replica三个组件构成:Client把请求发给co-ordinator,co-ordinator找到对应replica并附上时间戳发起修改,replica根据时间戳决定哪次修改最终生效,以此实现最终一致性。但如果要在这套最终一致性系统上支持Compare-And-Set(CAS)这样的强一致性操作,就会遇到”冲突”——假设两个Client同时想把key x从3改成不同的值(一个改成4,一个改成5),如果左边Client成功把第1个副本改成4、右边Client成功把第3个副本改成5,那么各自去修改另一个副本时都会失败,因为该副本的值已经被对方改掉了。这种冲突的危险之处在于系统无法自行判断x的最终值该是4还是5,也无法从这个冲突状态中自动恢复——一旦请求可以绕过顺序、并发地直接修改多个副本,系统就失去了判断”谁该赢”的依据。解决方法是引入日志把所有请求先序列化:co-ordinator不再直接修改replica,而是把请求写入日志,所有replica按日志的顺序读取请求并依次修改本地状态——由于”3改成4”这个操作先于”3改成5”被写入日志,所有副本会先被改成4,”3改成5”这个操作因为此时值已不是3而自然失败。这条解法揭示的核心原理是:真正引发一致性冲突的根源不是”并发修改”本身,而是”缺少一个所有节点都认可的全局顺序”,日志的价值就在于把这个全局顺序显式地固化下来,让原本可能相互冲突的并发操作变成可以逐一裁决的有序队列——这正是Pub/Sub模式的基础,也是Twitter决定构建一个可被所有内部分布式系统复用的高可用日志服务的直接动机。

结构图

flowchart TD
    A["两个Client并发修改同一个key\nClient1: x 3→4;Client2: x 3→5"] --> B{"是否存在\n全局顺序仲裁"}
    B -->|"否:直接并发修改多副本"| C["冲突:不同副本被改成不同值\n系统无法判断最终值、无法自动恢复"]
    B -->|"是:先写入日志序列化"| D["按日志顺序依次应用到所有副本"]
    D --> E["'3→4'先入日志\n所有副本先被改成4"]
    E --> F["'3→5'再入日志\n因值已非3而失败\n一致性冲突消解"]

参考来源

- 位置:《高可用架构(第1卷)》第1章《高可用架构案例精选》"1.1 Twitter高性能分布式日志系统架构解析"节,"1.1.1 为什么需要分布式日志"(源文件:_epub-src/OEBPS/Text/Chapter1_1_2.xhtml) - 结论依据:原文用两个Client并发修改同一key引发的"冲突"场景说明最终一致性系统上CAS操作的困境,并给出"我们使用日志来序列化所有的请求……在这个例子中,将3修改为4的操作在将3修改为5的操作之前写入日志。因此,所有的副本会首先被修改成4"的解法,直接支撑本卡片结论与结构图。 - 原始内容:这就是之前提到的"冲突"……因为你不知道这个系统中,x的最终值应该是4还是5……系统无法从这个"冲突"状态中恢复……解决办法是什么呢?日志!我们使用日志来序列化所有的请求。