Uber架构教训警示工程界 无主复制与LWW难保核心交易零丢单

[Chinese] Chapter 8: replication part2 — Scott Shi

Scott Shi

无主复制与最后写入者胜机制本质上属于尽力而为架构,物理时钟漂移必然导致数据静默丢失,强行用于支付对账或强状态工作流将引发毁灭性架构灾难。

AI-assisted summary of [Chinese] Chapter 8: replication part2 — original source. Check the original for full context.

Source published:

Key ideas

  • 单调读一致性防止用户在读取时遭遇“时间回溯”,工业界最直接的规避策略是将同一用户的读请求基于哈希固定路由到同一个副本节点。 (5:14)
  • 多主复制(Multi-Leader)虽能显著降低跨国请求网络延迟并提高可用性,但异步冲突检测会导致灾难性的“双花”问题,涉及账户资金的核心链路必须通过路由收敛或强持久化单主数据库进行串行化控制。 (30:00)
  • 无主复制(Leaderless)以高可用为核心导向,采用 Quorum 多数派投票读写并借助反熵和读修复保证最终一致性,但其本质是尽力而为(Best-effort),绝不可用于对一致性和无损要求极高的金融交易系统。 (55:00)
  • 两阶段提交(2PC)与共识算法(Consensus)是正交概念:2PC 解决跨分片/跨节点的原子提交但易因协调者单点故障而阻塞,共识算法则专注在不可靠网络下为事件达成全局全序。 (86:45)

Detailed notes

## Background 该视频由 Scott Shi 于 2020 年 12 月 13 日发布在 YouTube 平台上。视频简介表明这是关于数据复制(Replication)主题的深入探讨,接续前序讨论,配套分享了一份 Google Slides 演示文稿以供参考。

## TL;DR 本期内容围绕《数据密集型应用系统设计》(DDIA)复制机制的后半部分展开,系统剖析了单主、多主(Multi-Leader)与无主(Leaderless / Dynamo 风格)复制架构的设计哲学与工程权衡。讲师深入拆解了单调读、因果一致性、异步写冲突、Quorum 读写、Sloppy Quorum、Hinted Handoff 以及 Anti-Entropy 等核心机制,并结合 Uber Blitz 工单平台与支付选型等工业界真实踩坑案例,警示开发者不可盲目将无主数据库用于强事务场景。在答疑环节,讲师进一步辨析了 2PC 与共识算法(Consensus)、线性一致性与事务隔离性的本质边界,强调在大规模系统中规避技术陷阱的重要性。

## Atmosphere & style 整场技术分享兼具一线大厂资深工程师的实战底气与硬核研讨的亲和感,讲师语言犀利幽默,频频直白调侃业界的反面选型教训(如“敢拿无主系统扛支付的都是勇士”)。讲解风格举重若轻,既能将晦涩的因果偏序与分布式时钟陷阱抽丝剥茧,又能坦率分享自身早年面试挂掉与撰写 50 页 Design Doc 答辩的真实心路。听众互动活跃,现场充满求知解惑的研讨张力与对分布式底层复杂性的敬畏感,让人切身体会到工业界架构决策背后的沉重权衡与洞察乐趣。

## Bulletin - [05:14](https://www.youtube.com/watch?v=vGb7JfpKFro&t=314s) 单调读一致性防止用户在读取时遭遇“时间回溯”,工业界最直接的规避策略是将同一用户的读请求基于哈希固定路由到同一个副本节点。 - [30:00](https://www.youtube.com/watch?v=vGb7JfpKFro&t=1800s) 多主复制(Multi-Leader)虽能显著降低跨国请求网络延迟并提高可用性,但异步冲突检测会导致灾难性的“双花”问题,涉及账户资金的核心链路必须通过路由收敛或强持久化单主数据库进行串行化控制。 - [55:00](https://www.youtube.com/watch?v=vGb7JfpKFro&t=3300s) 无主复制(Leaderless)以高可用为核心导向,采用 Quorum 多数派投票读写并借助反熵和读修复保证最终一致性,但其本质是尽力而为(Best-effort),绝不可用于对一致性和无损要求极高的金融交易系统。 - [01:26:45](https://www.youtube.com/watch?v=vGb7JfpKFro&t=5205s) 两阶段提交(2PC)与共识算法(Consensus)是正交概念:2PC 解决跨分片/跨节点的原子提交但易因协调者单点故障而阻塞,共识算法则专注在不可靠网络下为事件达成全局全序。

## Key takeaways - [00:00](https://www.youtube.com/watch?v=vGb7JfpKFro&t=0s) 系统设计面试应优先用一个具体的端到端(end-to-end)实例跑通系统流程,闭环展现设计决策后再深入特定底层细节。 - [05:14](https://www.youtube.com/watch?v=vGb7JfpKFro&t=314s) 单调读(Monotonic Reads)承诺用户不会看到状态发生时间回溯,常因读请求分散路由到存在复制延迟的各节点而遭到破坏。 - [08:35](https://www.youtube.com/watch?v=vGb7JfpKFro&t=515s) 保障事件因果一致性只需要维护偏序(Partial Order),无需为追求全局绝对全序(Total Order)而承受代价昂贵的共识算法开销。 - [11:47](https://www.youtube.com/watch?v=vGb7JfpKFro&t=707s) 网络延迟是无界的,系统架构设计必须推演最坏情况(Worst-case Scenario),不能仅依赖 P99.9 的理想指标。 - [14:48](https://www.youtube.com/watch?v=vGb7JfpKFro&t=888s) Uber 曾尝试用 Kafka 承载支付业务,因消息中间件无法做到绝对的百分之百无损,涉及资金的核心链路必须由强持久化数据库保障。 - [14:48](https://www.youtube.com/watch?v=vGb7JfpKFro&t=888s) Cadence 强状态工作流引擎错误选用弱一致性的 Cassandra 作为底层存储,导致大量难以调和的 Corner Case 频发。 - [25:00](https://www.youtube.com/watch?v=vGb7JfpKFro&t=1500s) 多主跨数据中心部署的核心动因是降低地理往返网络时延、提升容灾高可用性以及隔离跨机房网络波动。 - [35:00](https://www.youtube.com/watch?v=vGb7JfpKFro&t=2100s) 最后写入者胜(LWW)依赖物理时间戳,由于物理时钟漂移(Clock Drift)不可避免,极易导致最新的写入被静默覆盖造成隐蔽丢数据。 - [40:00](https://www.youtube.com/watch?v=vGb7JfpKFro&t=2400s) CRDT 与 Event Sourcing 采用类似无序集合追加的哲学,将冲突处理从“写时阻塞仲裁”推迟到“读时客户端解释还原”,实现高吞吐写入。 - [01:00:00](https://www.youtube.com/watch?v=vGb7JfpKFro&t=3600s) 无主架构依靠 Quorum 多数派(W + R > N)保证读写重叠,并搭配后台反熵(基于 Merkle Tree)与读修复(Read Repair)收敛至最终一致性。 - [01:05:00](https://www.youtube.com/watch?v=vGb7JfpKFro&t=3900s) 在无主复制中,如果写入节点未达 Quorum 即失败报错,后续读修复可能因缺乏全局事务回滚机制而错误地将未提交脏数据转正。 - [01:10:00](https://www.youtube.com/watch?v=vGb7JfpKFro&t=4200s) Sloppy Quorum(宽松法定人数)与 Hinted Handoff 虽然保障了极致可用性,但在网络分区时会打破 Quorum 交集假设,造成读取端无法读到最新写入。 - [01:20:00](https://www.youtube.com/watch?v=vGb7JfpKFro&t=4800s) Version Vector 用于检测因果依赖与并发冲突,而 Lamport Timestamp 仅用于生成逻辑全序,二者机制与目标截然不同。 - [01:26:45](https://www.youtube.com/watch?v=vGb7JfpKFro&t=5205s) 线性一致性(Linearizability)针对单对象的实时最新性,事务(Transaction)针对跨对象操作的 ACID 边界,两者概念正交互不包含。

## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=vGb7JfpKFro&t=0s) 系统设计面试技巧与后续课程规划

在系统设计面试中,求职者往往很难在 45 分钟到一个小时的有限时间内,把从收集需求到最终数据 Schema 的完整流程毫无遗漏地全部走完。面试官通常会挑选某个特定模块要求深入展开。很多面试官极其看重候选人能否用一个具体例子把整个系统从头到尾(end-to-end)跑通一遍。如果能在开局就把端到端流程清晰展现出来,就像写完代码后跑通关键测试用例一样,这场面试基本上就稳了;后续有多余时间,再去深究具体的 API 设计、数据库选型或底层数据模型。

面试成败的关键还在于双方是否处于同一个技术频道上。如果彼此思维同频,沟通效率会非常高。例如探讨为什么选用 Redis 时,只需简单一句“它的读写效率高很多,支持存储较复杂的数据结构,且当前业务数据不需要强持久性(durability)保证”,双方即可心领神会地快速跳过并进入下一环节。如果候选人与面试官完全不在一个频道上,往往意味着面试走向崩盘。候选人即使面试官没有主动要求,也应主动抛出一个端到端的具体实例贯穿始终,在分析各个组件时把该实例代入进去讨论。

关于后续课程安排,目前的系统设计讨论预计在 8 月底前全部结束,后续转为在群里进行针对特定问题的 Ad-hoc 讨论。在此之前会补充一至两节不同于常规大型分布式架构的面向对象设计(OO Design),例如亚马逊等大厂非常热衷考察的“停车场设计”(Design a Parking Lot)。此外,主讲人计划用大约五周(约 11 次课)的节奏继续啃完《数据密集型应用系统设计》(DDIA)全书。由于后面的章节难度骤升,节奏会适当放缓,必要时将单章拆成两次讲透。

- 面试时间分配策略:核心在于先跑通 end-to-end 流程展示闭环,再根据剩余时间深入 API、数据库设计和底层细节。 - 沟通同频意识:迅速切中关键设计决策的技术权衡(如 Redis 的高性能、丰富结构与无持久性要求),避免无效纠缠。 - 课程推进计划:涵盖 DDIA 核心篇章攻坚、8 月底收尾、穿插亚马逊偏好的 Parking Lot 面向对象设计。

[05:14](https://www.youtube.com/watch?v=vGb7JfpKFro&t=314s) 单调读保证与时间倒流异常

主讲人回顾了上次讨论的单主复制(Single-Leader Replication,由一个 Primary 节点向两个 Secondary 节点同步副本),并明确本节核心任务是彻底搞定多主复制(Multi-Leader Replication)和无主复制(Leaderless Replication)。随后重点探讨了单调读(Monotonic Reads)一致性保证。单调读的核心承诺是:如果用户多次读取数据,绝不会看到系统状态发生“时间回溯”(time goes backwards),即不能先看到较新的版本,随后刷新又回滚到旧版本。

```mermaid flowchart TD accTitle: 单调读一致性缺失导致的时间倒流示意图 accDescr: 用户写入评论后向两个从节点同步,由于网络延迟差异,读取路由到不同节点引发时间回溯。

subgraph Write["写入阶段"] U1["用户写入"] -->|"insert 'sounds good'"| L["主库 Leader"] end

subgraph Replication["主从复制差异"] L -->|"t1 已同步"| F1["从库 Follower 1 (有新数据)"] L -.->|"t2 尚未同步 (Lag)"| F2["从库 Follower 2 (旧数据)"] end

subgraph Read["用户连续读取"] U2["用户读取 1"] -->|"路由到 Follower 1"| F1 U2 -.->|"看到新评论 'sounds good'"| R1["结果:正常"] U3["用户刷新 2"] -->|"路由到 Follower 2"| F2 U3 -.->|"评论离奇消失"| R2["异常:时间倒流 (Time Backwards)"] end ```

出现违背单调读的根本原因在于客户端将读取请求分散发往了存在复制延迟(Replication Lag)的不同副本节点。例如用户发表了一条评论“sounds good”,主节点向两个从节点复制。在时间点 1,数据已同步至 Follower 1,但尚未到达 Follower 2。此时用户第一次从 Follower 1 读取,看到了刚发的评论;紧接着刷新页面,请求被路由到了 Follower 2,刚才的评论瞬间消失。这种体验极差,就像在社交网络上看特朗普发了一条推文,刷新一下就没了,用户完全无法分清究竟是博主自主删除了动态,还是系统内部副本不一致导致的时间回溯。

保证单调读最直接有效的方式,是确保同一用户的每次读取操作都路由到同一个副本节点(Same Replica)。然而这也引出了工程上的妥协:如果绑定的这台从库挂掉了该怎么办?此时请求不得不故障转移到其他副本,在极端的时序缝隙下确实可能再次打破单调读假设。但在分布式系统设计中,这类非涉钱业务(如浏览评论)面对几千万分之一甚至上亿分之一的边缘容灾异常,通常无需过度设计;除非涉及转账等严肃金融交易,或是面试官强行要求极限兜底。此外,在企业内部数据中心环境里,所有节点都默认出于善意工作,完全不考虑代价极其昂贵且无法平衡成本效益的拜占庭错误(Byzantine Failure)。

- 单调读定义:防止出现读操作回退到历史版本的时间倒流(Time Travel Backwards)异常。 - 根因与规避:读请求发往延迟各异的不同副本所致;最基础的解决方案是按用户 ID Hash 固定路由到同一个副本读取。 - 系统设计的取舍原则:常规业务无需为千万分之一级别的故障转移时序异常过度消耗架构成本,且私有机房内直接排除拜占庭恶意节点假设。

[08:35](https://www.youtube.com/watch?v=vGb7JfpKFro&t=515s) 因果一致性与偏序关系

单调读被打破的深层次危害在于它会连带破坏因果关系保证(Causality Assumption)。因果性要求有因果关联的事件必须按照先后顺序呈现。主讲人举了一个经典时序错乱场景:Alice 提问“你能预知未来多久?”,Bob 听到问题后回答“大概能看到未来 10 秒”。如果这两个问答分别写入不同分区的 Leader 节点,在向观察者副本复制时,包含回答的 Partition 2 同步极快,而包含提问的 Partition 1 网络阻塞,外部观察者就会惊悚地发现“答复出现在提问之前”,因果逻辑被完全颠覆。

因果保证在后续共识算法(Consensus)章节中将展开深入剖析。如果系统要求所有事件拥有全局绝对的绝对顺序,就必须实现全序(Total Order),而全序依赖全节点共识,执行代价高昂、吞吐效率极低。但如果系统仅仅是为了守护因果性,允许互不相干的并发写操作并存,那么只需要维护偏序(Partial Order)即可。

主讲人在此分享了一段自己的面试挫败经历:早在 2013 至 2014 年面试 Twitter 时,面试官当场出了一道关于偏序(Partial Order)的算法设计题,当时由于没能吃透原理导致答题失利,甚至挂掉面试后苦思冥想了数月之久才彻底搞明白考点。很多技术难点往往需要在工程中沉淀数年才能顿悟,但只要最终想通了就为时不晚。工业界系统设计与算法面试刷多了就会发现本质上全是固定套路,面试表现往往与日常搬砖工作并不直接等同,经过针对性准备和认知升级后完全可以实现极高通过率。

- 因果一致性核心:防止由于跨分区同步延迟导致的“跨时空倒错”(如回答先于提问显示)。 - 偏序 vs 全序:保障因果关系只需要保留并发状态下的偏序(Partial Order),无需为追求全序(Total Order)而承受昂贵的全局共识开销。 - 面试经验谈:Twitter 早期即深入考察过偏序在系统排序中的应用,面试本质是对核心套路与底层抽象的认知映射。

[11:47](https://www.youtube.com/watch?v=vGb7JfpKFro&t=707s) 复制延迟与“最坏情况”的工程考量

分布式系统中的复制滞后(Replication Lag)是破坏单调读与因果性的共同元凶。由于在最终一致性(Eventually Consistent)系统里,所有跨节点同步全部依赖网络,而真实网络通信是无界的(Unbounded),任何延迟上限都无法得到刚性保证。优秀的系统工程师在设计之初就必须对最坏情况(Worst-Case Scenario)做好推演:在日常运行中,P99 或 P99.9 的同步延迟可能均低于一秒,然而一旦碰上那千分之一的极端抖动,延迟拉长到数分钟甚至数小时,系统是否会出现灾难性后果?

主讲人直言,很多技术大厂在线上实际运行的系统设计远非完美,即便身为分布式系统鼻祖的 Google 也不例外。例如早期 Google 的 HBase 实现中由于缺乏防护令牌(Fencing Token),导致两个节点同时认为自己拥有写权限,疯狂写入后直接把数据彻底写坏(Data Corrupted)。主讲人在早期使用 Google Hangouts 时,也频繁遭遇过极其严重的丢消息和延迟投递故障——自己发出的消息或是好友发送的内容,动辄在数小时之后才突然送达。哪怕是巨头打造的系统,在面对极端网络延迟与并发场景时,边界处理也经常翻车。

- 网络延迟无界性:不可依赖 P99.9 表现,必须严谨评估一旦延迟扩大至数小时系统是否会引发灾难性后果(Disastrous Result)。 - Google 历史踩坑案例:HBase 写入缺乏 Fencing Token 导致脑裂损坏数据;Google Hangouts 极端延迟数小时投递消息。 - 架构防御哲学:批判性思维(Critical Thinking)必须强于随波逐流的盲从,边界情况的兜底能力直接决定了系统的生命力。

[14:48](https://www.youtube.com/watch?v=vGb7JfpKFro&t=888s) 工业界反面教材:Kafka 跑支付与 Cassandra 承载 Cadence

除了 Google,Uber 在架构选型上也踩过令人啼笑皆非的“大坑”。主讲人提到,Uber 曾一度匪夷所思地尝试用 Kafka 来承载支付交易系统(Payment Service),这让当时团队内部懂分布式理论的人大为震惊。即便后续引入了所谓的“无损 Kafka”(Lossless Kafka),其消息丢失率或许从常规的万分之一、百万分之一降到了千万甚至十亿分之一,但它在本质上依然无法提供百分之百的无损保证。涉及资金流转的核心链路,底线原则是必须依赖经过多副本强持久化保证的关系型数据库作为可靠队列,而非拿消息中间件去赌微小丢单概率。

```mermaid flowchart LR accTitle: 核心支付与工作流存储选型对比 accDescr: 对比 Kafka 与 RDBMS 在支付场景的表现,以及 Cassandra 在强状态工作流引擎中的选型冲突。

subgraph Payment["支付链路选型"] direction TB K["Kafka / Lossless Kafka"] -.->|"存在十亿分之一丢失率"| BadP["不可接受:资金对账异常"] DB["RDBMS 多副本持久化"] -->|"ACID 强可靠性"| GoodP["正确方案:绝对零丢失"] end

subgraph Cadence["Cadence 工作流选型冲突"] direction TB C["Cadence 引擎核心"] -->|"强要求: Durability + State-Machine"| Need["强状态保障"] Need -.->|"底层强行依赖"| Cass["Cassandra 数据库"] Cass -.->|"弱一致性 / Best-effort 冲突"| Bug["恶梦:Corner Case 频发、Rollout 故障"] end ```

另一个更具代表性的架构选型事故发生在 Uber 的编排引擎 Cadence(现开源流行版本为 Temporal)上。Cadence 是一个强持久化(Guaranteed Durability)、重状态(Stateful)的有状态机工作流系统,其底层存储却出人意料地选用了以“尽力而为”(Best-effort)、弱一致性著称的 NoSQL 数据库 Cassandra。这个决定的源头在于 Cadence 的主导设计者 Maxim Fateev(原亚马逊 Simple Workflow Service 的创始工程师,拥有近 30 年深厚资历,后离职创立 Temporal)。当时由于权威背书,团队无人提出异议。然而在后续真实生产运行中,强状态引擎与弱一致存储碰撞出无数难以调和的边缘角标异常(Corner Cases),导致运维与维护工作成为工程师的噩梦,数个业务项目在上线推广时接连遭遇重大失败。

后端工程师在职业生涯中 90% 的时间都在与数据库打交道。修改数据模型(Data Model)引发的数据迁移已经足够伤筋动骨,而一旦在底层数据库选型上犯下方向性错误,后果无异于“把整套系统推倒重新投胎”。对于业务广泛的核心基础设施,工程师必须时刻保持批判性质疑,不可迷信技术权威,基础核心概念与容灾底线必须极其扎实。

- 支付场景架构禁忌:Kafka 无论如何调优仍存在极低概率的丢失,资金类核心链路必须依赖强持久化数据库配合多副本同步。 - 架构选型错配教训:Cadence 要求强一致与强状态,却错误搭载 Cassandra,导致边界 Corner Case 难以收敛,造成线上 rollout 多次翻车。 - 数据库变更代价:数据模型改动属于“伤筋动骨”,底层数据库选型错误则是导致系统面临彻底推倒重构的毁灭性打击。

[18:37](https://www.youtube.com/watch?v=vGb7JfpKFro&t=1117s) Cadence 与 Piper 的定位差异及事务抽象

在答疑环节,针对学员对 Amazon Simple Workflow Service(SWF)以及 Uber 内部工具关系的疑问,主讲人做了清晰的架构解构。Amazon SWF 允许开发者先定义一堆具体的 Task(无论是计算、存储还是具体操作),再将这些任务编排成一个有向无环图(DAG),由引擎从头到尾严格保证该工作流有状态机式地完整执行。这非常适合复杂的长事务业务场景,例如外卖平台 Uber Eats 的点餐订单处理——下单成功后触发生成账单、周期性扣款以及信用卡鉴权扣款等跨阶段动作,Cadence 在 Uber 内部正是承担这套核心 DAG 工作流的底座。

群里有人询问 Cadence 与 Piper 的关系,主讲人澄清 Piper 并非 Cadence 的替代品,而是 Uber 内部基于开源 Apache Airflow 二次开发并深度定制的数据处理工具。Cadence 偏向系统级底层框架,理论上完全可以在 Cadence 之上重写一个 Piper。二者应用场景截然不同:Piper 核心用于处理脱离用户即时交互的批处理任务(Batch Job),例如每天定时批量扫描逾期未还款的 Uber 欠费用户并做催缴,或是每天按批次向特定人群分发促销邮件(Promotion Email);此外还用于海量分析型离线流水线(Analytical Data Pipeline),将数据从交易型在线数据库抽取清洗至分析型数仓。

回到系统抽象层面,对于应用层开发人员(Application Developer)而言,最理想的境界是完全感知不到数据复制(Replication)的复杂性。底层的多副本同步应当像单机单一副本一样清爽直观——开发人员面对数据库只有存(Write)和取(Read),刚写入的数据读出来必然保持最新,而不需要业务层去痛苦对抗并发与复制延迟。为了实现这种绝佳的封装抽象,数据库领域才孕育出了“事务”(Transaction)机制,由数据库自身去屏蔽并发控制与持久性的泥潭。

主讲人最后指出,业务事务(Business Transaction)与数据库事务(Database Transaction)存在本质区别。业务层面的“订票全流程”往往跨越多个阶段,而现代工程设计极力避免将业务长事务直接做成数据库的交互式长事务(Interactive Transaction)。现代最佳实践倡导请求必须是自包含的:一个请求过来立即开启一个独立的数据库事务,快速执行并 Commit 返回,严禁长时间占用开放式事务。在分布式系统演进中,围绕“单节点事务(Single Node Transaction)是否因成本过高而在分布式环境下失去生存空间”的争论从未停止,这构成了理解现代分布式存储演化的核心分水岭。

- 工作流系统(Cadence / Temporal / AWS SWF):以 DAG 有向无环图和状态机为核心,保证跨服务长链路(如 Uber Eats 下单到支付闭环)从起点到终点强持久化执行完毕。 - 批处理系统(Piper / Apache Airflow):基于 DAG 调度离线批处理与分析任务,专注异步非实时场景(如定时发促销邮件、欠款对账与数仓 ETL)。 - 事务抽象与交互演进:事务的核心目标是为业务研发屏蔽并发控制与底层复制脏细节;工程架构已从历史上的长周期“交互式业务大事务”彻底转向现代的“单一请求自包含快提交”模型。

[25:00](https://www.youtube.com/watch?v=vGb7JfpKFro&t=1500s) 单节点到多节点复制:为何现代架构偏爱 Multi-Leader

在分布式系统架构中,单节点事务(single-node transaction)依然占据核心地位且不可替代,而跨节点事务(cross-node transaction)虽然在某些一致性要求极高的场景下必不可少,但两者在工程实践中权衡各异。随着单 Leader 复制(Single-leader replication)讨论的收尾,系统设计的重点转向了更贴合大规模工业级落地的多主复制(Multi-leader replication,或称 Multi-node replication)。目前无论是在 Facebook 还是 Uber,多 Leader 跨数据中心部署都是标配架构。

驱动系统采用多数据中心 Multi-leader 架构的核心动力主要体现在以下几个维度: 1. **性能与网络延迟(Latency)**:用户读写请求路由到地理上距离最近的数据中心。如果一个在中国的用户的所有写请求必须穿透公网打到美国数据中心,往返时延(RTT)随随便便就会增加 200 毫秒,整体响应性能会变得极差。 2. **容灾高可用(Datacenter Outage)**:单一数据中心发生灾害或宕机时,流量可以无缝切至另一个数据中心继续承接写入。 3. **应对网络波动与分区(Network Partition)**:跨地域网络的不稳定性容易引发各种诡异异常。不论在 Raft 还是 Paxos 这类共识算法中,最糟糕的极端边界情况(worst-case scenarios)往往都是由网络抖动诱发的——简单的网络故障可能直接让系统难以收敛达成共识,甚至大幅拉低恢复速度。因此,拥抱多数据中心已成为业界应对网络常态化故障的普遍共识。

```mermaid flowchart TD accTitle: Multi-Leader Multi-Datacenter Replication Flow accDescr: 跨数据中心写入与复制的四层拓扑结构图,展示本地复制与跨数据中心复制及冲突解决流程。

subgraph DC1["Data Center 1 (Primary DC)"] L1["Leader 1<br>(Source of Truth)"] F1["Follower 1"] F2["Follower 2"] L1 -->|"In-DC sync/async"| F1 L1 -->|"In-DC sync/async"| F2 end

subgraph DC2["Data Center 2 (Secondary DC)"] L2["Leader 2"] CR["Conflict Resolution"] F3["Follower 1"] F4["Follower 2"] L2 --> CR CR -->|"Push resolved data"| F3 CR -->|"Push resolved data"| F4 end

Client["User Write Request"] -->|"Low Latency"| L1 L1 -.->|"Cross-DC Async Replication<br>(High Latency: 50-200ms)"| L2 ```

从数据流拓扑剖析,Multi-leader 复制实际上形成了一个精细的四层结构(在某些场景下甚至演变成两套不同版本的数据源和多个延迟副本): - **第一层(Primary Leader)**:当前接收写入请求的数据中心 Leader,是该事务的绝对真理来源(Source of Truth)。 - **第二层(Local Followers)**:同机房内的副本节点。由于同数据中心内复制网络稳定、时延可控,当 Primary Leader 宕机时,这些 Follower 几乎掌握着最新数据。 - **第三层(Cross-DC Remote Leader)**:数据跨越数千英里、承受几十甚至上两百毫秒网络往返后到达远端数据中心 Leader,在此层极易与本地并发产生的改动发生写入冲突(Conflict),必须进行冲突解决(Resolve Conflict)。 - **第四层(Remote Followers)**:远端机房在冲突裁决完成后,最终将一致性数据推送到本地的 Follower 节点上。

- 这种架构意味着在实际生产环境(例如 Facebook)中,同一个逻辑数据对象在某一时间断面可能存在 6 份物理拷贝:Primary Leader 一份、本地两个 Follower 各一份、Secondary Leader 一份、远端两个 Follower 各一份;在复制完成前,这 6 份数据状态可能截然不同。

---

[30:00](https://www.youtube.com/watch?v=vGb7JfpKFro&t=1800s) 异步冲突检测与并发写入的“死局”

业界现有的事务型数据库生态对 Multi-leader 已经有现成工具支持,例如 MySQL 社区常借由外部工具 Tungsten Replicator、PostgreSQL 的 BDR(Bi-Directional Replication),以及针对 Oracle 数据库的 GoldenGate。多主写入的典型场景不仅包括离线协作(Offline-ended operations),还包括多人在线协同编辑(如 Google Docs)。但多主复制最棘手、最本质的核心难题永远是**冲突解决(Conflict Resolution)**。

以简单的页面修改为例:User 1 和 User 2 同时修改初始标题为 `A` 的文档。User 1 在数据中心 1 将标题改为 `B`;User 2 在数据中心 2 将标题改为 `C`。写入各自本地 Leader 时皆大欢喜,没有任何并发锁竞争,本地均返回成功。然而,当变更跨机房异步复制时: - Leader 1 收到的变更意图是将旧值 `A` 改为 `C`,可此时本地值早已被改成了 `B`; - Leader 2 收到的变更意图是将旧值 `A` 改为 `B`,而本地当前值却是 `C`。

如果只是字符串标题覆盖还算轻微,若将其置换为**金融账户转账场景**,就会演变为灾难性的资金超付问题: 假设公司账户总余额只有 1500 万美元。User 1 从该账户划走 1000 万,本地扣减成功,返回 OK;User 2 几乎同时在另一数据中心也申请划走 1000 万,其扣减也是基于“账户尚有 1500 万”的前提(Assumption),同样在本地成功扣减并返回 OK。当两笔交易进行跨数据中心异步复制时,假设彻底崩塌——账户总额根本不足以支撑 2000 万的扣款需求。由于系统在各自机房早就向客户承诺“扣款成功”,此时已经产生了外部业务承诺(例如用户已经根据到账信息向下游设备厂商下单打款),强行回滚(Revoke)将引发一系列业务合规与信任危机。这种因为**异步冲突检测(Asynchronous Conflict Detection)**带来的“双重支付”(Double Spending)直接将系统逼入了死局。

---

[35:00](https://www.youtube.com/watch?v=vGb7JfpKFro&t=2100s) 规避冲突、强事务控制与 Last Write Wins 的时钟陷阱

面对双花(Double Spending)这类严苛场景,单节点并发控制依赖锁机制让事务串行化,正如工程老前辈常提起的经验之谈:"当你有很多 transaction 的时候不是问题,当你有很多 transaction compete 同一个 object 的时候也不是问题。一定会有一个 winner,winner will take everything,剩下的人多半都是 loser,loser 就得 stand back / stay back,等下一次 retry。" 但跨机房的异步多主完全无法套用这个逻辑。

工程上要彻底规避这一死局,针对强事务要求主要有两条路径: 1. **冲突规避(Conflict Avoidance)**:将涉及同一资源或账户的所有写请求,通过路由层强行收敛到唯一指定的 Leader 节点(例如全部路由给 Leader 1)。只要写请求收敛到单节点,本质上就退化成了单节点并发控制,永远不会出现跨机房并发双花。当发生机房级切换(Failover)时,必须严格确保前一个机房的所有在途事务完全持久化(Committed),才能放行新机房承接写流量。 2. **两阶段提交(Two-Phase Commit, 2PC)跨机房同步事务**:在 Leader 1 发起事务的同时,在 Leader 2 同步开启分布式事务协同。然而,跨洲际网络的 2PC 性能极其低下、延迟极高,在追求吞吐的高并发分布式系统中一般直接被排除在架构方案之外。

| 策略类型 | 核心机制 | 致命隐患 / 架构代价 | |:--- |:--- |:--- | | **路由收敛(Conflict Avoidance)** | 针对特定 Object 的写流量强行锁定路由到单一 Leader | 失去跨机房写入的高可用灵活性,Failover 时需精细处理边界 | | **最后写入者胜(Last Write Wins, LWW)** | 依赖各节点时间戳,后发生的写入覆盖先发生的写入 | 物理时钟偏移(Clock Drift)导致隐蔽的数据丢失(Data Loss) | | **节点 ID 裁决(Replica ID Ordering)** | 给每个 Replica 赋予固定全局权重 ID,高 ID 强行覆盖低 ID | 业务时序完全失真,较早发生的写请求极易被无情覆写 | | **CRDT(无冲突复制数据类型)** | 放弃原地修改,采用类似无序集合(Unordered Set)累加变更 | 存储膨胀,需要客户端或读路径承担复杂的解析与状态还原成本 |

针对非强事务(弱一致性)场景,系统允许中间状态暂态不一致,只需最终收敛一致(Eventually consistent)。业界常见的做法之一是 **Last Write Wins (LWW)**。但 LWW 背后潜藏着极其危险的时钟陷阱:物理硬件的时钟偏移(Clock Drift / Clock Skew)在所难免。如果 Node A 的时钟比 Node B 慢整整两分钟,一个现实中先写到 B、后写到 A 的请求,在用时间戳对比时,A 写入的时间戳反而更早。结果导致 A 的最新写入被 B 的旧数据无情覆盖。从最终用户的黑盒观察视角来看,系统出现了极其诡异的 Bug——自己先写入了 `1`,随后更新为 `2`,但最终读出的数据竟然倒退回了 `1`。像 Cassandra 这类底层重度依赖 LWW 的系统,天然就不保证严格读到现实时序的最新写入。

---

[40:00](https://www.youtube.com/watch?v=vGb7JfpKFro&t=2400s) CRDT 思想与 Uber Blitz 平台的工程实践

除了时间戳仲裁与 Replica 权重覆写之外,近年来(特别是 2014 年以后)学术界与工业界高度关注 **CRDT(Conflict-free Replicated Data Types)** 模型。CRDT 的设计哲学在概念上非常类似于数学上的无序集合(Unordered Set)。系统并不在写入时强求单一事实标准的原地状态更新,而是将写入操作全部视为集合元素的追加——无论机房节点是先追加 `+1` 还是追加 `+2`,两个状态记录均在底层无冲突共存,至于业务层面如何理解、还原这套复合状态,则交由上层业务逻辑裁决。

```mermaid flowchart LR accTitle: Uber Blitz Platform Append-Only CRDT Model accDescr: 展现 Uber 客户支持工单系统 Blitz 将交互行为转为无冲突追加式写操作的架构逻辑。

subgraph Ingestion["Write Path (No Conflict)"] A1["Action: User submits text<br>(Key: UUID-1)"] --> Map A2["Action: Agent replies<br>(Key: UUID-2)"] --> Map A3["Action: Mike views ticket<br>(Key: UUID-3)"] --> Map Map["Ticket Entity<br>(Unordered Action Hash Map)"] end

subgraph Serving["Read Path (Client Resolution)"] Map -->|"Fetch full actions map"| Client["Client / Front-end Engine"] Client -->|"Reconstruct state & thread view"| Display["Render complete ticket UI"] end ```

这种方案在 **Uber 的客服支持核心服务平台 Blitz** 中得到了极具代表性的实践: - **无锁无冲突的写入流**:在 Blitz 平台中,每个客服工单(Ticket)本质上被建模为一个巨大的无序哈希表(Unordered HashMap)。每次工单生命周期中的事件(Action)——比如用户给客服留言、客服跟进回复、甚至外部协作者(例如员工 Mike)打开工单看了一眼——都会被系统分配一个全局唯一的随机 Key(UUID),并作为一条独立记录塞入该工单的无序哈希表中。 - **天然免疫冲突**:由于每次写操作都是生成唯一的 UUID 执行无状态追加,来自数据中心 1 还是数据中心 2 的并发写请求彼此绝不会发生 Key 碰撞,底层的写入甚至根本不需要处理冲突检测,全部原封不动照单全收。 - **读路径上的解释权下放**:当用户或客服界面需要调取并阅读该工单详情时,系统直接将该工单名下哈希表包含的所有交互动作记录全量返回给客户端;由客户端的业务渲染引擎(Client responsibility)负责按照业务逻辑去排序、组装并还原出整个工单的交互视图。这种将冲突解决从“写时阻塞仲裁”移至“读时合并展示”的模式,构成了高吞吐协同系统极佳的解题范式。

[45:00](https://www.youtube.com/watch?v=vGb7JfpKFro&t=2700s) 冲突解决的权衡与 Event Sourcing 思想

在探讨复制冲突的责任归属时,讲者抛出一个核心分歧:**解决冲突的责任到底在数据库,还是在应用层客户端?** 站在客户端读者的视角,从数据库读取出来的数据可能包含大量历史状态和未决页面,由应用来决定如何可视化这些数据——比如先发生了操作 A,接着是操作 B,最后是操作 C——这属于应用层的业务逻辑,而非底层数据库的硬性责任。这种设计哲学各有利弊:好处在于完全避免了数据丢失,客户端发起的事务不会在数据库底层直接硬碰硬产生写冲突,主数据中心 1 和主数据中心 2 都可以各自持久化事件;坏处则是应用层开发者的负担被成倍放大。例如在订票、工单等应用场景中,业务端需要写大量繁复的代码来判断谁先看到了票、如何展示最终状态以及如何解决逻辑竞态。数据库基础设施开发者省心了,只需按顺序存一个无序/有序的哈希表或日志即可,而应用端开发者却要面对极为复杂的业务重构。

```mermaid flowchart TD accTitle: Event Sourcing 与快照机制 accDescr: 展示全量事件日志如何通过计算得出当前状态,以及定期生成 Snapshot 以降低计算开销的过程。

subgraph Log["事件溯源全量历史 (Event Sourcing Log)"] E1["事件 1: 创建工单"] E2["事件 2: 分配工单"] E3["事件 3: 更新状态"] E4["事件 4: 关闭工单"] E1 --> E2 --> E3 --> E4 end

subgraph Calc["状态物化 (Materialization)"] direction TB SNAP["定期快照 (Snapshot)"] CURR["当前最终状态 (Current State)"] E2 -.-> SNAP SNAP --> CURR E3 & E4 --> CURR end ```

此时与会者 Lu Sun 在讨论区提及了“事件溯源(Event Sourcing)”。有听众联想到比特币(Bitcoin)的设计原理,即把所有交易记录在区块链这个公开账本中,计算当前余额时需把账户所有的历史交易打包对账累加。讲者澄清,Event Sourcing(约 2005 年提出)与比特币(2009 年出现)在概念上虽有时间跨度与本质区别,但在“保留不可变完整历史(Full History)而非仅存快照”的思路上是相通的。这与 Git 的版本控制思想也很相似:Git 记录 Commit 历史,并允许通过提交、合并与 Squash 打出版本树。然而,纯粹依赖全量历史进行计算在工业界也有现实短板:当数据量达到一定规模时,若应用每次都要把一整年的历史事件重新推演一遍,性能将极其低下。因此,系统必须在项目运行中引入定期快照(Snapshot),通过“快照 + 增量事件”来实现高效状态复原。

- **写时解决(Resolve on Write)**:在数据实际写入持久化存储之前拦截并解决冲突,若检测到冲突则执行特定的合并函数或直接抛出异常。 - **读时解决(Resolve on Read)**:写入时无脑追加或记录所有版本(如多版本有序映射),等到客户端读取数据时再交由应用逻辑完成冲突仲裁(关系型数据库多要求写时即确定具体值)。

---

[50:00](https://www.youtube.com/watch?v=vGb7JfpKFro&t=3000s) 自动冲突解决与多主拓扑结构(Multi-Leader Topologies)

除了应用层手动介入,学术界与工业界也提出了多种自动解决冲突的算法机制。讲者特别提到了 CRDT(Conflict-Free Replicated Data Types,无冲突复制数据类型)。CRDT 内部支持更为丰富的数据结构抽象,例如无序集合(Unordered List/Set)、有序列表等,通过数学上的交换律与结合律保证各节点无论以何种顺序接收到变更,最终都能收敛到一致的状态。不过讲者指出,这类自动冲突合并算法在当时大多还处于较为前沿甚至偏学术的探索阶段。目前 CRDT 在分布式键值存储 Riak(音频中口误提及 Reddit/React/Rack)等系统中有较为知名的应用;而 Redis 由于采用单线程串行化执行命令,原生并不需要此类机制。此外,Git 在协同版本控制中采用的三方合并(3-Way Merge)函数,本质上也属于一种自动冲突解决的实现分支。

在多主架构(Multi-Leader)中,不同数据中心或节点之间的网络互联方式称为“拓扑结构(Replication Topology)”。常见的拓扑形态包括环形拓扑(Circular)、星形拓扑(Star)以及全互联拓扑(All-to-All):

| 拓扑类型 | 架构特征 | 优势 | 潜在劣势与挑战 | |:--- |:--- |:--- |:--- | | **环形拓扑 (Circular)** | 节点首尾相连成环,复制数据按固定单向流转。 | 结构清晰,每个节点只需与相邻节点通信。 | 复制链路长,末端节点同步最滞后;单点通信中断会破坏整条复制环路。 | | **星形拓扑 (Star)** | 存在一个根/中心节点,其余边缘节点均与其直接通信。 | 中心节点数据通常最全面、更新最及时(Close to up-to-date)。 | 中心节点成为网络瓶颈与潜在单点故障源。 | | **全互联拓扑 (All-to-All)** | 每一个主节点都向所有其他主节点直接广播复制请求。 | 容灾容错性极高,节点间复制延迟短,无中间跳步。 | 出向网络流量(Outgoing Traffic)极为繁重;存在复杂的消息乱序到达问题。 |

讲者结合企业实际架构举例:像 Uber 只有两个主要数据中心,它们之间的拓扑就是一条简单的直连链路,既可看作全互联,也可看作退化的星形或环形,架构非常轻量;但像 Amazon 这类跨越多个大陆、拥有数十个数据中心的巨头,其多主拓扑的设计与流量控制就会极为复杂。

```mermaid flowchart LR accTitle: 三种常见多主拓扑结构 accDescr: 环形拓扑为单向成环流动,星形拓扑以中央节点为枢纽,全互联拓扑各节点两两互联。

subgraph Circular["环形拓扑 (Circular)"] direction LR C1["Node 1"] --> C2["Node 2"] C2 --> C3["Node 3"] C3 --> C1 end

subgraph Star["星形拓扑 (Star)"] direction TB S_CTR["Center Node"] S1["Node A"] --- S_CTR S2["Node B"] --- S_CTR S3["Node C"] --- S_CTR end

subgraph AllToAll["全互联拓扑 (All-to-All)"] direction TB A1["Node X"] <--> A2["Node Y"] A2 <--> A3["Node Z"] A3 <--> A1 end ```

在全互联等多主架构中,另一个典型的隐患是因网络传输延迟不一导致“因果依赖倒置(Causality Violation)”。例如,主节点 1 写入数据 A 并向外复制,随后主节点 2 基于 A 写入了后续操作 B;但由于网络链路波动,第三个主节点可能先收到了 B 后收到了 A,导致本地回放顺序彻底错乱。针对这种并发与因果顺序冲突,理论上可以通过“版本向量(Version Vectors)”进行检测与追踪,但其实现逻辑极其繁杂,在实际工程应用中极具挑战。

---

[55:00](https://www.youtube.com/watch?v=vGb7JfpKFro&t=3300s) 无主复制(Leaderless Replication)与 Dynamo 架构哲学

讲者从 Single-Leader 和 Multi-Leader 的讨论自然过渡到无主复制(Leaderless Replication)。讲者首先分享了自己在系统设计(System Design)实践与工作面试中的个人技术选型哲学:无论业务场景多么眼花缭乱,底层基础组件的考量通常可以收敛为核心的“四到五类数据库加两套流处理工具”: 1. **内存数据库(In-Memory Databases)**:Redis、Memcached,负责高速缓存与暂态读写; 2. **无主复制/类 Dynamo 数据库(Leaderless / Dynamo-like Databases)**:如 Cassandra、DynamoDB、Riak,用于高并发写入与极高可用性场景; 3. **关系型/分片 SQL 数据库(Sharded / Traditional SQL)**:保障 ACID 事务与结构化数据; 4. **列式/数仓分析数据库(Column-Based / OLAP Databases)**:如 Redshift,用于海量数据的离线与实时聚合分析; 5. **流计算与事件流管道**:Kafka 与 Apache Flink。

讲者强调,很多看似繁杂的系统设计方案,核心就是把这几类积木搭好,足以应对 90% 以上的工程场景。

随后,讲者重点拆解了源自经典 Amazon Dynamo 论文的无主复制模型。在无主架构中,集群完全摒弃了主节点的概念,系统的“正确性/真实状态”由多数派(Majority / Quorum)投票决定。

```mermaid flowchart TD accTitle: 无主复制法定人数机制 (Quorum Write) accDescr: 客户端向三个副本并发写,副本1和副本2成功,副本3离线,因达成多数派写操作成功返回。

Client["写客户端 (Client)"] subgraph Nodes["数据副本集群 (Replicas, N=3)"] R1["Replica 1 (正常)"] R2["Replica 2 (正常)"] R3["Replica 3 (宕机离线)"] end

Client -->|"Write"| R1 Client -->|"Write"| R2 Client -.->|"Write (超时/失败)"| R3 R1 -->|"ACK 成功"| Client R2 -->|"ACK 成功"| Client

Result["法定写入成功: W=2 >= 多数派"] Client --> Result ```

- **高可用与无单点故障(No SPOF)**:在传统单主复制体系中,若主节点(Leader)宕机,在通过共识算法(Consensus Algorithm)选举出新主之前,整个系统的写服务将陷入不可用;而在无主复制中,单个节点的宕机离线对整体读写几乎无感。例如副本因子 $N=3$ 时,客户端并发向副本 1、2、3 发起写入,即便副本 3 挂掉,只要多数派(副本 1 与副本 2)确认写入成功,整个写操作即可判定成功返回。 - **并发冲突裁决机制**:多副本写入可能产生不同版本,系统可以通过最后写入者胜(Last Write Wins, LWW,依赖时间戳,但易受物理时钟漂移 Clock Drift 影响)或逻辑版本号来进行确定性冲突判定。 - **最终一致性(Eventual Consistency)与修复**:由于允许部分副本在写入时掉线,副本间的数据在某一时刻必然存在偏差(例如副本 3 重启后缺失了刚刚的写入)。系统为了达到最终一致性,必须引入反熵修复机制(如 Anti-Entropy 后台对齐)与读修复(Read Repair,当客户端同时读取多个副本发现版本不一致时,主动将最新版本写回落后的副本)。

[01:00:00](https://www.youtube.com/watch?v=vGb7JfpKFro&t=3600s) 副本修复机制:Read Repair 与 Anti-Entropy Process

在无主复制(Leaderless Replication)架构中,客户端并发读取多个副本时可能会发现版本不一致。例如读到两个 version 7 和一个 version 6,客户端就能明确知道 version 6 已经过时(outdated),随即主动通知该节点将其更新至 version 7,这种在读取路径上触发的纠错机制称为“读修复”(Read Repair)。除了 Read Repair,系统通常还会运行一个后台机制——“反熵过程”(Anti-Entropy Process)。该机制会在后台持续比对数据库不同副本之间的数值,一旦发现副本间存在不一致,便定位差异并纠正。

在比对不同副本的数据差异时,存在一个高效的算法与数据结构技巧:Merkle Tree(默克尔树)。通过 Merkle Tree,系统无需全量传输数据,而是能在大约 $O(\log n)$ 的极高效率下快速比对出任意两个副本在具体哪一段数据上存在差异,进而根据版本号决定谁是最新值并完成修复。

不同的分布式存储系统在实现取舍上差异显著: - **Voldemort**:未实现后台的 Anti-Entropy Process。这虽然避免了后台竞争,但也显著损害了其数据持久性(Durability),导致客户端读到陈旧数据(stale data)的概率大幅增加。 - **Dynamo**:将 Read Repair 与 Anti-Entropy 结合使用,二者相辅相成才能从工程上保证最终一致性(Eventual Consistency)。 - **资源争抢问题**:Anti-Entropy 作为后台任务,在运行时不可避免地要与线上正在进行的实时事务(ongoing transactions)争抢 CPU、I/O 等系统资源,进而拖累前台性能,系统的调优和优化因此变得极其复杂。

```mermaid flowchart TD accTitle: Replica Inconsistency Resolution accDescr: Dynamo-style systems resolve inconsistent data via Read Repair on read paths and background Anti-Entropy using Merkle Trees.

subgraph ForeGround["前台读取路径 (Read Repair)"] C["客户端读取多个 Replica"] -->|检测到 v6 与 v7| C C -->|写入最新 v7 数据| R6["过期的 Replica (v6 -> v7)"] end

subgraph BackGround["后台反熵路径 (Anti-Entropy)"] MT["Merkle Tree 结构比对<br/>(O(log n) 快速定位差异)"] --> Diff{"发现数据分歧?"} Diff -- 是 --> Fix["比对版本号拉取最新值覆盖"] Diff -- 否 --> Idle["持续周期性巡检"] end ```

针对现场听众提出的疑问——“如果写入失败(例如配置为 5 个节点仅成功写入 1 个),之后的 Read Repair 是否会产生误判?”,这触及了 Quorum(法定人数)机制的核心。在 Leaderless 架构中,读写通常需要满足法定人数投票原则:即读法定节点数 $R$ 与写法定节点数 $W$ 之和必须严格大于总副本数 $N$($W + R > N$),以此在数学上确保写入的最新节点集合与读取的节点集合必然存在交集。

---

[01:05:00](https://www.youtube.com/watch?v=vGb7JfpKFro&t=3900s) 异常写入下的 Quorum 边界与脏读修复困境

如果一个写入在 5 个节点中只写入了 Replica 1,其余 4 个全部失败,从写入客户端的视角来看,这次写操作已经超时或报错(Aborted / Failed)。然而在 Dynamo 等无主数据库中,由于没有分布式回滚机制,那个已经写入 Replica 1 的新版本(如 version 7)并不会被撤销。当后续另一个客户端发起读操作并凑巧读到了 Replica 1 时,就会引发严重的边界问题:由于 Dynamo 原始论文并未在细节层面解决“未达 Quorum 的失败写是否该被传播”的问题,Read Repair 很有可能会做出错误决策。

最棘手的边界情况(Worst Case)如下: - 系统参数为 $N=5, W=3, R=3$。 - 某次写入尝试将数据更新为 version 7,由于网络问题只成功写入了 1 个节点,写入失败返回错误。 - 后续读操作配置为读取 3 个节点,恰好读到了那 1 个包含 version 7 的节点以及另外 2 个仍为旧数据 version 6 的节点。 - 此时读取客户端面对“1 个新版本 + 2 个旧版本”的局面,无法判定另外未被读取的 2 个节点到底是 version 6 还是 version 7。如果系统单纯以“版本号大者优先”的策略执行 Read Repair,客户端不仅会读到这个本该被放弃的未提交数据(Dirty Read),甚至还会反向把 version 7 同步刷给另外两个读到的节点,将一次未成功的写入在事实上给“转正提交”(Committed)了。

这种 Corner Case 深刻说明了:在 DynamoDB、Cassandra 这类无主数据库上追求严格的事务保证(Transaction Guarantee)是极难成立的。它们本质上并不提供传统意义上的事务语义。

---

[01:10:00](https://www.youtube.com/watch?v=vGb7JfpKFro&t=4200s) Quorum 数字游戏与 Sloppy Quorum 机制

从理论推演,$W$ 与 $R$ 并非强制要求 $W + R > N$。系统在不同业务场景下可做极端调配: - **$W=1, R=N$**:写极为频繁、写入性能优先的场景,写任意 1 个节点即可返回,但读取时必须全量扫描 $N$ 个节点挑出最新值。 - **$W=N, R=1$**:读极其密集的场景,写入必须保证全部写入成功,读取时则可就近从任意 1 个副本直接返回。

然而,这类极端的参数调配在工程实践中往往只是“玩数字游戏”。无主复制的核心设计初衷是在容忍节点下线不可用(Fault Tolerance)的同时保证系统整体高可用,纯粹追求极高读写性能完全有更好的专门方案。

关于无主复制的扩展性(Scalability),其集群整体容量在理论上是无限水平扩展的(Infinite Scale)。虽然单条数据的副本数固定(例如 $N=5$),但系统并不是只有 5 台机器,而是通过 Consistent Hashing(一致性哈希)从上万甚至上百万台机器组成的集群环中,根据数据 Key 的 Hash 值动态映射到具体的某 5 个节点。

```mermaid flowchart LR accTitle: Sloppy Quorum and Hinted Handoff accDescr: Illustration of Sloppy Quorum using a neighbor node when designated nodes are unavailable, followed by Hinted Handoff.

subgraph HomeReplica["主数据节点集合 (1-5)"] R1["Replica 1 (存活)"] R2["Replica 2 (存活)"] R3["Replica 3 (宕机)"] R4["Replica 4 (宕机)"] R5["Replica 5 (宕机)"] end

subgraph TemporaryNode["借住临时节点"] R6["Replica 6 (借宿写入)"] end

Client["写客户端 (W=3)"] -->|写入| R1 Client -->|写入| R2 Client -->|因3,4,5宕机借用| R6 R6 -.->|"Hinted Handoff<br/>(节点3恢复后回传数据)"| R3 ```

当某条数据专属的 5 个副本中恰好有 3 个节点同时宕机(例如 Replica 3、4、5 全部挂掉)时,严格的 Quorum 机制将导致写请求完全被拒。为了贯彻 Dynamo“绝对保证高可用”的理念,系统采取了一种折中机制——**Sloppy Quorum(宽松法定人数)**与 **Hinted Handoff(提示移交)**: - 这就如同自己钥匙丢了进不去家门,先去邻居家的沙发上借睡一晚; - 客户端在无法凑齐原主节点 Quorum 时,会临时借用环上的相邻节点(如 Replica 6)接收写入; - 一旦原本挂掉的 Replica 3 恢复正常,Replica 6 会再将暂存的数据拷贝交还给 Replica 3。

---

[01:15:00](https://www.youtube.com/watch?v=vGb7JfpKFro&t=4500s) 网络分区陷阱、并发写入冲突与“甩锅”客户端

Sloppy Quorum 极大地提高了可用性,却也带来了更为诡异的一致性陷阱。在网络分区(Network Partition)发生时,如果节点 1、2 与节点 3、4、5 之间发生网络孤岛: - 写入端与 1、2 在同一分区,由于凑不够 3 个节点,借用了临时节点 6,完成了对 1、2、6 的写入; - 读取端恰巧在另一个分区,从 3、4、5 读取数据; - 此时写操作和读操作虽然各自都宣称满足了“3 个节点”的 Quorum,但两边集合完全无交集,读取端完全读不到刚才的写入。

此外,并发写入(Concurrent Writes)还会带来不可逆的数据丢失风险。在没有全局主节点协调的情况下,多个副本节点之间存在时钟漂移(Clock Skew),依赖时钟判断先后极易出错。部分系统(如 Cassandra)默认采用 **LWW(Last Write Wins,最后写入获胜)** 策略,此时并发写必然会导致其中一部分写入被物理覆盖而悄无声息地丢失。

```mermaid flowchart TD accTitle: Concurrent Write Conflict accDescr: Leaderless architecture cannot determine write ordering during concurrent writes, resulting in conflicting states across replicas.

NodeA["Client A"] -->|写入值 A| Replica1["Replica 1 (存有值 A)"] NodeB["Client B"] -->|写入值 B| Replica3["Replica 3 (存有值 B)"]

Replica1 -->|复制同步| Replica2["Replica 2"] Replica3 -->|复制同步| Replica2

Replica2 --> Conflict{"Replica 2 发生冲突<br/>(A 与 B 谁是最新?)"} Conflict -->|策略1: LWW| LWWChoice["依本地物理时钟裁决 (可能有时钟漂移导致丢数据)"] Conflict -->|策略2: 甩锅客户端| AppLogic["调用业务端 Conflict Resolution 逻辑裁决"] ```

由于 Leaderless 架构从底层数学和网络物理上都无法完美保证因果一致,DynamoDB 最终选择了“甩锅”策略:将冲突解决逻辑(Conflict Resolution)完全交由客户端业务代码自定义。数据库自身只做“尽力而为”(Best Effort)。

在业务场景层面,不同操作的冲突合并规则差异极大: - **电商购物车案例**:Dynamo 论文中经典的购物车结算(Shopping Cart Checkout)场景。如果客户端 A 尝试往购物车里添加商品,而并发的客户端 B 尝试从购物车中移除某件商品,一旦发生并发冲突,合并策略通常必须设计为“绝不删除被移除的商品”,宁可多加或保留,也绝不能让用户放进购物车的商品被静默丢弃。 - **系统选型反思**:对于像 Cadence 这样重度依赖事务原子性与一致性的工作流引擎,在 Leaderless Replication 数据库上硬造分布式事务往往会让底层工程师“痛不欲生”。演讲者调侃称,在生产环境中敢直接拿 DynamoDB 或 Cassandra 等无主系统来做强一致业务事务(Business Transaction)的架构师,都是不折不扣的“勇士”。

[01:20:00](https://www.youtube.com/watch?v=vGb7JfpKFro&t=4800s) 冲突解决与复杂机制:Version Vector 与 Lamport Timestamp

在分布式系统的冲突解决(Conflict Resolution)中,业务场景往往决定了具体的技术选型。以购物车添加商品为例,如果系统在并发更新时无法判断谁先谁后,往往会默认把新增的 item 追加在最后——这种设计说白了就是为了促进用户消费,完全是由实际的业务用例(business use case)倒逼数据库设计的体现。

提到并发冲突处理,幻灯片上展示的 Version Vector(版本向量)复杂度极高。讲师直言今天不打算深入展开这个单节点因果依赖的 Version Vector,因为这种机制极其烧脑,讲起来"首先要把我自己绕进去,然后其次的话大家可能会被绕进去"。如果再将其扩展复制到 $N$ 个节点上,整体系统的状态与因果关系复杂度会呈爆炸式上升,建议感兴趣的同学课后自行阅读。

```mermaid flowchart TD accTitle: Version Vector vs Lamport Timestamp accDescr: 对比版本向量与兰伯特时间戳在并发检测与全序生成上的不同定位

subgraph VV["Version Vector"] V1["捕获并发写与因果依赖 (Causal Dependency)"] V2["用于并发冲突检测与解决 (Conflict Resolution)"] end

subgraph LT["Lamport Timestamp"] L1["生成事件逻辑全序 (Total Order)"] L2["无法区分两事件是因果相关还是真正并发"] end ```

有学员提问 Version Vector 和 Lamport Timestamp 是不是同一个东西。讲师澄清它们完全不是一回事: - **Lamport Timestamp** 相对简单,核心作用是为分布式事件提供全局一致的逻辑顺序(Total Order)。 - **Version Vector** 则用于追踪因果依赖并检测并发写(Concurrent Writes),从而进行冲突解决(Conflict Resolution)。

由于细节过于庞杂,讲师在本次分享中跳过了部分原书细节 slide。他坦言技术分享主要是 high-level 框架的普及与思考方向的引导;如果一开始就扎进过于琐碎的死角,反而容易让初学者在大方向上彻底迷失。

---

[01:22:42](https://www.youtube.com/watch?v=vGb7JfpKFro&t=4962s) 复制架构全景复盘:Single-Leader、Multi-Leader 到 Leaderless

讲师带领大家回到最初的 Agenda,对整个 Replication(复制)章节进行了系统性串讲与总结。

```mermaid flowchart TD accTitle: 三种核心数据复制架构对比 accDescr: 展示 Single-Leader, Multi-Leader 和 Leaderless 三种复制模式的核心特点与权衡

subgraph SL["Single-Leader Replication"] SL1["标准主从复制架构"] SL2["异步 / 同步复制"] SL3["基于专用的 Replication Log"] SL4["提供 Read-after-write 与 Monotonic Reads 等一致性级别"] SL5["痛点: Leader 宕机导致几百毫秒至数秒的不可用"] end

subgraph ML["Multi-Leader Replication"] ML1["跨多数据中心 (Cross-DC) 部署"] ML2["核心挑战: 跨主并发写冲突解决"] ML3["最佳实践: 尽量将写路由到同一 Leader 避免冲突"] ML4["方案: CRDT / 集合合并 / 读时解决 (Read Repair)"] end

subgraph LL["Leaderless Replication"] LL1["Dynamo 体系结构风格"] LL2["Quorum 读写机制 (W + R > N)"] LL3["容错优化: Sloppy Quorum 与 Hinted Handoff"] LL4["数据修复: Read Repair 与 Anti-Entropy 进程"] LL5["定位: 极高可用性优先的 Best-effort 架构"] end ```

1. **Single-Leader Replication(单主复制)**: - 最经典的传统关系型数据库方案。主节点(Leader)接收所有写请求,再同步或异步复制到从节点(Followers)。 - 早期尝试用 Statement-based Log 或 Write-Ahead Log (WAL) 做复制均有局限,因此现代数据库普遍设计了专用的 Replication Log。 - 读从库时会衍生一致性级别的权衡,如写后读一致性(Read-after-write consistency)和单调读一致性(Monotonic reads)。 - 该模式对于几百、几千人的初创团队完全足够,但对于 Facebook 或 Uber 这种体量的企业则显露瓶颈。一旦单一 Leader 宕机引发 Failover,重新选主的过程往往需要几百毫秒甚至数秒,期间写服务完全不可用,这种可用性抖动在巨型系统中不可接受。

2. **Multi-Leader Replication(多主复制)**: - 常见于跨数据中心(Cross Data Center)部署。多个数据中心各有 Leader 接收本地写,再跨机房异步同步。 - 核心痛点在于并发写冲突。最理想的设计是业务架构上完全规避冲突——例如通过路由策略,将特定租户或行的所有写请求固定分流到同一个 Leader。 - 若业务允许一定程度的冲突,可采用无序集合(Unordered Hash Set)、CRDT(无冲突复制数据类型)或读时处理等机制,先无条件记录所有写操作,并在读取时进行 resolve。

3. **Leaderless Replication(无主复制)**: - 以 Amazon Dynamo 论文及其开源衍生品(如 Cassandra)为代表。 - 讲师强烈推荐大家去精读 Dynamo 的原始论文。初读会极其吃力,甚至需要死磕十几个小时,但读通后会对分布式系统设计思想产生质的飞跃。 - 该架构一切围绕"最大化可用性"展开,通过 Quorum、Sloppy Quorum、Hinted Handoff 以及后台 Anti-Entropy(反熵)等机制保证最终一致性(Eventual Consistency)。 - 针对数据陈旧问题,DynamoDB 和 Cassandra 等系统配备了专门的 Stale Monitoring 指标,方便工程师根据延迟与陈旧度做精细调优(Fine-tuning)。 - **血泪警示**:Leaderless 属于 Best-effort(尽力而为)型架构。Single-Leader 数据库调优后能稳定保证事务隔离与持久性,但如果哪位架构师想用 Leaderless Replication 来扛核心支付或金融资金业务,"那都是勇士啊,基本上都死得很惨"。

---

[01:26:45](https://www.youtube.com/watch?v=vGb7JfpKFro&t=5205s) Q&A 研讨:Two-Phase Commit (2PC) 与 Consensus 的本质辨析

在问答环节,一位学员针对后续章节提出疑问:书中先讲了分布式事务的 Two-Phase Commit (2PC),随后又讲了容错共识算法(Fault-Tolerant Consensus Algorithm,如 Paxos / Raft)。学员困惑 2PC 是否是提供强一致性的手段,而共识算法是在没有 2PC 时退而求其次的选择?

讲师明确指出,**2PC 和 Consensus(共识)不是替代关系,两者解决的问题不同,概念上有交集但本质偏正交(Orthogonal)**:

- **Two-Phase Commit (2PC)**: - 核心目标是**跨分片、跨节点(Cross-shard / Cross-node)的原子事务提交**。 - 它保证要么所有节点全部提交,要么全部回滚。但 2PC 存在致命缺陷:协调者单点故障易导致阻塞,导致系统可用性极差且执行效率极慢。

- **Consensus Algorithm(共识算法)**: - 核心目标是**在不可靠的分布式节点间就某个状态或序列达成全序一致(Total Order)**。 - 典型应用场景包括:确定 Single-Leader 模式中 Replication Log 的全局严格写入顺序;或者当旧 Leader 挂掉时,促使剩余众多 Followers 在不可靠网络下协商选出新的唯一 Leader。

随后,讲师借此引申探讨了 **Linearizability(线性一致性)与 Transaction(事务)** 之间经常被混淆的关系: - 线性一致性强调的是"单个对象在读写时间轴上的实时最新与全局全序";事务(如 ACID 中的 I)强调的是"跨多个对象操作的原子性与隔离性"。 - 两者互不包含,互不保证: - 存在保证 Linearizability 但不保证 Transaction 的场景(如单一强一致键值更新)。 - 存在保证 Transaction 隔离性但不保证 Linearizability 的场景(例如采用快照隔离 Snapshot Isolation 或序列化快照隔离 SSI 的数据库,即使事务完全隔离,读操作读到的依然可能是快照历史视图)。 - 唯有类似两阶段提交等重型机制,才试图同时强绑 Linearizability 与多节点 Transaction,但这付出了极其惨重的吞吐与延迟代价。

---

[01:30:18](https://www.youtube.com/watch?v=vGb7JfpKFro&t=5418s) 工业界经验杂谈:从 50 页 Design Doc 看 Last-Write-Wins 的数据丢失陷阱

讲师感慨,分布式系统的术语体系复杂且细节繁多。虽然面试时极少有人会死磕这么深的水下细节,但在工业界的大规模工程实践中,吃透底层原理至关重要。

讲师分享了自己在公司主导系统设计的真实经历: - 他曾花费心血撰写了一份 50 多页的大型架构设计文档(Design Doc),并提交到公司的 Level 2 评审委员会(Review Committee)。 - 评审席上坐满了拥有二三十年工程经验的资深 Staff Engineer。在评审答辩中,讲师专门向大家剖析了一个技术陷阱:**Last-Write-Wins(LWW,最后写入者胜)机制绝对无法保证数据零丢失(Zero Data Loss)**。 - 这个观点立刻引起了全场资深工程师的浓厚兴趣,随后大家就并发时钟偏移、覆盖写造成静默丢数据等细节展开了深层次的技术探讨。

讲师最后总结道,在实际工作中,大部分工程师可能不会去深究这类边角限制,"你不理解也没事儿,但是你理解了肯定是对你有好处的——至少你不会在系统里给队友埋下一个巨大的坑"。全场在致谢中结束了本次复制专题的课程。

## Notable quotes > [00:00](https://www.youtube.com/watch?v=vGb7JfpKFro&t=0s) 它的读写效率高很多,支持存储较复杂的数据结构,且当前业务数据不需要强持久性(durability)保证 — Scott Shi

> [35:00](https://www.youtube.com/watch?v=vGb7JfpKFro&t=2100s) 当你有很多 transaction 的时候不是问题,当你有很多 transaction compete 同一个 object 的时候也不是问题。一定会有一个 winner,winner will take everything,剩下的人多半都是 loser,loser 就得 stand back / stay back,等下一次 retry。 — Scott Shi

> [45:00](https://www.youtube.com/watch?v=vGb7JfpKFro&t=2700s) 解决冲突的责任到底在数据库,还是在应用层客户端? — Scott Shi

> [01:20:00](https://www.youtube.com/watch?v=vGb7JfpKFro&t=4800s) 首先要把我自己绕进去,然后其次的话大家可能会被绕进去 — Scott Shi

> [01:22:42](https://www.youtube.com/watch?v=vGb7JfpKFro&t=4962s) 那都是勇士啊,基本上都死得很惨 — Scott Shi

> [01:26:45](https://www.youtube.com/watch?v=vGb7JfpKFro&t=5205s) 2PC 和 Consensus(共识)不是替代关系,两者解决的问题不同,概念上有交集但本质偏正交(Orthogonal) — Scott Shi

> [01:30:18](https://www.youtube.com/watch?v=vGb7JfpKFro&t=5418s) Last-Write-Wins(LWW,最后写入者胜)机制绝对无法保证数据零丢失(Zero Data Loss) — Scott Shi

> [01:30:18](https://www.youtube.com/watch?v=vGb7JfpKFro&t=5418s) 你不理解也没事儿,但是你理解了肯定是对你有好处的——至少你不会在系统里给队友埋下一个巨大的坑 — Scott Shi

## Mentioned Scott Shi, DDIA (Designing Data-Intensive Applications), Redis, HBase, Google Hangouts, Google, Twitter, Uber, Uber Eats, Kafka, Cadence, Temporal, Cassandra, Maxim Fateev, Amazon Simple Workflow Service (SWF), Piper, Apache Airflow, Facebook, MySQL, Tungsten Replicator, PostgreSQL, BDR (Bi-Directional Replication), Oracle GoldenGate, Google Docs, CRDT, Blitz, Event Sourcing, Bitcoin, Git, Riak, Amazon Dynamo, DynamoDB, Memcached, Redshift, Apache Flink, Voldemort, Merkle Tree, Lamport Timestamp, Paxos, Raft

## Video description In this view, we continue the discussion of replication, the slide could be found here: https://docs.google.com/presentation/d/1XfbqOmNh-yRqsVnznYFBd6hdjrreaef43TP9rcb2TXQ/edit

[Source](https://www.youtube.com/watch?v=vGb7JfpKFro)

中文简报

Uber架构教训警示:无主复制与LWW难保核心交易零丢单 无主复制与最后写入者胜机制本质上属于尽力而为架构,物理时钟漂移必然导致数据静默丢失,强行用于支付对账或强状态工作流将引发毁灭性架构灾难。 本期内容围绕《数据密集型应用系统设计》(DDIA)复制机制的后半部分展开,系统剖析了单主、多主(Multi-Leader)与无主(Leaderless / Dynamo 风格)复制架构的设计哲学与工程权衡。讲师深入拆解了单调读、因果一致性、异步写冲突、Quorum 读写、Sloppy Quorum、Hinted Handoff 以及 Anti-Entropy 等核心机制,并结合 Uber Blitz 工单平台与支付选型等工业界真实踩坑案例,警示开发者不可盲目将无主数据库用于强事务场景。在答疑环节,讲师进一步辨析了 2PC 与共识算法(Consensus)、线性一致性与事务隔离性的本质边界,强调在大规模系统中规避技术陷阱的重要性。 单调读一致性防止用户在读取时遭遇“时间回溯”,工业界最直接的规避策略是将同一用户的读请求基于哈希固定路由到同一个副本节点。 (5:14) 多主复制(Multi-Leader)虽能显著降低跨国请求网络延迟并提高可用性,但异步冲突检测会导致灾难性的“双花”问题,涉及账户资金的核心链路必须通过路由收敛或强持久化单主数据库进行串行化控制。 (30:00) 无主复制(Leaderless)以高可用为核心导向,采用 Quorum 多数派投票读写并借助反熵和读修复保证最终一致性,但其本质是尽力而为(Best-effort),绝不可用于对一致性和无损要求极高的金融交易系统。 (55:00) 两阶段提交(2PC)与共识算法(Consensus)是正交概念:2PC 解决跨分片/跨节点的原子提交但易因协调者单点故障而阻塞,共识算法则专注在不可靠网络下为事件达成全局全序。 (86:45)

Reading summary

讲师 Scott Shi 在本讲中系统剖析了多主复制与无主复制的核心机制与工程权衡,强调分布式系统选型必须基于底层一致性语义与边界风险,切忌盲目依赖尽力而为架构承载强事务业务。 复制延迟引发的一致性破缺与工程底线 (5:14) 网络延迟的无界性使得跨副本同步可能出现长达数分钟乃至数小时的极端抖动,直接破坏单调读与因果一致性。在主从复制中,读请求分散到存在延迟的副本会导致读操作发生“时间倒流”;而跨分区延迟更可能导致“答复先于提问”的时序错乱。保障单调读的最简方案是按用户 ID 将读操作绑定至同一副本,而守护因果性仅需维护偏序(Partial Order),无需付出全序共识的高昂代价。 面对极端延迟与故障,系统设计必须推演最坏情况。讲师列举工业界教训指出,早期 Google HBase 因缺少防护令牌(Fencing Token)引发脑裂损坏数据,Google Hangouts 也曾发生数小时的消息延迟投递;Uber 曾尝试用 Kafka 支撑支付系统,但即便消息丢失率降至十亿分之一,在核心资金链路中仍属不可接受的架构风险。 多主架构的双花死局与冲突解决策略 (25:00) 跨数据中心部署的多主复制虽能降低地理延迟并提供容灾,但其核心痛点是并发异步写入导致的冲突。若在两地数据中心同时扣减同一账户余额,异步复制会导致双方在基于旧余额的前提下分别返回成功,最终引发灾难性的“双花”超付且无法轻易回滚。 彻底规避冲突的方案是将特定资源的写操作通过路由层强制收敛至单点 Leader,或承受吞吐极低、跨洲际延迟巨大的两阶段提交(2PC)。对于允许暂态不一致的业务,采用最后写入者胜(LWW)会因物理时钟偏移(Clock Drift)造成静默数据覆盖。更优路径是借助 CRDT 思想或读时解决,例如 Uber Blitz 客服平台将交互事件作为带 UUID 的记录追加至无序哈希表,写入完全免锁无冲突,再将数据还原与展示排序交由客户端业务逻辑处理。 无主复制的 Quorum 机制与脏读修复陷阱 (55:00) 源自 Amazon Dynamo 的无主复制彻底去除了中心主节点,依靠多数派投票($W + R > N$)换取极致的高可用性与无单点故障。系统依赖前台读修复(Read Repair)与基于 Merkle Tree 的后台反熵(Anti-Entropy)实现最终一致性,但后台修复进程会与在线事务竞争 CPU 和 I/O 资源,且 Voldemort 等未实现反熵的系统数据持久性会大打折扣。 在异常写入场景下,Quorum 会暴露严重的边界漏洞。若一次写入在 5 副本中仅落盘 1 个节点即报错中断,后续读操作凑巧读到该节点与 2 个旧节点时,若读修复按大版本号覆盖,反会将未达 Quorum 的失败写入“转正提交”,引发脏读。此外,Sloppy Quorum 与 Hinted Handoff 虽能在主副本不可用时借用邻居节点维持可用,但在网络分区下会导致读写集合完全无交集,造成暂态数据完全不可见。 复制模型定位与分布式事务辨析 (82:42) 三种复制模型各有适用边界:单主复制适合中小规模与强事务,但主节点故障转移有秒级不可用抖动;多主复制专注跨数据中心协同,写冲突治理是核心;无主复制属于“尽力而为”的高可用架构,适合购物车追加等宁增不减的场景,拿来强撑工作流或支付事务极易导致架构崩溃,如 Uber Cadence 强行搭载弱一致的 Cassandra 引发大量难以收敛的角标异常。 针对常见概念混淆,讲师澄清两阶段提交(2PC)与共识算法(Consensus)本质正交:2PC 解决跨分片、跨节点的原子提交,但协调者单点易引发阻塞;共识算法(如 Raft/Paxos)则是在不可靠网络中对状态达成全序一致。此外,线性一致性强调单对象的全局实时最新,事务强调跨多操作的 ACID 隔离,二者互不包含,唯有付出高昂延迟代价才能强行兼得。 分布式架构不存在万能模型,架构师必须清晰辨析系统一致性边界与业务长短事务的区别;在设计审查中警惕 LWW 丢数据、无主库造事务等认知盲区,守住数据持久化底线。

Source quotes

[0:00] Scott Shi: 它的读写效率高很多,支持存储较复杂的数据结构,且当前业务数据不需要强持久性(durability)保证 [35:00] Scott Shi: 当你有很多 transaction 的时候不是问题,当你有很多 transaction compete 同一个 object 的时候也不是问题。一定会有一个 winner,winner will take everything,剩下的人多半都是 loser,loser 就得 stand back / stay back,等下一次 retry。 [45:00] Scott Shi: 解决冲突的责任到底在数据库,还是在应用层客户端? [80:00] Scott Shi: 首先要把我自己绕进去,然后其次的话大家可能会被绕进去 [82:42] Scott Shi: 那都是勇士啊,基本上都死得很惨 [86:45] Scott Shi: 2PC 和 Consensus(共识)不是替代关系,两者解决的问题不同,概念上有交集但本质偏正交(Orthogonal) [90:18] Scott Shi: Last-Write-Wins(LWW,最后写入者胜)机制绝对无法保证数据零丢失(Zero Data Loss) [90:18] Scott Shi: 你不理解也没事儿,但是你理解了肯定是对你有好处的——至少你不会在系统里给队友埋下一个巨大的坑

Original transcript

[0:00] 而且我这边有点心得就是说我感觉实际上很多时候你面这个System Design的时候它很难就是说你在45分钟大部分是45分钟到一个小时之内把这整个的流程就是从一开始collect requirements一直到最后的data schema那边全部走完很多时候我感觉可能就是一般你就看面试官会挑哪一块儿而他会让你具体讲讲我感觉很多面试官其实很关心你怎么样能够给一个例子把这整个系统end to end 跑一遍跑通然后如果这一步如果你能够把它说清楚的话,我感觉基本上这个面试就没什么问题,然后之后再来说,反正就是我的意思是说,如果时间很紧的话,确保就是能够,你设计了这个系统,按-to-end 跑一遍,就是给它展示一遍,就像你写完程序之后给它演示一遍,你test case一样,然后这样子之后有多余的时间你再来做其他的API,或者是database,或dataschema,因为很难,45分钟,之内基本上是不可能把所有东西都cover到否则你也cover就是不会很detail所以说但是我感觉我个人经验是这个Antoine的这个过程是很多面试官是非常care的对的,其实也看面试官,一般来说安团案的话,其实System Design,我觉得如果是两个人在一个频道上聊得可以很快的,System Design两个人在一个频道上,比如说为什么要Redis,然后简单聊一句,这个Redis效率高很多,你可以存一些比较复杂的Data Structure,然后你这个Data不需要Durability,很快就聊完了,其实System Design我觉得很重要的一点是,你聊的一定要在一个频道上,是吧,有点入门的理解对于这个System Design然后你要是和面试官聊的不是一个频道上的话这个System Design多半就怪了然后其次的话也要看面试官想考什么有的时候面试官想考的是比如说你对Database的理解,有的时候就是更多想考察的是不一样的地方是吧?但我觉得这点其实挺好的,你碰上这点说的挺好的,就是说应该有一个Antoine的例子是吧?就是很多时候有一个Antoine例子,甚至面试官没有主动提的话,你也可以把一个Antoine例子举出来是吧,一开始就举个安图案例子,然后再讨论每一部分的时候,把这个安图案的例子然后再写进去写一个example然后这样讨论过来的话我觉得多半就没什么问题了是好的,然后这块我们接着聊,我还是想在这个之前说一下,我现在大概估计还是到8月底之前结束这个System Design所有的讨论,之后的话我们可能有一些AdHoc的这种,或者是在群里面有些问题的话,我们可以一个问题讨论,但是我还是想在8月底之前结束,然后现在的话我觉得结束的话,这个System Design这块的话,我可能加一些跟之前不一样的System Design,就比如说,比如说Design a parking lot 这种有点像OO Design那种 Design Parking Lot这种因为这个其实有的公司也面Design Parking Lot也面它和我们之前学的System Design的风格不一样然后但是挺重要的一方面我知道Amazon喜欢面这个Design Parking Lot其他地方也喜欢面Design Parking Lot这个的话我们到时候加一两节我希望就是一到两节吧然后这样的话我们可以思考一下这个Parking Lot这种东西的DesignOO的Design对,然后这是一点,然后其次的话就是我尽量把这本书走完,就是这个Design Data Intensive Application这本书走完嗯后面的话这几张都比较难,我尽量还是讲的稍微慢一点,要么就多的时间长一点,要么把它break成两张。但是后面这几张都比较难的,所以这个还是尽量把这本书做完,所以大概还有大概还有五周左右,然后五周的话,加上这次可能有11次左右,我尽量把这个都搅完好好,我们接着讲这个replication,我们上次是讲了一个single leader replication。然后是一个Primary到两个SecondaryCopy,是吧?可能今天就是从这开始讲然后今天任务是把剩下的讲完就是Multi-Leader Replication 和 Leaderless Replication然后 again就是这个replication这张真的非常复杂,相对来说是比较复杂的 [5:00] 也就是这个system,就CPU system 里面相对来说比较核心的一部分了所以大家的话还是就是花点时间再额外看一看这个slides然后再思考一下这个slides是吧然后我们接着看这个 monotonic rateMonotonic read 是什么呢?保证什么呢?就是你从一个SIM Replica读的时候,你读的时候都从SIM Replica读,那你就可以保证Monotonic Grid。但什么时候不能保证Monotonic Grid呢?就是你读同一个Data,你比如说这种情况下,这个User一二三次,insert了一个comments,说这个sounds good,然后这个comments呢在replicate两个followers的时候,它时间是不一样的,比如说在时间1的时候,它就replicate到followers1,所以你在时间1之后,如果从followers1读的话,你就读到这个comments,但你从followers2读的时候,你就读不到这个comments了。这种情况下的话,我们看到从follow 1读的时候,我们是看到这个comments,然后你再读的时候,这个request 又到follow 2了,然后就读不到这个comments了。所以你看到这个data,实际是travel back to time,就是说,你看到这个data,实际上是revoke了,revert到之前的一个version了。有点像这个时间回溯了,time goes backwards,这个anomaly有些情况下是影响比较大的,其实任何情况下都影响比较大,比如说你读了一个Facebook feeds,你看到一个,比如说川普在Facebook上发了一篇文章,然后你一秒钟前看了,refresh又看不到了,然后你这时候可能就以为是不是这个文章也剔除掉了,假设你没有refresh的话,其实对user是造成了很多confusion的,那到底是在refresh的时候这文章出现了,到底是Trump在自己之间把这篇文章删掉了呢?还是他这个设计的是你读到不同的replica,然后你就是go back to time, go back, backward to time 了?这个其实是区分不出来的,从user 角度是区分不出来的,这些user experience 会很糟糕。所以我们怎么样能保证没有这个monotonic read呢?就是怎么样能保证有monotonic read呢?其实最简单的方式就是你每个read都去same replica就可以了,这个monotonic read的话,它是这会出现这个go backwards to time的,实际上是你去不同的replica读这个data的缘故,然后才go back towards time所以这个的话是这个可以保证你没有monotonic read但是这有同样的问题这个同样的问题是什么呢?这个同样的问题是什么呢?就是你这个user呢其实是你的request wrote到一个server了但是如果这个server fill掉怎么办?是吧?你如果这个server fill掉的话你还是需要去另一个replica读那你去另一个replica读的时候你可能就同样的问题就会出现如果timing不好的话你就会确实看到这个时间回溯了但是我们说server down这个概率是几千万分之一我觉得Sumle rule的话就在System Design中除非是这种前的transaction这种是吧否则的话如果它出现的概率是几千万分之一或上亿分之一的话很多时候你就不考虑了,不需要考虑那么多细节了那就像这种情况下,我们server的request,就是user的request是总是 rode 到其中一个database server假设Database server done的话,我们去另一个follow,这种情况下可能会broke monotonic read 的 assumption,但是如果是这种 comments read,我们就不考虑了,只要不是这种 transfer money 这种事情非常严肃的问题的话,你是不考虑这个问题的,或者是除非面试官强烈要求你要做这个事情,否则你是不考虑这种非常小概率的事件。进行优化是吧,然后其次的话在这个Data Center里面,所有的这个拜暂停 failure,拜暂停 failure,都是不被考虑的,就是你假设你这个Data Center里面所有的 nodes都是 friendly,都是 auto good face,就是出于好心的,不是这个 malicious nodes,这个是不考虑的,因为你这个考虑的话,这个就是 cost efficiency,是完全就是这个 cost 是完全不能 cover the efficiency 的。好,然后,所以我们在讨论这个 monotonic rate 为什么很重要的时候,是因为它会broke 一个Assumption,broke一个是什么Assumption呢?就是casualty assumption,就是因果效应,casualty就是因果效应。比如说CakePulse问Cake你能看到未来多久之后是吧然后Cake在听到这个Pulse的问题以后说我大概能看到未来10秒钟之后它这个呢,在这个数据呢,这两个数据呢,它写进去的时候,可能就写到不同的地儿了,所以这个从某个user来看的话,从这个observer的角度来看的话,因为这个cake的回画,是先写到这个partition2的leader上,然后先replicate到partition2的leader上, [10:00] 然后这个问话就是这个这个问话呢实际上它这个就是copy 的时候它时间要久一点,它这个在partition 1 的时候,因为它这两个在不同的partition上在partition 1 的时候它这个时间copy 要久一点,所以就导致了好像这个答在问之前对,这个打在问之前,所以这个的话是casualty呢,guarantee是比较复杂的一点比较复杂一点,就是我们在这张之后也会有额外的讨论是吧,怎么样guarantee这个casualty,然后在这个consensus这张也会有额外的考虑是吧,怎么样guarantee这个casualty,这个casualty的话,你可以认为这样子,如果你在consensus的角度来讲的话,你如果guarantee这个casualty的话,你可以是个partial order你只要有 partial order 就可以 guarantee 这个 casualty 了但如果你想保证所有的这个对话全部是 order 的话 你要 total order在Total Order的话就需要全部的consensus了,这个时间效率就很低,但你只想保留这个casualty的话,这个关系的话,就是你允许concurrent这个data出现的话,你只要reserve这个partial order就可以了。这个partial order和这个total order这个感觉和这个coding不是有一个题吗?就是partial order的话,我记得我很久之前面那个Twitter的时候,他给我出了一个partial order的题,然后这个题我当时没有答出来,2014年,2013年的时候,我当时没有答出来。然后我过了好几个月后我都没想到怎么做这个patch order的题反正这个pass order就是你用pass order的话可以guarantee这个casual date然后其次的话就是大家反正遇到什么想不通的题的话就是也可以讨论一下然后也可以思考一下就是我觉得很多时候我之前面试过的题就是过了好几年后来想通了怎么回事他想考虽了什么点但是我觉得也不迟吧反正只要能想通就可以并且如果你面试的足够多的话,其实说的题都是那些套路,如果面试足够多的话,大概都知道什么套路了。然后这个基本上能保证你之后的面试基本上都能过,稍微准备一下的话,然后再加一些经验的话,基本上这个面试其实是和这个工作是不直接相关的,和这个是不直接相关的,是对。好我们接着谈剩下的所以我们讨论的话就是这几个问题,这几个consensus break,这几个这几个guarantee break 都是根据 replication lags 相关的好所以说我们要怎么样解决这个replication lag呢?所以我们可以这么讲,如果我们是在一个eventually consistent的这个system里面,我们在最早的时候也讲过,所有的replication都是通过networking的,然后networking是unbounded的。所以假设你对这个replication的时候,你要思考一下,就是做entrl要做最坏的情况的考虑最坏的情况考虑就是如果这个情况是最糟的情况下的话会发生什么比如说我们的replication大部分时候都在99%、p99.9%都是一秒钟以下是吧,一秒钟以下,一秒钟已经很长了。但是假设这个replication在这1/1000%的概率的时候,它是几分钟甚至几个小时那会发生什么如果你说没有问题那我们这个系统就非常好了就是这个guarantee是非常好的就是这个guarantee就非常好的就没有什么问题了但是你说这个如果这个delay就是potential的这个replication的delay呢几个小时会有disastrous result那这个系统其实设计的就我们可能考虑一下是不是这个System Design要额外思考一下很多时候大公司现在已有用的这个system design不一定是很好的system design,我们看到很多这个Google的例子,我看到很多Google例子,比如说这个HBase写的时候没有fancing token,然后就导致这个HBase自己写的写的就挂掉了,就它自己的data就crofted了,Google是自己就干过这种事情,就是HBase写的时候,然后其次的话,我记得我早些时候用Google hunt的时候,Google hunt自己动不动,就开始丢message了就比如说我发了一个message然后几小时后才deliver了或者别人发过Message几小时后才deliver了,这是Google之前常干的事。Google相当于是distribute system的鼻祖了,经常干这种事情。没有想法,这个千分之一的case如果出现的话怎么处理。Message Deliver,这个Delay几个小时事情我见过不止一次,我觉得这个做的就比较糟糕。Uber也干过很多很扯淡的事情,比如说Payment走Cafka,是吧?我觉得我当时还是比较惊呆的后来虽然有Lossless Kafka 但Lossless Kafka也有Loss因为这个只要是这个Caseless Kafka,我记得之前也有Loss,它的Loss的概率其实很低而已 [15:00] 普通的 Kafka 落实概率可能是一万百万分之一啊,但是Lossless Kephala可能在这上面再加个千万分之一或者是什么,就是十亿分之一是吧,但是它还是有Loss的,还是有Loss的所以这个就不应该拿 Kafka 作为 kill 是吧? 你还是应该拿 database 作为 kill 再加上 replicate 几次,这个 guarantee 是远远比 Kafka 强的当时就是你 deliver feature 的时候,你一个小组里面可能就一两个人说话算,然后 deliver 做完了以后就发现各种扯淡的事情。还有另一个讨论也是很有趣的,就是说我们Uber不是有一个叫Cadence的那个Flow吗?Cadence的这个Workflow,然后Cadence workflow是用什么实验呢?它的database是用这个Cassandra实验的。所以你 Cadence 是 Guarantee Durability 的, Guarantee Durability, Guarantee State Fold然后你最后你的这个backend用了一个best effort 的一个database你可以想想看这个Cadence的Engineer遇见这种Counter Case是很头疼的,非常头疼,非常头疼。所以他们就是,当时选的时候就说这个为什么选Cadence用这个Cassandra呢?是当时Cadence的设计人叫Maxim,现在他已经跳出去专门做Cadence的startup了。他当时也是Amazon Simple Workflow的Founding Engineer,他在Amazon先设计了Simple Workflow,然后被Uber挖过来做Cadence,现在做开源版的Amazon Simple Workflow。也没有反对嘛,因为他,你想他这个30年经验是吧,然后又是Single workflow 的这个创始人是吧,也是放电池尼尔,他说用 Cassandra 就行,那就是用 Cassandra 结果发现之后的话,好,这个 Connor case 完全没法处理导致现在的话,maintain起来,就出各种各样的问题,然后中间也造成了好几个project rollout的时候 failure,是吧?就是这个counter case完全没法处理。所以我说这个point就是,作为一个Engineer来说,始终要考虑Worth Case的,你百万分之一的这个概率发生,如果这个发生了以后是Dizastrous,那你就是要考虑一下是不是我们要换一个东西,然后其次的话,你其实作为一个Engineer来讲,90%的时间,特别是back-end的时间,90%的时间都在和database打交道,是吧?你这个database选的什么,如果出问题的话,你就不是伤筋动骨的事了,就是整个人要重新写一遍了,整个人要重新换一遍的事。你如果是这个data model换了,就是要做data migration,伤筋动骨的事。你要做database换了,这个整个人都感觉要换一遍的感觉。就比伤筋动股还要严重一下所以这个设计的时候要多考虑一下然后特别是我觉得这个基础的这块基础的东西还是要非常扎实的就是要考虑到可能潜在的情况然后这个是很重要的特别是你的use case比较广的话作为一个好的entity的话往往是批判性是比他这个赞同性要强的就是你批判批判性是永远是比赞同性强的这是我直观的几个想法吧大家这块有什么好的例子也分享一下的话有没有什么更好的例子可以分享一下谢谢大家我看就还有个chat我看一下Amino Simple Workflow是啥啊?Amino Simple Workflow是,你可以搜一下就叫Amino Simple Workflow吧它是就是说本来是一个执行一大段,就是你把所有的那个task,你说先define一个task,一堆task,然后把这个task定义成一个DAJ的graph,然后这Emerald Simple Workflow就开始执行这个task的graph了。它比较适合什么呢?比如说,有些business use case,比如说你发生了一个transaction,你发生了一个 transaction 以后你要 generate invoice 是吧?然后你 generate invoice 以后你还需要去 credit card, swap credit card 是吧?然后你比如说这个,比如说之前讨论的这个怎么样,就是 periodically 每个月的时候你swap the credit card,你就可以用这个 workflow 来做。然后你像整个 Uber Eats 是可以在这个用这个 simple workflow 来做的,可以用 Amazon simple workflow 做的,它实际上也是用 Cadence 做的。这个Durability 还有这个Data Consistency 有时候没法Guarantee的话有各种各样的问题是这么一个东西就说白了是你define task然后每个task是executable不管是做计算还是做存储还是做任何具体的东西然后你再define一个graph然后这个graph是个DAJ然后DAJgraph然后你把这个task的连接 [20:00] 就变成一个 workflow了然后它这个 framework 呢它这个 cadency framework 呢能 guarantee 你这个 workflow 从头执行到尾从这个起点执行到终点有点像一个有线状态机,然后它帮你guarantee这个执行碗建盈说不是Paper吗?不是Paper啊Piper 你可以这么认为,你可以用Cadence写一个Piper因为这个Piper其实是这个Open Source的AirflowPiper是我们copy Airflow 的但是我们copy了一个版本以后,我们就自己做这个版本的维护了你可以认为这个Airflow或这个Piper呢,是可以用这个Cadence写一遍的,就是你在Cadence的基础上写一个Piper并不复杂你知道吧 就在Cadence的基础上写一个Piper并不复杂 是这样子的所以它跟Piper是不一样的Piper的话它现在设计基本上就是执行一些这个batch job什么batch job 我给你举简单几个例子就比如说你现在要sign promotion email 是吧你就是一个batch job 然后就是每天执行下sign promotion email或者是说我看一下哪些user还有欠款没有还给Uber 是吧有欠款没有还 那我就到时候就是每天的时候看到这个欠款没有还进行怎么样处理啊,这种 batch job,它不跟 request 相关嘛,一般的时候我们都说 request response 嘛,就是 transaction request response 嘛,这个是跟 request 不相关的 batch processing,还有一些的话,analytical 这块有很多 paper job他很多时候是把这个data从这个transaction database转到analytic database 然后进行额外处理这个Cadence和Piper的关系是先有Cadence和有Pepper 是吧?你可以认为这个Cadence是Pepper的这个 backbone framework两个是不一样的谢谢大家好,我们接着讨论剩下的这款的话,然后我看一下这款谢谢大家我们讨论哪了,对我们讨论了说这个,对我们要考虑的时候很多时候是worst case,就是如果最坏的情况下发生的话会有什么样的结果,所以说如果是这种情况下有最坏的情况下发生的话,那你不会有很坏的结果的话,那么就没事了是吧?所以最好的情况是什么呢最好的情况是你的application developer就是作为一个application developer你不是infra developer或者不是system developer你不需要关注一些replication issues,就是对你来说,你的replication不存在,对你来说你只有single copy of data,对于developer的角度来讲的话是最好的。我面对的database,我不知道它怎么实践的,我不知道这个底层database怎么实践,但对我来说,这个database做了一个存和一个去一个存一个取 是吧 一个write 一个write 然后我期待于我刚存的data我读出来以后就是我刚存的data 是吧不是一些非常 weird 的 result,这种情况下的话,这个 abstraction 做最好的。所以这就是为什么有 transaction 的存在,是吧?Transaction 这个存在的原因就是,他希望把这个系统中特别 messy 的那部分,特别复杂的那部分,包括 durability,包括主要的 concurrency control,怎么办我这个conference control从database角度就帮你处理掉了所以是希望这样子的但是呢,这个Transaction,其实我看到群里面有讨论说这个Business Transaction 和这个Database Transaction 不一样的,我记得上周也有讨论了,Business Transaction 就是整个订票是一个Transaction,但你这个Database Transaction 是你任何一个Commit,是吧?有很多时候Developer 决定的,任何时候一个Commit是一个Transaction,更多讲究的是一个非常就是任何一个Request 希望就contain了任何东西,你尽量不要有这个Interactive Transaction,尽量是你一个request过来以后就是一个transaction我把你的request全部执行完以后就给你返回你不要这种open的transaction,不要interactive transaction这也是近几十年的一个系统设计的改变吧之前的话,一个大的transaction,就是business transaction,就是database transaction后来整个系统的设计这个观念就改变了好,所以说这个database这块transaction和这个distributed transaction是有额外的讨论的,我们之后的话会讨论一下。现在的话有两个观点,就是有的人就说在这个distributed system里面这个single node transaction不应该存在因为他这个cost太高了但有的人说 [25:00] Single node transaction还是应该存在的,因为它难以避免的会很重要。在distribute system里面,我们还是应该满足这个transaction,但是cross node transaction还是应该满足的。但是这两个观点各有各的好处吧我们在之后的这个Transaction那张也会仔细的讨论一下不同的Transaction我们这个Single node replication讲完了我们现在开始讨论Multi node replication嗯我们说这个Single Node replication呢,就相对比较简单吧,就是一个Node是吧,你所有的写都是写到一个Leader上,然后replicate到不同的Follower上MultiNode replication相对来说是更实用的一个replication现在的话不管是这个Facebook 还是Uber 都是用的MarketNode replicationMulti-leader replication 什么原因呢?就是说你的这个read和read是找你你Lentancy最近的Data Center就是说你的Write和Write同一个用户,同一个User你的Write和Write可能不是在同一个Data Center上有多种原因,一个的话最重要的原因就是它的performance会更好一点,是吧?你可以找离自己更近的这个Data Center你比如说你在 China 的话,你可能在这个 China 的 data center 是吧,你在 US 的话,可能在 US 的 data center。而不需要说,哎,我这个 user wrote 到这个 US 的,但我实际上在 China,那我整个这个 run trip 的话就加200毫秒。the performance就很糟糕了所以更多时候为了performance原因然后其次的话在你Datacenter有outage的话你也可以从另一个Datacenter 说你PrimaryDatacenter Outage的话你就去另一个Datacenter把说的request wrote到另一个Data Center然后再其次的话,这个你有Network的话,比如说你连不到你这个最近的Data Center的话,你可以把你的request wrote到远一点的Data Center并且这个Network,Network是可能trigger一些非常非常weird的issueNetwork 可以吹发出很多VR的意料不管是在这个Rraft,还是在这个Paxos,在Consensus algorithm里面,有一些非常非常worst-case scenario都是由Network来造成的,并且这个Network可以有一个非常简单的Network failure,然后就造成了一个非常非常复杂的一个这个issue,就是这个就不能reach consensus,或者reach consensus的这个收敛速度就很慢了。所以这个Data Center Outage和Network是一定会发生的,所以我们这个Mari Data Center这个Trade-Off呢基本上是业界达成共识的,就是production里面,基本上还是以Mari Data Center比较特别到你到一定规模的时候,这个Mari Data Center是难免的一个一个进层 蓝面的一个里层就是你变成一个market data center就是你的reader和reader可能到roll到不同的data center上那Mati Data Center呢我们可以看一下之前的这个这个是什么呢?就是什么意思呢?就是说你这个rate和rate呢,是写到,比如说我们这个rate和rate是写到这个theta center 1的,你的锐利和Ride都写到DL11特别是Ride写到DL11它这个change 是会Copy到Data Center 1 的Follower 同时在 Cross Data Center copy到Data Center 2 的Leader然后再把这个Change呢和这个Data Center 2的Leader进行就是"Resolve"这些Conflictresolve conflict,我们之后会讲这个conflict这两个conflict resolve 了以后,然后再push到datacenter2的这个follower上所以它是三层的结构其实是四层的结构第一层就是你这个Primary Data Center Leader 是Source of TruthPrimary data center leader 是 source of truth,它是包含了你这个 record,你这个 transaction, source of truth,然后这个 follower 是 copy of source of truth,所以这个多半也是没有问题的,如果这个 data center leader 当掉的话,这个 follower 多半是有最新的数据的,可能会有一些,如果是 asynchronous 的 replica 的话,可能会有一些丢失数据,但是它基本上是拿到最新数据的,因为这个就是in data center replication是时间相对来说是比较稳定一些可控的但是这个cross data center replication呢这个时间是很长的因为你很多时候这个travels的地点也极其远是吧你可能整个要横跨美国大陆啊横跨个几千个mile这个时间本身就很长可能要加个几十毫秒到一百毫秒甚至两百毫秒都可能并且你过来的时候你这个可能就和这个leader呢是有conflict的你需要resolve conflict然后这个Resolve Conflict的以后,然后你再把它写到这个Follower上,所以这个其实是四层吧,就是这是一层,这是一层,这是第二层,这是第三层,这是第四层,其实是四层的这个Data,其实是有四个,其实是有两个Copy的Data两种版本的data 然后再加上两个版本的这个啊就是delayed replica,所以实际上是四个copied data四个甚至更多 [30:00] 就是你有可能这个Leader replicate两份到Followerreplicate到Follower1和Follower2然后Follower1和Follower2的Data不一样所以你在这个实际生活中Practical Speaking你在Facebook的Data你可能有六个Copy是吧你Primary Leader一份然后你Follower1一份Follower2一份然后Secondary Leader一份然后Secondary Follower1一份Follower2一份这六份data可能在某个时间节点上是都不一样的那你如果这个leader挂掉了或者这个leader有conflict怎么resolve conflict的话这个是比较复杂的一个问题也是这个multi data center最核心的问题相对于这个singling data center最核心的问题怎么样resolve conflict好 这块的话就是讲了这个multi leader这个MultiLeader的很多时候这个EU的这个Transactional Database已经支持了像比如说MySQL的话MySQL的话这个有一个External Tool叫Townson Replica是支持这个MySQL的这个MultiLeader的Configuration然后BDR也是支持这个Configuration然后Goldengate的话也是支持这个对Oracle来说是支持这个multi-leader replication所以这个multi-leader replication就是我们怎么样解决conflict是比较复杂的我们之后也会讨论一下怎么它这个conflict可能发生的格式这款是一个具体的use case我就不讨论了大家有兴趣的话可以读一下就是offline-ended的use case就是一个用的 case 具体的 use case另一个 use case 就是比如说 Google Docs 的 edit也是多种人在同时 edit 的同一个 document这个也是一个 use case 我在这块也不考虑了就是不讨论了 我们只讨论这块最核心的问题就是 conflict这块 conflict 就比如说举了一个这个例子User 1 和 user 2 同时在 modify 一个 page这个 page 呢 title 是 A就是User1和User2同时Modify这个Page,Title是A然后User1把这个Title改成B了然后User2把这个Title改成C了然后他们写在自己的PrimaryData Center的时候都没有问题就是在Data Center Leader1这块这个Title就变成B了,在Leader2这块这个Title就变成C了,就写进去的时候没有问题因为你没有任何跟你competing没有concurrent run,没有人跟你competing之后也没有问题的但是它replicate到另一个data center的时候就发现有conflict了因为为什么呢因为它这个old value是从A到C的但是它到这个data center的时候到data center这个到leader 1的时候它这个值已经不是A了已经变成B了那所以就conflict了然后其次一样的这个leader 1的change到leader 2的时候它是从A改成B的但是现在这个Leader2的这个值不是A这Leader2的值是C那改成B的话是不是会有问题是吧这个可以想象一个Account的Transaction就是User1和User2同时对这个Company的Account进行TransactionUser1从这个CompanyAccount滑走了10个MillionCompanyAccount只有15个Million滑走了10个Million然后在这写说没问题我滑走10个Million然后User2又从这个CompanyAccount滑走了另10个Million另外10个million然后一对比说我滑走10个million的assumption是你这个account里面有15个million所以我滑走了10个million但是到这个当你cross data center replicated 过程的时候观点不是这个assumption15个million就不是了因为已经滑走10个million已经变成5个million了这种情况下的话这个就有conflict 是吧你不能这块滑走10个million然后另一个又滑走10个million已经不是了,因为已经滑走10个million,变成5个million了这种情况下的话,这个就有conflict,是吧你不能这块滑走10个million然后另一个又滑走10个million你没有20个million了,去滑,是吧所以就是account的原因这个问题会变得极其复杂为什么复杂呢这个为什么说,我们说为什么说这个和这个single node的concurrency,这个conflict不一样呢是因为很多时候你为了performance你只要在这个primary data center上只要满足你没有任何conflict你就返回给user ok了所以这种情况下的话我们不管对user 1 说你拿到10个million了跟user 2 说你拿到10个million都返回的是 ok我们这时候如果在revoke的话他们可能有其他的promise了是吧比如说他拿了10个million以后他可能已经答应下游的公司说我已经要买你的设备了是吧这种情况下的话你就会有其他问题你这个assumption就被打断了所以这个conflict就必须被resolve这种情况下的话就变成一个死局了你假设自己有20个命令但实际上你只有15个命令但是连续划走两个10个命令并且都返回的是OK好所以说我们在这个market data center的时候 [35:00] 你实际上是Asynchronized Conflict Detection, 不像是一个Single Node, 你是Synchronized Conflict Detection,在Mari-Diesel Center是Asynchronized Conflict Detection所以你这个要想办法解决这个问题是吧你不能两个人request到不同datacenter同时滑走10个million这个问题是不能出现的是吧这种情况下是不能出现的所以说我们怎么样解决这个问题呢最简单的解决方式是减少conflict发生的可能就说我们所有的这个right啊走同样的leader,那这个conflict就不会发生了,是吧?你要划走company A 的钱,那你所有的request都去leader1里面去划,那你这个conflict是没有发生的那你就不可能出现了,因为毕竟Leader1的话,它这个Data是consistent的是吧,最多是concurrency control的问题,两个人要同时滑的话,你做这个transaction的时候,一定有一个人先执行是吧,我之前那个Lead mentor经常跟我说,啊当你有很多transaction不是问题,当你有很多transaction的时候不是问题。但是你有transaction complete同一个object的时候也不是问题。当你有很多transaction complete同一个object的时候,一定会有一个winner。那 winner 会 take everything 剩下的人多半都是 loser 那 loser 的话就得 stand back, stay back, 等下一次再 retry是吧 当你所有request都到同一个datacenter的时候不是问题你competing同一个resource不是问题 但你一定会有一个winner甚至可能没有winner,但最多只有一个winner最多只有一个 Winner, concurrently request,最多只有一个 Winner, Winner will take everything, and then leader will retry, will be a bot.是这样子的结果嗯所以这种情况下的话,如果这个request,就是所有的划钱的request都到同一个leader的话,这个事情是不会出现的。然后如果不是这个样子的话,就是说如果不是这种像划钱的case的话,我这块说,如果是这种strong transaction的例子的话,你基本上只有一种选择,就是说你只有一种选择,就是你说的request要到同一个leader上。基本上只有这一种选择有另一种选择什么是Synchronize Configure Conflict Resolving 什么意思呢?你在Leader1写Transaction的时候,同时在Leader2上也同时开一个Transaction就是这两个相当于是就相当于做 two-phase commit 了2-phase commit了,这个效率是非常低的在Leader1和Leader2同时开一个transaction这个效率是非常低的,所以系统一般是不被考虑2-phase commit一般是不被考虑的所以做transaction的角度来讲的话Multi-leaders setup是非常dangerous的你一定要保证你尤其只有一个datacenter在处理你的transaction当你进行这个Data Center fill over 的时候,你必须保证之前的这个Data Center,所有的这个Change已经 committed了,然后才能开始另一个Data Center的 survey。是吧,这个是有一些timing,如果设计的话要仔细考虑的,要仔细考虑的,你总是有一些engineering solution 能解决的,但是核心的观点就是,你只有在任何一段时间,你只有一个data center在SERV,只有一个Data Center里面的一个Leader在SERV的Transaction,那就不会出现这个Double Spending的情况,这个情况有个专用的名字叫Double Spending,包括比特币设计的时候也提到很多次的Double Spending的情况。好 然后然后其次如果不是这种transaction情况的话,就是你允许这种,就是不是这种transaction,这种非常strong的这个要求的情况下的话你允许有一些不一样的state 是吧你最后只要convert到一个consistent state 去啊eventually consistent 就可以了 是吧只要 eventually consist of a state 就可以的话那这种情况下 我们有多种方式可以解决 是吧比如说这个比如说这个常见的方式的话就是因为你都是两个都是concurrent嘛,我们比如说可以用last right win这个方式Not the right wing,就是说当你有 concurrent right 的时候last write 就是overwrite 之前的write但是这个last right fin是有一个covid,是有一个陷阱的,我们之前最早的一期的时候,就是5月份的时候讲过一期的时候,这个data node,就是这个node的时间是不一样的,所以你很有可能发现这种情况,就是你在nodeA上和nodeB上,Node A的时间,这个 timer,这个clock是比Node B慢两分钟的,有可能。就是Node A的时间是比Node B慢两分钟的。然后你新来了一个request实际上是先写到B上,后写到A上。就是说你新来一个request先写到B上后写到A上从绝对的时间来讲那这种情况下的话那你是不是A的时间应该是最后的结果但是因为A的时间慢了两分钟 [40:00] 所以最后跟B compare,然后又被revoque了,是吧?B的结果变成最后结果,因为A的时间慢了,所以这话其实会有data loss,从你的customer角度来讲的话,从你customer角度来讲,observer的角度来讲的话,我先写了一个1,再写了一个2,最后返回我的是1。就从customer角度来讲,我先写了一个1,再写了一个2,如果用last right win 的话,有非常小的概率,最后返回我的是1,你知道吧?就是你实际上是broke了这个guarantee 的所以任何用Lacelet-Vin的这个mechanism都有这种问题啊,都有这种问题,就是说你实际上是可能是有 data loss 的,并且是非常 subtle 的 data loss 的,你不可能,你很多时候 detect 不出来你有 data loss,这个就是根据这个 clock,就是根据这个时钟有这个,在不同机器上有这个 screw 的原因,就是它就时钟有 drift 的原因,clock drift 的原因,任何用 last right thing,就是当你读这个 database 的时候,比如说Cassandra是用Lasthreat Fin的你知道Cassandra是用Lasthreat Fin的话你本能的想法就是它不guarantee你的data是最新的data也有可能被旧的data覆盖新的data是可能的因为他用 last thread finish 是吧然后其次的话是另一种就是你可以做这个怎么样去detect Winner呢?是你可以给每个 Replica 一个 Unique ID 然后这个 Replica ID 高一点的那就把 Replica ID 低一点的这个 Write 就 overwrite 掉了这个也是有Data Loss,这个Data Loss的可能性更高,是吧?你user给A写了一个值,然后给B写了一个值,然后先写的1再写的2,然后因为这个写1的这个Data ID更高一点,然后就把这个2给overwrite的话,这个Data Loss更明显一点。这个last write win这个Data Loss,比较不容易发现,但是就是你有一个这个概念以后,你一查这个可能性,counter case是有可能用的话,这个是很明显的。就是还是比较致命的一件事然后再其次的话就是最新的话就是有一些近几年我记得可能是14年以后吧有一个叫CRBT的一个model CRDT的一个modelCRDT的一个model,我忘了全称是什么了,后面也有,CRDT的一个model,这个model很多时候也比较直观,有点像这个unordered set,是说你在这个unordered set里面加值的时候,你不管是+1和+2都无所谓是吧,你+1加进去,+2加进去的话,反正这两个共存,就是1和2都是存在里面,那你最后怎么解读是你的问题,这个Uber的Blitz Platform是这么实践的,这个database就很有趣,这个database实践是很有趣的就是说,你现在有个database,你有个resource,比如说这个ID是1,从这个1的object里面写东西你往这里面写东西我接受你写的任何东西你不管这个写是从node1写的还是从node2写的,我都不看,我接受你写的任何东西。就是你写我任何东西我都保留下来然后当你读这个东西的时候,我把你所有的写的record返回给你我们就拿Blitz作为例子,我们就举了Uber的一个例子。Blitz是一个Customer Serving的一个platform,他这个里面呢,可能就是他可能会比如说我要返回这个,我要说这个Uber有一单东西有问题是吧,我要向Uber客服说一件事,我就给他说,我就进建了一个Biz Tickets,我对Uber客服说一件事。然后我们这中间可能有很多交流,不是交流,有很多交流,是吧?比如说我写了一点话给客服,客服写了点话给我,是吧?然后包含有其他user,看了一下这个ticket,是吧,我比如说,我写了一个ticket,然后,然后,比如说Mike看了眼这个ticket,是吧?然后这也是一个action他所有的action,他这个ticket是一个大的hashmap你可以认为这个ticket是一个非常大的hashmap,on order的hashmap然后他这个key是random generated的key,就是hashmap的key是random generated的key然后他把所有的action写到这个HashMap里面所以这个写是很好写的,不管是来自于Datacenter1,Datacenter2,无所谓,我就把你放在HashMap里面,也不可能有conflict,因为这个CaseUID嘛,就是conflict的可能性基本的原因,是吧,我所有的写都写进来。但我进行读的时候,那我就把所有的action读了,这个action包含我所有的交互是吧,我读。我给客服发信,客服给我发信,然后其他用户看这个message,所有这个交互读下来,然后是client responsibility去怎么样显示这个object [45:00] should claim the responsibility. I'm a reader, I'm a client. I read this database. That the database in there is a large number of the pages. What are the actions that we have? It's my responsibility to how to visualize the pages. I want to show that first happened A, then happened B, then happened C. This is my responsibility. It's not the database responsibility. This is a very interesting time. How to say this? The problem is that you have a good place and a bad place. The good point is This is not a source of evidence, this is not the same. You've been starting Let's talk about the good and good and good and good. I forgot about sourcing. What is the good and good? What is the good and good? You don't have data loss. You should write a transaction, it won't conflict. So you don't have to write in the primary data center, primary data center is 1, primary data center is 2, you don't have data loss. um, What is the case? The application developer's all of the application is very complicated. For example, there are application developers who want to visualize the tickets. You need to write a lot of logic, how to visualize it, how to solve it. For example, there are application developers who want to look at the tickets. For example, I want to see who saw the tickets. You want to write a lot of logic, who saw the tickets. Database is a little like a "fuck" feeling. Database infrastructure developer's job is very simple. Just write a hash map on order. But your application developer use it very complicated. You just need to put the hash map on order. And see how to pass the data. Lu Sun, you said event sourcing. You said event sourcing is what? Thank you. Okay. Bye. You follow me on the event trusting link. Thank you. All right. Thank you. Thank you. Hello, I'm going to say that. Event sourcing is the Bitterbit design principle. It is to put all transactions in blockchain. And if you need to balance the current balance he put all the money in the bag of all the transactions in the bag and reconcile to the current balance. Is this one? I don't remember that Sucing and Bitcoin is not related to the same concept. It's a unique concept. Okay. You can see the event solution it was 2005, and the Bitcoin was 2009. It was 2009. It's a different concept, but I forgot what I was going to do. Yes, it has a lot of different concepts. CRDT is a concept. And GitHub is a bit similar. GitHub is a snapshot and a new commit. It is a bit similar. You can have a snapshot and commit a lot, and smash it, and smash it, and then create a new snapshot. There are many places. Yes, but the idea of it is that it is not to have snapshot. It is not a snapshot. It is a full history. And if you need to have snapshot, it is from full history. Yes, it's like this. You're just going to use calculus matchup. This is the point of view. But when you have data to be big enough, Basically, in addition to the project, the project will be able to produce snapshots. Many times, you have to use data for a year, you can't be calculated once again. This is a very low effect. If you have data for a year, you can't be calculated once again. The result is a very low effect. So you can use snapshots. I'll just read this event sourcing. I think it's hard to read after this. That's the... in the chat. Yes, and when we say this config, we have a result of a non-resolve method. Resolve the function, one of the result is you write the result. If you have a conflict, you write the result. [50:00] resolve conflict,就是你在写进去之前然后另一种Resolve的话就在锐的时候ResolveRed的时候 Resolve,比如说我刚才说这个就是写的Ordred HashMaps相当于是在Red的时候进行Resolve Conflict,然后Red的时候 Resolve的话很多时候就是比如说Relational Database,很多时候就是在Red的时候就必须得写一个具体的值了,Resolve。然后有一些自动的这个conflict result方式,比如说这个CRDT就是这个 Conflict-Free Replicated Data Types就是我们之前说的这个,有点像我们之前说的这个,就是这个,就是这个, directory 是吧,叫,on order list 或者 order list,它这块这个 CRDT 呢,它这个支持更多的这个 data type 是吧,比如说这个,这个 order list 啊,这种,啊,支持更多这种的额外的这个 data typeOK然后还有一些其他的automatic result的方式但是基本上所有的这些algorithm都非常年轻非常非常年轻很多时候都在学术阶段的我觉得现在最火的可能就是CRDT的吧这个CRDT在Reddit上用的比较多然后React上用的比较多在 Rack 用比较多有没有在Redis上用? 应该是没有在Redis上用,因为Redis是这个Single Thread的这个Serialization,所以应该是没有在Redis上用,所以在RAC用的比较多。我不记得这块有没有在这个Reddit上运用了然后其次的话像GitVersion的Control啊也是也是用一个相似的这个CVMerge function也是这个Automatic Resolve Conflict的一个方式嗯我们就是讲这个topology, multi-leader topology,这个multi-leader topology也有多种topology,就是什么circular,star topology,outward topology,这个也可以作为一个不同的理解,就是有一个观点,不同的topology也是其中一个比较复杂的一方面,像Uber的topology比较简单,因为Uber就两个data center,所以就是一条直杠线,是吧,就是就没有这个,像这个topology都比较简单,三到四个以上才有这种环形的还是这个start型的还是out to out的Uber的话你可以认为它是out to out的也可以认为是start的也可以circular是吧因为两个server的话这个topology就比较简单了但是有的这个topology会比较复杂的像这个Amazon的topology就相当复杂了这么多datacenter的话我记得Amazon可能有几十个datacenter啊在大陆就整个几个大陆都有是吧然后有几十个datacenter的话这个topology怎么样设计还是很复杂的一件事情嗯你除非做这个相关的一般来说是不会有这个问题的,但是这块可以看到一些这个topology,每个topology不同的地方嘛,比如说你要是这个circular topology的话,你这个data flow方向是一个方向找另一个方向的是吧,就是你这个data replicate的方向是一定是这个每个node的这个replicate的这个方向是,就是最后面的node这个data呢假设你写了data写到这个node的话这个node是最后被reapplicated的然后如果是start topology的话那你这个一般来说这个中心的这个server呢,它这个data是最更新的,或者最up to date,是吧,或close to update。如果是out to out topology的话,你的这个traffic会更heavy一点,是吧,你说的这个request,你要同时replicate,就是同时写到三次,你进out to going的这个traffic,outgoing traffic 会更 heavy 一点但是你这个 replicate 的时间会更快一点 是吧嗯对吧 这是各个的好处好谢谢大家然后计划有一个说是一个multi leader replicationRite to me a RAM at a strong order at the same replica.这块是给另一个例子是吧就是说当你写这个Multi Leader的时候你在这个叫你是multi leader的时候,你当有第三个leader的时候当你有mart leader 的时候 当你有第三个leader 的时候 你这个第三个leader 可能先收到这块的话,这Lead2可能先收到你的顺序是错的,就是和刚才那个casualty violation是类似的例子,跟casualty violation是类似的例子。这也是conflict的一种吧,就是和这个一种,然后他这块的话说这个resolve用version vector这个version vector也是非常复杂的,我觉得这个可能我可能今天还是会跳过,我觉得作为如果感兴趣的话,你可以仔细读下这个version vector,我觉得还是比较复杂的谢谢大家好,我们讲完MultiLeader了以后,讲这个Leaderless Replication我们之前讲的这个不管是single leader 和 multi leader 的 replication 他有个基本的 assumption 基本都是用SYCLE database我突然想要自己的 [55:00] Bis. Leaderless Replication, die ich oft als DynamoDB fühle mich an. Also, wir haben viele Sachen, vor allem, ich persönlich, ich persönlich, auch mit dem eigenen Preferen, ich persönlich habe, ich persönlich nur考虑 die Datenbasis. In-memory databases, Redis, Memcache, leaderless replication, Dynamo-like database, SQL-like database, sharded SQL, escherned SQL. Das ist die 4 verschiedene Database. Und dann, nachdem man die verschiedenen Analyse anwerteile. Wenn man die Analyse anwerteile, kann man sich auch eine Redshift oder Redshift, eine Column-Based Database anwerteile. Aber in den Grundsatz, wir müssen diese 4-5种 Database ausfragen. Es gibt viele verschiedene Database, die sich in den Zusammenarbeit mit einer Use Case zu erzeugen. Die Möglichkeit, die diese Systeme zu erzeugen, die sich in den Zusammenarbeit mit einer Umgebung zusammenfragen. Und die viele verschiedene Gebäude, die sich in den Zusammenarbeit mit einer Umgebung zu erzeugen. Kaffee und Flink, die auch in einem bestimmten Schleuchten zu nutzen. Das ist sehr gut. Kaffee und Flink. Wir sehen uns die Designs, die 5-6 Punkte zu lösen. 5-6个节目解决 Ich habe jetzt noch nicht gesehen, dass eine Design-Bestin-Bestin ist. Das ist nicht die 5-6-jährige Geist. Es ist nicht die optimal Lösung, das ist nicht die optimal Lösung. Das ist nicht die optimal Lösung. Aber in der System Designs, oder in der Arbeit oder in der Arbeit, kann man diese 5-5-5-Database plus die Tools, Kafka und Flink, das kann man sich zu lösen. Das ist alles möglich. Wir haben hier die Leiter-Leist-Database gesprochen. Das ist DynamoDB. Ich wünsche euch, wenn ihr es habt, den DynamoDB zu machen. Das ist ein sehr guter Wissen. und auch eine Leader-Less Replication-Klip. Das Buch ist sehr gut. Wenn die User ein, zwei, drei Mal in den drei Replicaten schreiben, dann ist das Leader-Less Replication nicht ein Leader. Ihr ist die Wahrheit von den Majority. Wenn man die Majority verhältst du das ist, dann sollte man das ist. Wenn man die User-Replica 1,2,3 schiebt, dann die 1 und 2 schiebt. Und zwar ist es Offline, sollte es auch zurückkehren. Das ist schon geschrieben. Wenn es aus dem Linken aus 2345 aus dem Linken zu den Daten. Wenn man diese Daten aus dem Video dann kann, dass man diese Daten aus dem Video dann kann, dass man diese Daten aus dem Video dann kann. Das ist eine Determine Factor. Das ist eine Determine Factor. Das ist eine Version. Das ist eine Last Ride Timestamp. Das kann man sich mit dem Timestamp anwenden. Wenn man die Timestamp anwenden kann, dann kann man die Timestamp anwenden. Das ist eine Clock Drift. So, wenn wir den Leaderless Replication sehen, ist es ein Vorteil. Vorteil ist, dass es Du bist ein Leader. Wenn man einen Server auf der Anwendung hat, kann man auch die Reader und Write Request zu erweitern. Wenn wir eine Replication haben, wenn wir eine Replication haben, dass Replication 3 ist, dass Reader ist, dann wird es nicht mehr Reader. Wir müssen die Replication 1 und die Replication 2 sein. und durch den Consensus Aquarium herausfinden, eine neue Leader zu finden. Wenn man die Replica 3 ist der Leader, dann ist die Service unavailable. Aber mit Leaderless Replication kann man einen bestimmten Replica unavailable. Es gibt keine Single Point Feature, keine Single Point Determiner. Wenn man eine Single Server unavailable hat, dann ist es nicht der Service. Also, das ist das. Leaderless Replication ist eine sehr wichtige Meinung, dass wir diese Daten in der Zeit, in der Zeit, in der Zeit, in der Zeit, in der Zeit, in der Zeit, in der Zeit, in der Zeit, die Daten sind nicht gleich. Es gibt einen Moment, die Daten sind noch mehr als die Daten. Das ist nicht so ein bisschen. Nama. Wir wollen wie die Daten eventuell verifizieren? Das ist eine eventuell consistent. Das ist eine eventuell consistent. Wie soll man eventuell verifizieren? Wie kann man eventuell verifizieren? Wir können eine eventuell verifizieren. Beispielsweise in der Übersetzung, User 2345 Client, [60:00] 两个七的version,一个六的version那他就知道这个6的version是autodate的那他就可以说让6的这个version更新成7的version就这个Client告诉这个Server说你应该更新成最新的version了然后这个就叫Read-Repair嗯然后它不仅可以做ReadyPart,它还可以做什么呢?它可以做一个叫Antitrophy Process.Antitrophy Process 什么呢?就是说它不停地 compare 这个Database 不同的几个 replica 之间的值。它如果发现几个 replica 之间有一个值,不一致的话,它就找见那个不一样的值,然后把它crack掉。这块有个小的trick叫mercure trick叫merc tree,这个merc tree是一个非常高效的能compare出两个replicate哪一块值不一样的一个algorithm, data structure,这个data structure,叫merc tree,然后可以非常高效的compare出,比如说给任何两个replicate,它具体哪一个data不一样,可以通过这个来compare出,非常高效的就compare出了应该是log n就compare出这哪个data不一样然后再根据这两个data不一样的对比一下哪个version更前面那就可以repart这个data了不是所有的data center这两个都有的,不是所有的这个,不是所有的database这两个都实践的。你比如说这个Voltemont的话是不做这个anti-trophied process的,它是没有这个background process的。那因为它没有这个 background process 所以它影响了它的 durability 并且你读到 data 非常有可能是 still 的 data 是吧你就是steal的可能性会增加但是Denimal 是一般这两个周时间就是你rider repair 和这个antitrophic processor 都有的这两个各有各的好处吧这两个结合起来才能guaranteeeventually consistent像Voldemore的话,其实我觉得很难定义成 eventual consistent 的,因为它没有这个 anti-trophied process。但是另一方面来讲,有 anti-trophied process 的话,你实际上这个 anti-trophied process 是个 background process当你这个Background Process 进行 Run 的时候,你多半是要和你这个 ongoing 的 transactions 进行 resource 的 complete 的了。你可能会影响一些 ongoing 的这个 transaction 的 performance 是吧。所以这个到时候 optimize 的时候也会比较复杂。然后这块有个问题啊说如果有一些write field 了五个node只写到了一个那read repair 会不会make mistake 啊这块的话这个就是没有就是这个没有达到 quorum我们说这块这个所有的这个Leaderless Replication是要讲究一个chorum的概念,什么意思呢?这个chorum一般是用户define的,像这个DenimalDB这个设计的时候非常有先见但我毕竟设计设计的时候很多想法是很很有先见的也就是说大部分情况下我们在讨论的例子都是说你要达到这个你Red和Red要达到绝大多数,什么意思呢?就是你要大于N,比如说你RAID和RAID的数量是大于N的像你提的这种例子就是五个node只有一个写成功的话,那么你这个其实除非你这个read呢,除非你这个设计是write=1就可以,read=n。就假设五个node的,你read只要写到其中一个就可以,但是你read要五个都读。嗯你知道吧,就是我们可以假想一下有5个node,下面这个例子有5个node你怎么样能guarantee这个eventual consistent呢?eventual consistent就是怎么样能guarantee quorum呢?就是这个投票的大多数呢你一定要是Red和Red加起来是要大于5当你Raid和Raid加起来大于我的时候你就一定最新的data一定会被读到就是你写进去以后当你读的时候一定会被读到是这样子的,我知道是需要read,read需要corum,但我这里的意思是,就是你第一次read,它有五个notes,然后你只写到了一个,这个read fail了,好像就只写到replica1,那你接下来有另外一个read,然后你read的时候呢,读replica1是读到一个最新,但是并没有write,最新,但是不算是成功write到的一个数据。那这个时候,它会read repair吗?好问题,这个其实是一个很trick的问题,这个其实也是Denemo里面没有回答的一个问题。这个的话这个的话这个问题呢是是这是存在问题有可能是有可能不是因为Denible那篇配合里面写的时候是他是没有就是没有解决这个问题他只是说当你data不一致的时候你应该有个mechanism去解决这个inconsistent data但这个mechanism是只要你有一个人是最新data就应该是把剩下的都否决了还是你应该按majority来吧 [65:00] Hold. He actually didn't talk about this problem. If you can just use that paper to understand 你这个呢是这个data呢 you do a red repairer, it will make mistakes But from the paper that I wrote about the whole thing, you can make mistakes. Let's give a example. You have five nodes, and the four fields are filled. And you this red actually think be about it right but you have a note written in the other words in the animal when you have a note written in the other words you will not be revoked You write it out after you read it, you write it. Actually, transaction from DynamoDB's perspective, it's not a problem. It's an uncommitted transaction. But you read it. If you read it, you read it. If you read the three and read the four and read the five, it would be better to divide. Because if you read the four, you will find the majority is not the version. You can also repair it. 但是最worst case 是读了三 就是你的read和read 都写的是三 结果你读了三 读了三的话 你有一个新的version 两个旧的version 你这种情况下 是区分不出来 这个新的version 是不是真的新的version 它有可能是 about it, commission, but there's a lot of other three nodes that are written in the version. But the two nodes are filled. I just read the version from this node. I read the version from the two nodes. I read the version from the two nodes. In this case, you can do read and repair and then cover it into the version. - Actually, I saw the Nino-Risability that was mentioned in the beginning. It was actually related to the meaning of this. But I probably didn't express it. - Yes, this is a very valid question. This is a very countered case. You actually from Dynamo, when you write it, you have no reach out. It's just five nodes. We'll talk about this one. If we have 5 nodes, 5 replicas You write to 3 to successfully write You write to 3 points to success for the rate. When you write to write, you write to the replica 3 points. The other write to fill all the points. From the right perspective, it actually is filled out of From right perspective, it's fill-in. If you write version 7, and you write version 6, you think 7 is fill-in. You can read the database, it is 6 or 7. In DynamoDB, the leaderless replication is not reversed. So you read the reader, you read the 3, 4, 5, right? So you read the 3, 4, 5, right? So you read the 3, 7, and the 4, 5, 6. In this situation, the reader is not sure 1 and 2 are 7 or 6. If it is 1 and 2 are 6, it would be better because majority will decide it is 6. But he doesn't know that 1 and 2 is 7 and 6, he can only imagine that 1 and 2 is If you have 7, you can probably put 7 in the middle. It's a 6.0.0. The about it write, you return to the user, I said I wrote about it. But the fact that the write is successful. It's successful in this particular situation. So this is a very counter case, but you can see Denimal DB or Leaderless DB, we can't guarantee any transaction. We just want to talk about transaction guarantee. It's actually very difficult to guarantee any transaction. I hope that this time we can understand the question and the more complex. Thank you. Let's look at DenimalDB. The red and red corner is very interesting. If the red is in the underlying state, it will be in the underlying state. You can tolerate available nodes and ensure that you can read the data. You can read the data from the last committed data. There are some more interesting things, right? For example, if you have three nodes, you can guarantee one node unavailable when you can do this transaction. If you have five nodes, you can guarantee that you can rate and write in two nodes unavailable when you can do this transaction. We can read and write. So this one gave a very good example. This is a simple example. You write to Replica 1, 2, 3, and you read to Replica 3, 4, 5. But because this one This is the same as the "Successful" version. So, at least there is a replica that you can see the latest "Successful" version. So this is what it is written in DynamoDB. And there is also a very interesting example. [70:00] 在这看这一块,你其实这个W和这个就是Write和Rate是没必要一定大于N的如果你就是说我就破罐子破摔了是吧就是说,我不要求任何guarantee我不要求任何,有些时候是有可能有这种use case我不要求任何guarantee是吧那我W和R是可以小于N的其次的话,W和R有一种特殊的case比较有特殊的case,就是比如说你Write写的任何一个node就可以了但是你Rate要把所有的node全部读一遍然后找出最新的写的也就是说你比如说五个node你write是写到其中任何一个node就可以了那你这种就是当你write heavy的时候write非常heavywrite非常infrequent的时候你write任何写了一个node都可以但你write的时候你就把所有的node涂出来然后找出这个值到底应该是什么这个也是能guarantee这个corum的然后另一种情况下是反过来就是你write是要写到所有的里面就是你所有的write要写到五个里面必须有五个全部写到但你RAID是可以从任何里面读这个就比较适合RAIDHeavy的UseCase你RAID是可以从任何里面读的但是这两个Case我觉得只Case呢它其实只是一种我觉得只是一种这个这个这个Corum的这种这种这种就是玩数字这种游戏因为这个这个整个这个Leaderless这个Replication设计的最终最初目的是TolerantTolerate这个Unavailable node同时让整个ServiceStill Available如果你是单纯的说这个SkillsSkills和Skills那你其实有不同的方式来解决比这个Leaderless Replication更好解决所以它这个更多是感觉是一种玩数字的游戏就是把这个WR随便设置想一下极端Case但是真的是Read Heavy 和Read Heavy的话你可以有不同的方式来解决没必要用这个Leaderless Replication它更多时候是tolerate这个unavailable node的 performance更好一点好这个Leaderless Replication有很多问题比如说你这个sloppy column read的时候你可能会有问题什么叫sloppy column呢就是我们来再讨论一下再来看这个例子就是很多时候在想很多时候有人问这个Leaderless replication它scalability是多少其实它scalability应该是Infinite scale这个Leaderless replication应该是Infinite Skill为什么呢你这个机器呢你可以思考一下每个Data每个Data它到底是划分到哪个哪五个Replica上其实是可以由系统决定的你比如说你任何一个Data任何一个Rate和Read我可以给你生存一个YoyoID给你生存一个ID然后我根据你的ID来决定你到底是哪五个Replica来Serve你这个可以 further skill 是吧你没必要说你不需要说整个DynamoDB就五个replicaDynamoDB可以有infinite replica你可以有上万个replica实际上DynamoDB确实有上万个replica你可以有上十万的replica上百万的replica都有可能但你具体的哪一个data你来了一个data那我这个data面对的具体是哪个machine的话我可以有一个hash 是吧我可以根据一个hash来决定这个data是我hash了一下这个data然后根据它ID前几位我决定它具体是拿几个Machine来Serve它是吧然后可以通过这个来决定那么我们会意识到这个replica在整个DynamoDB里面的replica数量是完全自由的所以它的数量应该是远远大于你这个n的数量的就是对于particular data对于具体的一个data你的这个replica具体值的replica数量就是远远大于这个的那么当你就是当你比如说假设这个是n等于5的话假设你正好这三个replica全部down掉了就是345全部down掉了那Denimal呢它设计的最重要的原因就是guarantee availability所以它就采取了一个折衷采取了一个什么折衷呢就是说假设我现在数量是不到这个corum不到这个corum数量我可以借用其他的replica就像说我现在想回家但我家那个钥匙丢了我家钥匙丢了我先去我邻居家沙发上税然后等我家钥匙早见的时候再回自己家税这种感觉一样所以这种情况下假设这个replica345全部down掉的话DenomDB的client可能会借用replica6去replica6来写这个data这叫sloppycorum就是sloppy,就是没必要严格执行这个corum去其他家的沙发上借数一晚上的时候当你这个replica3又backup回来的时候当你replica3又恢复的时候再把这个replica6的data再copy给replica3所以这个叫sloppycorum所以这个sloppycorum当你有sloppycorum的时候你可能会发生一种 [75:00] 很奇怪的情况是吧你这块写的时候你写的data是写到1和2和6上的你注意到 写的电台写的1和2和6上的但你读data的时候读的是三四五的data所以你这个写是完全没有被涂到的因为你有 Slopey Corum,所以你从这个Ride的角度来看,Data是写到1,2,6上的你写的时候写到126,但是你读的时候从345上读的,这个chorum是没有达到的这个其实很常见你想一下你如果有个network partition把1 2和3 4 5分开了这个情况就发生了是吧,你有个Network Partition,把1,2和3,4,5分开了,那你写的时候只能,1,2不够了只能进入一个6,但你读的时候从3,4,5读,那这个Data就不一致了,所以这个Network Partition很容易使这个sloppy column发生嗯好,然后其次的话,这个DenableDB还有什么可能的情况呢?就是这个Data Loss,发生Data Loss,就是当你有同时的很多 concurrency right的时候,你实际上根本区分不出哪个right发生最先发生的。那你可能每个replica的话,每个replica它可能有clock school,然后就导致这个last right win就可能会loss。就是你如果你这个conflict resolution是last right thing的话你有可能loss我再强调一下,Tenemodb它设计中它这个Conflict Resolution是由用户自定义的,您可以是比如说Version是可以的,你可以LastThreadWin,甚至您可以写个Conflict Resolution的Logic你可以写的,在那个Cassandra中好像Default是LastThreadBin,在Cassandra的Default是LastThreadBin,所以这两个是不一样的好然后我们再读一下其他的几个Case,我看有什么其他有趣的Case,你没必要理解所有的有趣的Case,但是有些常见的Case,你可以理解这个,我们把DenemoDB或Leaderless Replication叫做Best Effort,就是你不能guarantee你读到最好的最新的。好所以像第三个例子就是刚才静怡说的例子就是当你写的时候你只写了其中一个但是当你读的时候呢,你实际上读的时候,你在测证情况下,你可能有Dirted Read的,甚至这次ReadedPier的话,就相当于是把一个Uncommitted的Change然后 committed 掉 这个是非常connor 的 case然后DinomDB即使任何事情都是 working correctly 的话还是有age cases,然后可能timing不好,然后就导致你这个data是crafted data,所以任何用Denimal DB或者是Cassandra这种leaderless database做这个transaction都是勇士啊,我们之前已经有Cadence这种各种头头的countercase作为先例了是吧,作为Cadence,我知道下图有些组这个engineers在这个Cadence组做工作已经是特别痛苦是吧,所以说这个任何敢用这个Leaderless Replication做Transaction, Business Transaction都是勇士啊。大家知道它是一个Best Effort的这个Database就可以了我看看这块 Sloppy Corum 和 Hinted Hand-off 我也讲过了就是当你不组 Corum 的时候 你要借用一个 Corum然后谢谢大家这个的话就是DenimalDB另一个例子了,就是当你写的时候,因为它是leaderless transaction,leaderless replication当你同时写的时候,比如说这个情况下,node1写了A,node2写了B,然后在Node1写了A,然后Node3写了B,在Node2上就发生冲突了那这种情况下的话,你其实是区分不出A和B,谁是应该是最后的这个的话是完全没有办法区分A和B谁是最后的你如果是按大多数的话可能是A,但是如果按timestamp的话有可能是B是吧嗯真的是看这个config resolution是怎么定义的所以这个DenimalDB为了甩锅呢就是叫你client自己来写config resolution所以它也是为了甩锅吧,这个database的话很多时候是不想承担这种特别大的风险的他当时举了好多例子呢,就是这个Shopping Card Checkout 的例子,就是在PayPal里面举了很多例子,Shopping Card Checkout 例子,就是比如说,你这个A的这个Transaction,A的这个Transaction可能是加一个Node,是吧?B的这个Transaction可能是从这个Shopping Card里面,就是 remove 一个 item当你进行conflict reserve的时候,你remove的item,如果发生conflict的话,是一定不被remove的。就是说,它是跟它business use case相关,就是说,如果我再加一个item同时, [80:00] 就是捡了一个item,然后我系统没法判断出谁先谁后的话我就假设这个家的itom在最后了就是说白来是为了增加更多的消费嘛也跟business use case 相关所以这个所以这个Database设计,Config Resolution这款也是和你的Use Case相关的。好,这个就是对刚才的例子的更多的解释最后这个非常复杂的version vector这个是一个 single node version vector我今天不打算讲进来,因为我觉得讲这个的话,首先要把我自己绕进去,然后其次的话大家可能会被绕进去这个还是很复杂的。大家可以有空自己看一下吧,你可以想像说这是一个Single node Casualty Dependency 的这个Version Vector如果你把它copy到N个Node的话,就是这个Version Vector了,这个复杂度是非常非常高的。嗯我可以做的事吗?好我想说这个version vector和lan pod的timestamp是不是同一个东西啊,lan pod的timestamp是是另一个东西那个比较简单,这个的话,Virtual Vector的话,是Concurrent Red,就是这个Resolution的,这个是比较复杂的一个东西,如果感兴趣的话可以读一下,我觉得读一下也挺好的,可能帮你思考一下这个Multi Leader的这种各种conflict会出现各种什么样的奇葩的情况我可以再用一次的这也是最后一个slide了,然后大家有什么问题的话,我们讨论一下,没有的话,今天就到这为止好了。我这边的话还是跳了很多slides了,我觉得多种原因吧,我这块就是列出来的话还是给大家一个有机会的话可以读一下,特别你要是没有这个书的话或者是你感兴趣的话可以读一下,但是因为它复杂度太高,我觉得我自己读一遍有时候容易把自己也绕进去的时候可能就不讨论了,其次的话我觉得这块的话很多时候是对于这个high level的这个知识点讨论和这个普及,很多时候讲的是这种方向是吧,如果我走太多细节的话,太过于细节的话,很多时候就之前,如果你是没有学过这个概念的话,大的方向可能都会迷失掉。所以我们再到今天的这个agenda里面回顾一下好在最后的时候再回顾一下所以这个replication是有多种replication的,一个是最常见的sql是有single leader replication那就是有一个leader然后他的data是replica有几个followers上然后你可以 asynchronous replicate,也可以 synchronize replicate然后这时候有不同的这个fileure handle方式是吧并且你replicate的话不管是用statement log 做replicate还是用这个write hand log 做replicate都不太好所以他们因此发现了发明了一个就是发明了一个新的就是设计了一个新的replicate log就是专门是做replication log然后当你做replication的时候你就多copy of data所以你可能会有不同的consistency level是吧然后这块的话就简单介绍两个consistency level一个是radial rate还有一个monotonic rate然后Single Leader Replication,大部分小公司已经足够用了,就是你做个startup几千人,几百人就足够用了,但是对于这种特别大的公司,比如说Facebook和Uber的话,你这个Single Leader Replication也是不够用的,因为你的availability会很糟糕,当你这个Single Leaderfuel掉,single-party failure fuel掉的时候,你虽然能再选一个leader出来,但是你这个中间会有几百毫秒,甚至几秒的availability,所以你这个performance是很多时候不可以接受的。那这种情况下就需要Metiliter ReplicationMetiliter Replication呢基本上也是 SQL 它只不过是多了一层就是 cross data center 这个 replica 然后你会有 conflict 所以呢Multi-Leader Replication大部分时候都讲怎么样解决这个conflict的最好是没有就是最好最好的情况就是当你做transaction最好的是没有conflict就所有的写是固定的road到一个leader上的但是如果你能允许适当情况下的conflict error,是吧?就比如说不是这种payment transaction,是吧?你可以做成这种CRDT这种,或者我们之前讲的blitz这种,就是un ordered hash set你所有的写全部recorded 但你读的时候进行这个resolving, resolve conflict然后最后的话讲了这个Leaderless Replication 主要是Denimal,我是强烈建立大家去读下Denimal这个Paper,读前会很累,可能会如果之前没有读过Paper,像我第一次读,我读了大概十几个小时读了十几个小时放的感觉,但是读了以后会看到很多,很多,很多这个就是能学到很多东西啊, [85:00] 设计理念,它整个设计就是围绕在怎么样提高这个database availability 上设计的所以它不管是choram,sloppychoram,还有包括这个read repair,antitrophia,设计非常轻盈的设计然后但是我们说这个leaderless replication database它是一个best effort database它和之前两种database是不一致的就是single leader 你可以认为是基本上可以guarantee任何 transaction就是你需要根据不同的这个Durability要求你可以需要Trick一下但是你可以Garantee任何Transaction但是任何讲把那个Business transaction特别关于Money相关的Transaction要做成用Leaderless Replication的Database来实践都是勇士啊基本上都死得很惨然后最后的话就是它这个Leaderless Replication有个Concept叫Eventually Consistent它通过Read-Repair或者Enteritrophied的Process然后导致这个SteelData被Repair掉然后它这个Leaderless Replication不管是TenableDB还有Consentral都有个Steel那种Monitoring它也可以看它这个data到底有多少的still,然后来决定是否是增加就是增加它这个conver的速度啊什么的,可以有一些fantune,就是可以更tune的更细一点是吧,可以tune的更细一点,使这个database的performance来说更好一点,特别是这个data的consistency更好一点。这个replication就讲完了,我还是建议大家再反复多读几遍,就是high level的话基本上cover了所有的replication但是可以做足几遍我觉得我今天要有个读的就是想读一下event-sourcing我忘了这个具体怎么回事了嗨,Scott,我这里有个问题你问好了是这样子的就不是那个replicationreplication那一张的问题是我看到后面consistency and consensus 那一张他让你有介绍distributed transactions and consensus他先是介绍tofaces commits然后是for tolerance consensus algorithm那我这里是想搞清楚一个概念是tofaces commits是可以保证在这些replicated system或者database里面它是一个能够保证一个比较strong的consistency而是它在没有2PC的情况下面用fault tolerance consensus algorithm来推而求其次2-face commit 是能保证 cross-shard就是cross nodes 的transaction它是一定能保证的但它经常不available然后它效率非常慢但是它能保证这个就是cross node的transaction那是不是在没有这个的情况下面才会有下面的consensus algorithm呢2-phase commit和consensus好像不是同一个概念consensus更多的是找一个total order它和linearity更相关还是这个概念还是有点有些相关但又有些 orthogonal就是那 consensus 在什么时候应用呢?consensus 更多时候就是比如说你有一大堆 replication你现在有一大堆 replication log就是比如说 single leader replication你有一大堆 replication log那怎么样决定这个 replication log谁在谁的顺序是怎么样的?就是 csensus这个是consensus或者是说你现在有一大比如说你single leader 的一个 database就是然后这个 leader 挂掉了你这么多 follower决定谁是新的 leader这也是 consensus对但是他们两个是不相关这个我很难用语言来形容出,就是说两个是有相关性的,不是Orthogonal的问题但是Linearability不guaranteeTransaction,Transaction不guaranteeLinearability我给你举个例子,Linearability和Transaction是不guarantee的,就是两个是不完全相互guarantee的有没有又Guarantee Linearability又Guarantee Transaction的呢那可能只有Two-Phase Commit了就是Guarantee这两个Two-Phase Commit可能是Linearability和Transaction都Guarantee的但是这个Performance是最糟糕的我给你举个例子就是Guarantee Linearability但不Guarantee这个Transaction比如说你当你有MarketNode的时候当你有多个Node的时候就是多个DataRecordUpdate的时候 [90:00] 那你很多时候你这个Linearability是可以guarantee的但是你这个Transaction可能没有guarantee 比如说嗯对这个可能是没有garantee然后在很多时候是transaction能garantee但linearity不能garantee比如说当你做这个snapshot isolationSerialized snapshot isolation database 时候是吧,你的Linear Abinitation 没法 加 产 提的对吧对的,谢谢好这个问题比较复杂,它这个细节也比较多哇并且加上terminology比较复杂的话,这个问题确实比较复杂但是我觉得好处是没有人会问你这么细的,我记得,其实我研究是比较细的,我当时做写一个前两个月写的一个这个Design,我这个Design写的特别写的,我大概写了50多页的一个Design,因为比较大的一个系统,然后我去那个就是我们这个Level 2的这个Community去,去这个review 这个 design 了,然后他们这块 Level 2 Committee 但不是都是 staff engineer 吧,都是二三十年工作经验的,这个非常 experience engineer然后我就说这个last right win这个algorithm是不能guarantee不能guarantee data cost,就是没有data loss的,我就解释,大家很感兴趣然后基本上所有的stuff都很感兴趣,然后就花点时间解释好就是为什么这个last right win 是不能guarantee这个没有data loss 是吧?就是解释了很多,然后其实看到这个做工程做多了以后这种非常细的东西理解是好的,但是它肯定大部分人是不理解的,大部分人是不理解的,你不理解也没事儿,但是你理解了肯定是对你有好处的,就是你至少不会给队友埋坑是吧,不会埋一个很大的坑在那里谢谢大家好 有没有什么其他问题好谢谢大家 谢谢大家好,那今天就到这里吧,谢谢大家,非常感谢。