分布式异步复制暗藏脑裂风险 专家解析主从一致性权衡

[Chinese] Chapter 7: replication part 1 — Scott Shi

Scott Shi

异步复制虽能降低网络延迟,但主节点故障转移与自增主键结合极易引发数据丢失与跨租户安全泄露,系统架构必须依赖逻辑日志与精准读路由机制兜底一致性。

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

Source published:

Key ideas

  • 分布式存储的复制(Replication)核心在于缩短跨地域网络延迟、实现高可用容灾及提升读吞吐量,但异步复制带来的最终一致性窗口是并发数据冲突与 Bug 的主要根源。 (5:00)
  • 故障转移(Failover)中超时阈值的设定需权衡系统停顿与误判抖动,硬编码超时易引发脑裂(Split-Brain),工业级实践更倾向于使用自适应故障检测算法。 (25:00)
  • 基于语句(Statement-Based)和预写日志(WAL)的复制方式因副作用及强绑定物理数据页正被淘汰,行级逻辑日志(Logical Log)因与存储引擎解耦和支持零停机滚动升级成为主流。 (55:00)
  • 复制(Replication)与事务隔离级别(Isolation)属于正交问题;写后读不一致与单调读异常可以通过区分自身/他人数据路由、内存时间戳过滤或会话固定路由等工程手段来缓解。 (60:00)

Detailed notes

## Background 本内容由讲师 Scott Shi 于 2020 年 12 月 8 日发布在 YouTube 平台。视频附带了演示幻灯片,主要内容聚焦于分布式系统设计中的数据复制(Replication)机制第一部分。

## TL;DR 课程在开篇简要体验了早期 OpenAI API 并探讨其对研发的影响后,全面切入分布式复制的核心议题。讲师系统剖析了单主、多主与无主架构,并对比了同步、异步与链式复制在延迟与可用性上的权衡。随后深入拆解了新节点接入、心跳超时、脑裂风险以及基于语句、物理预写日志(WAL)和行级逻辑日志的演进过程,并结合 GitHub 真实安全事故说明异步复制丢失数据的隐患。最后,内容辨析了复制与事务隔离的边界,阐释了写后读与单调读等经典一致性难题,并结合 Uber 与 Facebook 的实际场景对比了全球化架构的选型思考。

## Atmosphere & style 讲座呈现出技术交流会特有的亲切与硬核感,开篇借探讨 AI 会否取代程序员的互动迅速调动起轻松活跃的气氛。讲师风格从容稳健、抽丝剥茧,善于引用墨菲定律与“黑帮老大死而复生”等幽默比喻将枯燥的并发与一致性概念具象化。伴随着 GitHub 生产事故深度复盘和 Uber 大规模数据中心演习等工业界血泪史的展开,整场内容兼备扎实的底层原理推导与高度接地气的一线工程实战质感。

## Bulletin - [05:00](https://www.youtube.com/watch?v=SrnRA3JLjIA&t=300s) 分布式存储的复制(Replication)核心在于缩短跨地域网络延迟、实现高可用容灾及提升读吞吐量,但异步复制带来的最终一致性窗口是并发数据冲突与 Bug 的主要根源。 - [25:00](https://www.youtube.com/watch?v=SrnRA3JLjIA&t=1500s) 故障转移(Failover)中超时阈值的设定需权衡系统停顿与误判抖动,硬编码超时易引发脑裂(Split-Brain),工业级实践更倾向于使用自适应故障检测算法。 - [55:00](https://www.youtube.com/watch?v=SrnRA3JLjIA&t=3300s) 基于语句(Statement-Based)和预写日志(WAL)的复制方式因副作用及强绑定物理数据页正被淘汰,行级逻辑日志(Logical Log)因与存储引擎解耦和支持零停机滚动升级成为主流。 - [01:00:00](https://www.youtube.com/watch?v=SrnRA3JLjIA&t=3600s) 复制(Replication)与事务隔离级别(Isolation)属于正交问题;写后读不一致与单调读异常可以通过区分自身/他人数据路由、内存时间戳过滤或会话固定路由等工程手段来缓解。

## Key takeaways - [10:00](https://www.youtube.com/watch?v=SrnRA3JLjIA&t=600s) Replication 的三大核心驱动力为地理亲近性(削减跨洋网络数十至上百毫秒物理延迟)、高可用容灾以及按需水平线性扩展读吞吐量。 - [10:00](https://www.youtube.com/watch?v=SrnRA3JLjIA&t=600s) 工业界主流的三大复制流派包括单主复制(Single-Leader)、多主复制(Multi-Leader)与无主复制(Leaderless,如 Cassandra 和 Dynamo 风格)。 - [14:48](https://www.youtube.com/watch?v=SrnRA3JLjIA&t=888s) Replica 与 Cache 存在本质差异:Replica 具备在 Leader 宕机时随时晋升接管的 Failover 资格,且在复制时严格保持跨行事务边界的一致性。 - [17:45](https://www.youtube.com/watch?v=SrnRA3JLjIA&t=1065s) 半同步复制在写延迟与高可用之间做出折中,而链式复制(Chain Replication)通过 Head 处理写、Tail 处理读的线性流水线结构,换取了天然的强一致性视图。 - [20:00](https://www.youtube.com/watch?v=SrnRA3JLjIA&t=1200s) 为集群平滑添加新 Follower 需借助一致性快照(Consistent Snapshot)和增量变更日志流重放,避免传统维护窗口中长时间锁库甚至停机的弊端。 - [25:00](https://www.youtube.com/watch?v=SrnRA3JLjIA&t=1500s) 故障转移的核心危险在于脑裂(Split-Brain),多分区高并发集群若遭遇网络分区,大量分区同时选举会引发控制面与网络的雪崩效应。 - [30:00](https://www.youtube.com/watch?v=SrnRA3JLjIA&t=1800s) 异步复制在主节点宕机后会导致未同步数据丢失,若配合自增主键(Auto-increment ID)使用,可能引发类似 GitHub 源码泄露的严重安全穿透漏洞,采用 UUID 可有效防范此类风险。 - [40:00](https://www.youtube.com/watch?v=SrnRA3JLjIA&t=2400s) 故障检测超时阈值设置需用极限思维权衡,固定阈值容易受到网络毛刺干扰,Cassandra 等系统采用动态自适应故障检测器(如 Φ-Accrual)来平衡检测速度与误判率。 - [45:00](https://www.youtube.com/watch?v=SrnRA3JLjIA&t=2700s) 基于语句的复制(SBR)由于存在非确定性函数(如 NOW()、RAND())、严格顺序依赖以及存储过程/触发器的不可控副作用,已基本被现代系统弃用。 - [50:00](https://www.youtube.com/watch?v=SrnRA3JLjIA&t=3000s) 基于 WAL 的物理复制强依赖底层的二进制数据页结构,导致主从节点必须保持完全一致的实现,严重阻碍跨版本的零停机滚动升级。 - [55:00](https://www.youtube.com/watch?v=SrnRA3JLjIA&t=3300s) 逻辑日志(Logical / Row-Based Log)与具体存储引擎解耦,仅描述行级语义,不仅支持异构引擎与无痛滚动升级,也催生了变更数据捕获(CDC)流式架构。 - [01:00:00](https://www.youtube.com/watch?v=SrnRA3JLjIA&t=3600s) 复制(Replication)与事务隔离级别(Isolation)完全正交:隔离级别在主节点裁决事务并发竞争,而复制层仅负责把已提交的数据分发至从节点。 - [01:00:00](https://www.youtube.com/watch?v=SrnRA3JLjIA&t=3600s) 解决写后读(Read-Your-Writes)不一致的常用策略包括:用户自身敏感数据直连 Master、基于 Redis/Bloom Filter 记录更新时间戳进行短时读主分流。 - [01:10:00](https://www.youtube.com/watch?v=SrnRA3JLjIA&t=4200s) 单调读(Monotonic Reads)用于防止连续读取中发生“时光倒流”,最直接的实现方式是将同一用户的读请求基于哈希固定路由到同一个副本节点。 - [01:20:00](https://www.youtube.com/watch?v=SrnRA3JLjIA&t=4800s) 现代数据库架构已彻底弃用占用连接且风险极高的“长事务”,全面演进为业务由应用层管理状态、数据库仅执行毫秒级“短事务”提交的模式。 - [01:25:00](https://www.youtube.com/watch?v=SrnRA3JLjIA&t=5100s) 全球化架构设计需根据业务读写比和关系复杂度选型:Uber 模式偏向基于地理就近路由,Facebook 模式依赖写后读保护与反范式化读取,若无法优雅化解多主写冲突则应优先选用单主或 CRDT。

## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=SrnRA3JLjIA&t=0s) OpenAI API 与代码生成工具的初体验

在正式进入分布式系统的正题之前,讲师先向大家分享了一个最近正在探索的有趣工具——刚开放不久的 OpenAI API(当时处于早期测试阶段,仅向大约四五百名 Beta 用户开放)。讲师现场演示了它的能力,说明它本质上是一个基于深度学习的模式识别(Pattern Recognition)和“文本进、文本出”(text-in, text-out)系统。在补全测试中,输入 "I think",模型便顺理成章地补全出名言 "therefore I am";在输入 "iceberg" 后,模型也能返回类似 "what we know is a typical iceberg" 的连贯文本。用户还可以通过调节参数来控制输出结果,例如调整 Response Length,或是通过 Temperature 来控制输出的随机性(Randomness)。

除了简单的词句补全,讲师还演示了模型的文本分类(Classification)能力:输入 Facebook,它能打上 Media 和 Technology 的标签;输入 FedEx,它准确归类为 Logistic Transportation;讲师还随手测试了一家游戏公司 EA(Electronic Arts),模型也精准识别出这是一家 Video Game Company。此外,该 API 在文本摘要、命令行生成、问答系统(Q&A)以及聊天机器人(Chatbot)上的表现都相当惊艳。讲师提到社区里有人用它生成音乐、生成创意,甚至利用 React Component Generator 直接生成前端代码。针对弹幕中“程序员是不是快失业了”的调侃,讲师笑着安抚大家:“不会的,它只是 generate 已有的 code。”比如生成简单的 HTML 骨架和基础引用,但编程中最复杂的核心在于业务逻辑(Logic),这是目前模型无法轻易取代的。讲师表示如果听众有好的 idea 想做实验,他很乐意在群里分享自己的 API Token 供大家体验。随后,讲师对近期的课程进度作了简要同步:本期聚焦分布式系统中的 Replication(第 1 部分),而下周针对应届生 Leo 的模拟面试(Mock Interview)则可能会考察 TinyURL 或 Craigslist 这种相对基础的高频系统设计题。

- OpenAI API 早期特性:提供 Temperature(随机度)和 Response Length 等 NLP 核心配置项。 - 核心功能场景:文本生成与补全、实体分类、文本摘要、Q&A 问答及前端代码生成。 - 行业影响探讨:AI 能够加速模版化代码生成,但系统设计中最复杂的业务决策与核心逻辑依然需要工程师来主导。

---

[05:00](https://www.youtube.com/watch?v=SrnRA3JLjIA&t=300s) 为什么要 Replication:分布式系统的核心挑战与物理法则

课程进入正题,Replication(数据复制)与 Partition(数据分区)是分布式存储中不可分割的两大基石。讲师直言,Replication 往往是整个分布式系统中最容易滋生麻烦和 Bug 的温床。在单机系统里不存在多副本同步问题,但在分布式环境中,数据和代码都存在多版本演进,并且“数据往往比代码活得更久”。当多台机器之间进行数据拷贝时,大部分场景采用的都是异步复制(Asynchronous Replication),这就直接引出了“最终一致性”(Eventual Consistency)的概念。只要复制存在延迟,就会出现数据不一致、并发控制失效以及各种各样的写入冲突(Conflicts)。讲师特别提到 DynamoDB,他更倾向于将 DynamoDB 称为“Dynamo 风格”或“无主复制”(Leaderless Replication)数据库,而非单纯的宽列(Wide-Column)或键值(Key-Value)数据库,因为其系统架构的核心设计初衷正是为了应对 Replication 带来的挑战。

为了强调分布式系统的严谨性,讲师引用了道格拉斯·亚当斯的名言:“事物可能会出错与绝对不可能出错之间的主要区别在于:当一个绝对不可能出错的事物出错时,它通常会变成彻底无法挽回的灾难。”在分布式系统中,必须清醒区分什么是“可能出错”,什么是“在物理法则下绝不可能出错”。著名的墨菲定律在分布式领域体现得淋漓尽致——"What could go wrong, eventually will go wrong"(只要存在出错的概率,给它足够长的时间,它就必然会发生)。因此,在设计保证数据绝对安全(Data Safeness)和绝对正确的系统时,任何边缘容错场景都必须预先考虑周全。回归本质,Replication 就是通过网络将同一份数据冗余存放到多台物理机器上,其核心驱动力之一就是缩短用户物理距离(Geographical Proximity)以降低网络延迟。以 AWS 为例,全球分布着几十个可用区(Availability Zones);反观早期 Uber 只有 Phoenix 和 DCA 两个主力数据中心,如果一名俄罗斯用户发起打车请求,网络数据包跨越太平洋传输,仅端到端网络延迟(P99)就会增加 100 毫秒以上。将数据通过副本部署在离用户更近的数据中心,可以直接削减数十至上百毫秒的物理延迟。

- 分布式系统核心痛点:异步复制带来的最终一致性窗口,是数据冲突(Conflicts)与并发问题的源头。 - 墨菲定律与系统容错:只要系统架构存在可能崩溃的隐患,在生产环境持续运行下必然会暴发,必须提前做防御性设计。 - 跨地域延迟差异:单数据中心在服务跨洋用户时,仅物理光纤往返延迟就高达 100ms+,利用跨地域 Replica 可以有效改善用户交互响应。

---

[10:00](https://www.youtube.com/watch?v=SrnRA3JLjIA&t=600s) Replication 的三大价值与三大主流架构流派

除了通过“地理亲近”来降低端到端网络延迟外,Replication 在工程上主要还提供另外两重无可替代的核心价值:其一是容灾与高可用性(High Availability / Fault Tolerance)。当某一个数据中心因断电、光纤被挖断或自然灾害而彻底离线时,分布在其他区域的 Replica 可以迅速接管线上流量,确保对外服务不中断;其二是提升读吞吐量(Read Scalability)。绝大多数互联网应用都是重度读多写少(Read-heavy)的业务场景,如果所有请求都压向单台节点,机器很快就会不堪重负,通过引入多个副本并行处理读请求,可以呈线性地扩大系统的读承载能力。

基于处理写入与副本同步策略的不同,工业界主要演化出三大类 Replication 架构流派:

1. **单主复制(Single-Leader Replication / Master-Slave)**:最经典且应用最广的方案,广泛应用于 MySQL、PostgreSQL 等关系型数据库中。集群中仅有一个主节点(Primary / Leader)负责接收所有写请求(Write Requests),更新完成后将变更日志同步给多个从节点(Followers / Replicas);从节点只承接读请求(Read Requests)。 2. **多主复制(Multi-Leader Replication / Master-Master)**:通常应用在跨多数据中心的高并发场景,例如 Uber 的双活数据中心架构以及 Facebook 的大型跨地域集群。每个数据中心拥有独立接管写入的 Leader,但多主架构中最棘手的难题在于如何解决跨机房并发写入造成的冲突(Conflict Resolution)。 3. **无主复制(Leaderless Replication)**:以 Amazon Dynamo、DynamoDB 和 Cassandra 为代表。集群中没有固定的中心节点,客户端可向任意节点并发写入与读取。这种架构倾向于提供“尽力而为”(Best-Effort)的数据服务,放弃了严格的强一致性承诺,通过读修复(Read Repair)或后台反熵(Anti-entropy)来修补脏读(Dirty Read)和陈旧数据(Stale Data)。

在典型的 Single-Leader 架构中,客户端所有的写操作都强制路由到 Leader 节点,而读请求则可以分散路由到各个 Follower 节点上以分摊压力。

```mermaid flowchart TD accTitle: Single-Leader Replication Architecture accDescr: 客户端的写请求路由至 Leader 节点,读请求路由至 Follower 节点,Leader 负责将变更同步至 Followers。

subgraph Clients["客户端层"] WClient["写客户端 (Write Client)"] RClient["读客户端 (Read Client)"] end

subgraph Cluster["单主复制集群 (Single-Leader Cluster)"] Leader["主节点 (Leader / Primary)"] Follower1["从节点 1 (Follower 1)"] Follower2["从节点 2 (Follower 2)"] end

WClient -->|"写请求 (Writes)"| Leader Leader -->|"复制日志 (Replication)"| Follower1 Leader -->|"复制日志 (Replication)"| Follower2 RClient -->|"读请求 (Reads)"| Follower1 RClient -->|"读请求 (Reads)"| Follower2 ```

---

[14:48](https://www.youtube.com/watch?v=SrnRA3JLjIA&t=888s) 基础设施编排、Kafka 的本质与 Replica 对比 Cache

在现代工程运维中,单主复制集群通常借助基础设施即代码(IaC)工具(如 Terraform)进行自动化编排。工程师可以在 Terraform 脚本中精细定义网络拓扑与负载均衡器(Load Balancer),指定 MySQL 的 Primary 节点 IP、Secondary 节点的 IP 分布,并明确设定数据是以同步(Synchronous)还是异步(Asynchronous)方式复制到从库。讲师随后将视角延伸到消息队列,指出 Single-Leader Replication 不仅存在于传统数据库,同样是 Apache Kafka 等分布式流平台的核心机制。在讲师看来,“Kafka 本质上就是一个简化版的 Database”,虽然它并不适合用作通用关系型数据库,但它优雅实现了高持久化(Durability)、数据复制(Replication)与分区(Partitioning),其底层的日志流设计思想与数据库的写复制机制完全相通。

在互动环节中,针对听众提出的“CDN 到底更像 Cache 还是更像 Replica”的问题,讲师深入辨析了 Replica 与 Cache 之间的两项本质差异: - **故障转移就绪能力(Failover Readiness)**:当集群的 Master/Leader 节点发生宕机或永久离线时,Replica 必须随时具备晋升为新 Leader 的能力,在数据模型和角色定位上从机与主机并无本质差别;而 Cache 挂掉后只是产生缓存击穿,它绝不可能被选为主存储。 - **事务与状态一致性保障(Transaction Consistency)**:在银行转账等业务场景中,一个事务往往包含多行记录的原子更新(例如 A 账户扣减 100 元,同时 B 账户增加 100 元)。如果使用 Cache,由于缓存系统缺乏跨记录的事务原子性保障,客户端并发读取 A 账户和 B 账户时可能会读到相互矛盾的中间态。而在数据库 Replica 中,数据复制是以完整事务为单位进行的,绝不会让从库处于“A 扣了 100 元但 B 尚未增加”的残缺状态,从而规避了单条记录复制导致的逻辑破损。

- 自动化部署实战:通过 Terraform 等工具可直接声明主从拓扑、只读从库路由以及同步/异步复制模式。 - Kafka 的系统定位:一个聚焦于 Partition、Replication 和高持久性追加日志(Append-only Log)的特化型消息系统。 - Replica vs. Cache 核心区别:Replica 具备作为候选主节点的 Failover 资格,并在多行更新时严格保留全局事务边界(Transaction Consistency)。

---

[17:45](https://www.youtube.com/watch?v=SrnRA3JLjIA&t=1065s) 同步与异步复制机制及链式复制(Chain Replication)

讲师进一步拆解了主从架构中的同步复制(Synchronous Replication)与异步复制(Asynchronous Replication)。以典型的“一主两从”(Leader + 两个 Followers)为例:当用户发起写请求(例如更新头像图片)时,Leader 写入自身存储后,向从节点发送同步指令。在半同步配置下,Follower 1 被设定为同步复制,它在写入落盘后向 Leader 返回确认;Leader 收到 Follower 1 的 ACK 后,立刻向写客户端响应成功(OK)。而此时 Follower 2 采用的是异步复制,变更数据尚在排队传输中。这种策略带来的直接影响是:写操作返回成功时,Leader 与 Follower 1 的数据处于绝对一致状态;若此时另一位用户发起读请求查询该头像,如果请求命中 Follower 1,便能立刻看到新头像;但如果读请求落到了尚未完成同步的 Follower 2 上,用户就只能读取到陈旧的旧头像。

作为对比,讲师介绍了一种极具启发性的高一致性复制方案——**链式复制(Chain Replication)**。在链式复制拓扑中,节点被组织成一条有序单向链表:链表的头部(Head)充当 Master,后续依次连接 Replica 1、Replica 2,直到链表的尾部(Tail)。该架构对读写路径进行了绝对的分离:所有的写请求(Writes)严格只能发往链表头部的 Master 节点;Master 完成写入后,将数据依次单向向后传递(Replicate 到 Replica 1,再传到 Replica 2,最后传到 Tail);与此同时,所有的读请求(Reads)强制只由尾部的 Tail 节点承接。链式复制的巧妙之处在于:客户端只要能从 Tail 节点读取到某条数据,就意味着该数据必然已经在链条上的前置所有节点(Master 及中间所有 Replicas)中完整落盘同步,从而以极简的线性流水线结构换取了极强的一致性保障。

```mermaid flowchart LR accTitle: Chain Replication Data Flow accDescr: 链式复制的数据流向:写请求由链表头 Head 处理并沿链顺序向下传递,读请求完全由链表尾 Tail 承接。

subgraph Pipeline["链式节点管道 (Chain Pipeline)"] Head["头节点 (Head / Master)"] Node1["副本节点 1 (Replica 1)"] Node2["副本节点 2 (Replica 2)"] Tail["尾节点 (Tail)"] end

ClientW["写客户端 (Writer)"] -->|"1. 写入请求 (Write)"| Head Head -->|"2. 顺序传递 (Replicate)"| Node1 Node1 -->|"3. 顺序传递 (Replicate)"| Node2 Node2 -->|"4. 顺序传递 (Replicate)"| Tail

ClientR["读客户端 (Reader)"] -->|"读请求 (Read)"| Tail Tail -.->|"返回已全量落盘数据"| ClientR ```

- 半同步复制(Semi-synchronous Replication)权衡:只等待一个同步从节点确认即可返回成功,在写入延迟与容灾强弱之间做折中,但异步从节点仍存在读陈旧数据的窗口。 - 链式复制读写分离特性:Head 负责吞吐写入流水线,Tail 负责拦截读取并提供天然的线性一致性视图。 - 链式复制核心保证:在链尾 Tail 可见的任何写入,在此之前必定已经在整个链条的所有上游节点中全部提交完成。

[20:00](https://www.youtube.com/watch?v=SrnRA3JLjIA&t=1200s) 设立新节点与跟随者故障恢复

在分布式系统中,如果需要为一个现有的数据库集群配置一个新的从节点(Follower),最朴素的想法或许是直接把数据文件从一台机器拷贝到另一台机器。然而这种做法在实际运行中是行不通的。因为在数据持续写入的场景下,文件在拷贝过程中随时都在发生变动,直接拷贝会导致数据文件处于损坏或不一致的状态。在传统的企业 IT 运维中,常见的做法是申请一个停机维护窗口,比如业务停机维护六个小时,把数据库完全锁住或直接下线,再做全量数据备份(Dump)与拷贝。但对于现代互联网公司而言,这种需要长时间停机的维护方式是完全无法接受的。

为了实现在线平滑添加从节点,业界标准做法是利用一致性快照(Consistent Snapshot)。数据库系统在底层支持并发控制与非阻塞读,在打快照的过程中并不会阻塞业务的写入请求,因此对系统的高可用性毫无影响。具体的建立流程分为三个阶段:首先在主节点(Leader)上生成某一个时间点的一致性快照,例如包含编号 1 到 50 的事务;接着将该快照拷贝至新的从节点上并完成恢复;恢复后,从节点主动连接主节点,汇报自己已经拥有截至第 50 号事务的数据,并请求增量数据。主节点随后将快照生成之后发生的事务(例如第 51 到 100 号事务)以及后续持续产生的新事务(如 101、102 号)作为变更日志流发送给从节点。当从节点把落后的日志全部重放(Apply)完毕后,就正式追平了主节点,可以开始对外提供读服务,并在主节点宕机时具备被提升为主节点的资格。

从节点自身的故障恢复模型则相对简单直接。当从节点因宕机或网络中断发生故障时,系统设计通常遵循“故障恢复(Failure Recovery)”流程。从节点会在本地持久化记录自己从主节点同步并已处理的最新事务日志偏移量。一旦从节点重启恢复或重新拉起实例,它只需携带该偏移量重新连接主节点,请求落下的日志变更并依序追平,即可重新归队进入同步状态。然而,与从节点故障相比,主节点的故障处理要棘手得多。

[25:00](https://www.youtube.com/watch?v=SrnRA3JLjIA&t=1500s) 主节点故障、故障转移与脑裂风险

当主节点发生故障时,系统必须执行故障转移(Failover)流程:将某一个从节点提升为新的主节点,并重新配置集群路由与角色。故障转移往往不仅是在系统出现意外时的被动救灾,更是一项常态化的主动演练,其性质类似于日常的消防演习(Fire Drill)。比如在 Uber,早期工程团队每周都会主动触发一次跨数据中心的故障转移演练,后来才逐渐放宽为每一两个月一次。通过主动将主数据中心降级为备用数据中心,并将备用提升为主数据中心,能够确保在真实灾难来临时服务依然稳健不崩。

标准的故障转移包含三个核心步骤: 1. **故障检测**:在缺乏绝对权威的“预言机(Oracle)”的情况下,外界无法瞬间判断主节点到底是因为物理机崩溃、机房停电还是网络短暂拥塞。因此,业界通用做法是基于心跳超时机制(Timeout)。例如若设定的 3 秒超时时间内未收到主节点的心跳包,系统便认定该节点已死亡。 2. **选举新主节点**:通过共识算法(Consensus Algorithm,如 Raft 或 Paxos)从存活的从节点中推选出新领导者。这个过程极其复杂,因为在推选新领导者的瞬间,参与投票的从节点自身也可能发生宕机或网络中断。 3. **系统重构与脑裂规避**:不仅要让所有从节点认可新主节点,新主节点本身必须明确自身身份,最关键的是当“死掉”的旧主节点突然恢复时,必须能让它接受现实。

旧主节点复活的场景就像“黑社会的旧老大突然死而复生”,如果旧老大醒来依然坚持自己是老大,且同时接收写请求,而系统中又有大家公认的新老大,就会发生极其危险的“脑裂(Split-Brain)”现象。历史上脑裂多次引发灾难性后果。此外,大规模集群在网络分区下的雪崩效应更加惊人。比如类似 Uber 存储架构的系统,动辄拆分为 4,000 多个分区,每个分区配置“一主两从”(1 个 Master、2 个 Slave),全集群共计有 4,000 个主节点和 8,000 个从节点。一旦数据中心的核心骨干网交换机出现硬件故障导致网络分区,同一时刻可能会有成百上千个分区的主节点失联并同时触发选举。单次共识选举通常耗时 100 到 200 毫秒,几千个分区并发进行选举会瞬间击垮控制平面与网络,造成整个系统长时间不可用。

```mermaid flowchart TD accTitle: Split-Brain Scenario in Distributed Systems accDescr: Diagram illustrating how a network partition creates a split-brain condition with two conflicting leaders.

subgraph NormalCluster["正常集群状态"] L1["旧主节点 (Leader 1)"] F1["从节点 (Follower)"] F2["从节点 (Follower)"] L1 -->|"同步写操作"| F1 L1 -->|"同步写操作"| F2 end

subgraph PartitionEvent["发生网络分区 / 心跳超时"] direction TB L1_Isolated["旧主节点 (假死/网络隔离)<br/>仍自认为主节点"] F1_Promoted["被选举出的新主节点 (Leader 2)"] F2_Active["跟随新主的从节点"] F1_Promoted -->|"同步新写入"| F2_Active end

subgraph SplitBrainCrisis["脑裂危机 (Split-Brain)"] ClientA["客户端 A"] -->|"写入转账请求"| L1_Isolated ClientB["客户端 B"] -->|"写入转账请求"| F1_Promoted ConflictWarning{{"数据状态发生严重冲突与数据丢失!"}} L1_Isolated -.-> ConflictWarning F1_Promoted -.-> ConflictWarning end ```

[30:00](https://www.youtube.com/watch?v=SrnRA3JLjIA&t=1800s) 异步复制的隐患与 GitHub 安全故障剖析

由于同步复制(Synchronous Replication)会严重拖累性能——一次跨节点写入往往会让系统的 P99 延迟翻倍(例如从 100ms 飙升至 200ms),绝大多数分布式数据库在默认配置下都采用异步复制(Asynchronous Replication)。但异步复制的根本代价在于:一旦主节点宕机,尚未同步至从节点的已确认数据就会直接永久丢失。

例如在银行转账场景中,用户从 A 账户向 B 账户转入 100 元,主节点写入成功后立即向用户返回“OK”。然而日志还没来得及同步给从节点,主节点便瞬间暴毙。这个主节点可谓带着秘密“含恨而终”。当从节点被提升为新主后,根本不知道这笔转账的存在。当用户再次查询余额时,发现钱凭空消失了,这便造成了严重的数据不一致事故。

更具破坏性的是异步复制丢失数据与主键自增(Auto-increment ID)结合引发的安全穿透,GitHub 历史上就曾发生过一次著名的真实重大安全事故。在 GitHub 的架构设计中,MySQL 数据库使用自增主键来标识 Pull Request(PR)等元数据,而代码库的具体内容和元数据映射则缓存在第三方的 Redis 存储中。当时旧主库在写入一条 Uber 的私有 PR 记录时,分配的自增 ID 为 1,但该记录还没同步到从库,旧主库就发生了宕机。从库被提升为新主库后,并不知道 ID 1 已经被使用过。随后,Lyft 的开发团队恰好提交了一个新的 PR,新主库再次分配了自增主键 ID 1。然而,底层 Redis 缓存中 ID 1 所指向的代码引用仍然是之前 Uber 的私有代码仓库!这直接导致 Lyft 的人员在打开自己的 PR 时,竟然可以读取到 Uber 的内部私有源码。这类事故一旦发生,除了系统崩溃,更会带来极其严重的跨企业机密泄露与法律诉讼风险。如果当时系统采用全局唯一标识符(UUID)而非递增自增 ID,即便发生数据不同步,最多也就是访问报错,而绝不会错位映射到其他公司的私有资产上。

```mermaid flowchart LR accTitle: GitHub Auto-Increment and Async Replication Bug Flow accDescr: Shows how asynchronous replication and auto-increment ID reuse caused Lyft to access Uber's private code.

subgraph OldMaster["旧主库 (宕机前)"] PR_Uber["写入 Uber PR<br/>自增 ID = 1"] end

subgraph RedisStorage["外部缓存/引用存储 (Redis)"] CacheRow["Key: ID 1<br/>映射目标: Uber 私有代码仓库"] end

subgraph NewMaster["新主库 (故障转移后)"] PR_Lyft["写入 Lyft PR<br/>复用 ID = 1 (无前序记录)"] end

PR_Uber -->|"写入元数据"| CacheRow OldMaster -.->|"异步同步未完成便宕机"| NewMaster PR_Lyft -->|"错误复用 ID 1 查询"| CacheRow CacheRow ==>|"泄露危险!Lyft PR 读取到了 Uber 代码"| LeakTarget["安全与法律事故"] ```

[40:00](https://www.youtube.com/watch?v=SrnRA3JLjIA&t=2400s) 超时阈值的权衡与网络抖动

在故障转移的控制设计中,确定合理的判定超时时间(Timeout)是一个极具挑战的工程权衡问题。为了看清权衡边界,工程师在系统设计时经常使用“极限思维(Extreme Thinking)”: - **如果超时设得过长**(例如极端假设设为 1 小时,或者生产中设为数十秒):一旦主节点死掉,这 1 小时内任何发往该分区的请求都无法得到响应,这对在线用户体验而言是灾难性的“永远卡死”。 - **如果超时设得过短**(例如设为 50 毫秒):一旦网络出现轻微抖动、暂时性拥塞,或是主节点因垃圾回收(GC)发生短暂毛刺,系统就会误判主节点“已死”,从而草率触发选举。而重新进行共识选举本身就需要 200 到 300 毫秒的开销,紧接着旧主节点恢复后还要解决脑裂冲突、回滚未同步的数据,这反而导致系统陷入更剧烈的动荡和频繁抖动。

因此,固定硬编码(如直接写死 200ms 或 3s)并非最佳实践。在网络通信中,TCP 协议会通过测量往返时间(RTT)与其方差(Variance)来动态评估网络状态。在分布式系统中,成熟的工业级数据库(例如 Cassandra)也引入了基于统计概率的自适应故障检测机制(如 $\Phi$ Accrual Failure Detector),通过持续收集心跳的历史 RTT 数据分布,自适应动态计算当前合理的超时阈值,从而在检测速度与误判率之间取得平衡。

这一故障模式同样让运维基础设施的容错显得至关重要。例如 Cloudflare 曾遭遇的一次全网重大故障,起因也是配置变更失误将全网所有流量瞬间引流至亚特兰大(Atlanta)单个数据中心,直接冲垮了整条骨干网。但从这类人为失误(Human Error)中反观其基建架构,Cloudflare 耗费巨资自建了独立的私有骨干网络(Private Backbone Network),这在公有 CDN 选型时,无论在网络安全性还是极端性能保障上,依然具有极高的技术壁垒。

[45:00](https://www.youtube.com/watch?v=SrnRA3JLjIA&t=2700s) 复制日志实现方式:基于语句的复制

在厘清了同步与故障转移机制后,核心问题转向复制日志(Replication Log)的具体技术实现。最基础、最直观的方案是**基于语句的复制(Statement-Based Replication, SBR)**。顾名思义,主节点执行了什么 SQL 写入语句(如 `INSERT`、`UPDATE`、`DELETE`),就原封不动地将该 SQL 文本网络传输给从节点重新执行。

尽管该方案直观且日志体积小,但它存在众多极为棘手的边界隐患(Edge Cases): - **非确定性函数(Non-deterministic Functions)**:如果 SQL 语句中包含依赖运行环境的动态函数,例如 `NOW()` 或 `RAND()`,从节点在滞后执行该语句时,获取到的当前系统时间或随机数必定与主节点不一致。对于这类函数,数据库在记录日志前必须强行介入,将其重写为具体的静态字面量(例如将 `NOW()` 替换为主节点执行时的绝对时间戳)。 - **执行顺序与条件依赖(Order & Conditions)**:当涉及自增主键列(Auto-increment Column)或带有特定 `WHERE` 状态依赖的更新语句时,所有语句在从节点上的执行顺序必须与主节点保持绝对严格的一致,稍微发生重排就会导致后续状态完全背离。 - **语句副作用(Side Effects)**:许多数据库系统依赖存储过程或触发器(Triggers)。当包含触发器的语句在从节点重复执行时,可能会在从节点上产生意料之外的二次级联副作用,破坏从节点数据的一致性。

[50:00](https://www.youtube.com/watch?v=SrnRA3JLjIA&t=3000s) Statement-Based 复制的弊端与 Write-Ahead Log (WAL) 复制

基于语句的复制(Statement-Based Replication)在面对复杂数据库特性时会迅速崩溃。除了常见的非确定性函数,数据库中的触发器(Triggers)和存储过程(Stored Procedures)也是重灾区。例如触发器逻辑为“当 Column A 发生更改时,自动将 Column B 的计数器加 1”;而存储过程则是一大段打包在一起执行的语句块,旨在减少客户端与数据库之间的网络交互。讲师指出,存储过程在早期的传统 IT 企业中非常流行,但现代互联网公司基本不再使用,因为它的副作用(side effects)实在太多,代码极其脆弱(fragile),极易产生难以排查的 Bug。此外,关系型数据库还支持用户自定义函数(UDF),开发者甚至能在 UDF 中发起外部 API 请求;一旦复制包含这类逻辑的语句,从节点执行时产生的副作用将完全不可控,导致主从节点数据严重分歧。虽然这些边缘情况(edge cases)理论上能打补丁解决,但维护成本过高,因此业内如今基本淘汰了语句复制。

第二种方式是利用写前日志(Write-Ahead Log, WAL)进行复制。在讲解 B-Tree 索引时就提到过,数据库为了实现持久性(Durability),在修改内存中的数据页和 B-Tree 之前,必须先将变更追加写入磁盘上的 WAL。如果系统意外崩溃,重启时只需重放(replay)WAL 即可将状态完整恢复,恢复完成后才开始对外处理新请求。既然 WAL 忠实记录了所有底层变更,自然有人想到用它来做主从复制。然而,WAL 在设计之初纯粹是为了单机崩溃恢复与持久性,并非专为分布式复制而生,因此它带着天然的底层缺陷。

WAL 在本质上是一串纯底层的二进制字节流(a sequence of bytes),它不是人类可读的,而是直接针对具体磁盘数据页(File Page)的二进制覆写。这就带来了一个强硬的假设前提:从节点(Replica)底层的内部实现与数据结构必须与主节点(Leader)一模一样。

- **存储过程与触发器的痛点**:副作用不可控且极度脆弱,现代工程实践中已被主流互联网架构弃用。 - **UDF 的不确定性**:自定义函数甚至可能发起外部网络调用(API Call),在从库执行会导致主从数据状态彻底分歧。 - **WAL 复制的本质**:利用原属于单机 Durability 范畴的物理日志进行复制,以二进制数据页变更的形式直接作用于存储层。

---

[55:00](https://www.youtube.com/watch?v=SrnRA3JLjIA&t=3300s) 基于逻辑日志(Logical Log)与触发器(Trigger-Based)的复制演进

依赖 WAL 进行复制的最大痛点集中在数据库升级(Database Upgrade)与零停机时间(Zero Downtime)需求上。以 PostgreSQL 和 Oracle 这类深度依赖物理日志的数据库为例,若要从 Version 1 升级到 Version 2,而后者的底层内部数据结构不再向下兼容(not backward compatible),就必须停机维护:先将数据库下线,等待所有节点同步完成,再统一升级版本。如果尝试在不停机的情况下进行滚动升级,主从节点在升级窗口期必定处于不同版本,版本不兼容会导致解析二进制数据结构时直接错乱("data structure 直接 mess up")。在 Oracle 这种闭源商业数据库中,外部工程师甚至连底层源码都看不到,根本无法预判跨版本复制的兼容性风险。

为了彻底摆脱物理层束缚,业界演进出了专门为复制而设计的**逻辑日志复制(Logical Log / Row-Based Replication)**。逻辑日志与底层的具体存储引擎完全解耦,它采用一套与存储无关的描述语言来记录行级别的数据变更: 1. **插入(Insert)**:记录该行所有列的新值(new value of all columns)。 2. **删除(Delete)**:记录足以唯一标识该行的主键;若无主键,则记录所有列的旧值。 3. **更新(Update)**:记录主键信息以及发生变更后的列新值。

逻辑日志按事务提交的先后顺序记录严格的全局序列(Sequence),从库只要按序应用便能确保数据不会损坏。更关键的是,逻辑日志只关注数据逻辑本身,主节点使用 B-Tree 而从节点使用 LSM-Tree(Log-Structured Merge-Tree)完全可行,甚至允许主从节点运行不同的数据库版本,从而天然支持零停机滚动升级。此外,逻辑日志格式透明,易于被外部应用程序解析,催生了变更数据捕获(Change Data Capture, CDC)架构,使得像 Cassandra 或 MySQL 等数据库的行变更能以事件流形式输出,对接下游实时流计算。另一种演化方案是基于触发器的复制(Trigger-Based Replication,例如 Oracle GoldenGate),在行更新时触发应用层钩子逻辑来实现跨库同步,但由于在数据库层执行额外代码,其系统开销(overhead)明显高于其他机制。

```mermaid flowchart TD accTitle: 逻辑日志复制与 CDC 消费架构 accDescr: 展示主节点通过逻辑日志与存储引擎解耦,分别将数据同步给不同版本的从节点以及外部 CDC 流式管道的过程。

subgraph LeaderNode["主节点 (Leader - v2.0)"] EngineA["存储引擎 (例如: B-Tree)"] LogicalLog["逻辑日志 (Logical Log / 行格式)"] EngineA -->|"生成逻辑变更"| LogicalLog end

subgraph FollowerNode["从节点 (Follower - v1.0)"] EngineB["异构存储引擎 (例如: LSM-Tree)"] end

subgraph ExternalStream["外部应用与流计算"] CDC["CDC 读取器 (Change Data Capture)"] Consumer["流处理引擎 / 搜素索引 / 缓存"] CDC --> Consumer end

LogicalLog -->|"解耦同步 (按事务提交序列)"| EngineB LogicalLog -->|"解析事件流"| CDC ```

- **逻辑日志与物理日志对比**:物理日志(WAL)绑定存储页与具体实现;逻辑日志记录行级语义,与存储引擎实现彻底解耦(Separation of Concerns)。 - **零停机升级**:逻辑日志屏蔽版本差异,允许从节点先行升级,实现无痛滚动发布。 - **CDC 生态**:外部程序可订阅逻辑变更流,将数据库变为流数据源(Streaming Source)。 - **触发器复制的代价值**:如 Oracle GoldenGate 等工具通过触发器捕获变更,灵活性高但对事务写入性能有可观的额外损耗。

---

[01:00:00](https://www.youtube.com/watch?v=SrnRA3JLjIA&t=3600s) 隔离级别与复制的正交性,以及写后读(Read-Your-Writes)一致性问题

针对课堂上关于“日志复制与事务强隔离级别关系”的提问,讲师特别强调:**复制(Replication)与隔离级别(Isolation)是完全正交的两个概念(orthogonal problems)**。隔离级别与并发控制属于事务范畴,通常在主节点(Master)处理,用于裁定多并发请求中“谁胜谁负”(who is winner, who is loser);而复制解决的是主节点确定好结果后,如何将数据可靠同步到从节点(Master to Slave)。这两者不应混为一谈。

在异步复制(Asynchronous Replication)架构中,由于主从同步存在不可避免的网络延迟窗口,系统极易触发**写后读(Read-Your-Writes Consistency / Read-After-Write)问题**。典型场景是:用户发布了一条新评论,写请求提交到主节点并落盘成功;随后该用户立刻刷新页面,由于读请求被负载均衡路由到了尚有延迟的从节点,查询返回“暂无评论”。用户明明刚刚提交了数据却看不到自己的写入,会产生系统丢数据的错觉。

针对这种场景,工程上有几种经典的解决方案: 1. **区分自身数据与他人数据进行路由**:以社交平台(Facebook、Twitter)为例,用户对自己的个人资料(Profile)有即时可见的强心理预期,而对其他用户的最新动态延迟几十秒甚至一分钟并不敏感。因此,系统可以设计为“读自己的 Profile 强制读 Master,读其他用户的 Profile 则走 Follower”,以最小的主库负载代价换取良好的用户体验。 2. **基于更新时间戳与内存缓存判断**:跟踪记录实体的最后更新时间(`last_update_time`)。如果该实体在过去一分钟内发生过更新,读请求强制走 Master;若超过一分钟未更新,则安全路由给 Follower。 3. **内存级状态追踪**:如果将检查更新时间戳的请求直接发给 MySQL 等磁盘数据库,磁盘 I/O 耗时通常是内存访问的 100 到 1000 倍,会严重拖垮吞吐量。因此,更新时间戳通常存放在内存数据库(如 Redis)中,或者利用布隆过滤器(Bloom Filter)等极高效率的内存结构进行快速阻断拦截。

- **概念澄清**:Replication 负责 Master-Slave 间的数据流转;Isolation 负责 Master 端的事务并发冲突裁决。 - **写后读痛点**:异步复制延迟导致用户“刚刚写入却读不到自己的改动”。 - **按身份选择性路由**:自身敏感数据(如个人资料)读 Master,公共弱一致数据读 Replica。 - **更新时间窗口过滤**:结合 Redis 或 Bloom Filter 记录近期更新,利用低时延内存校验决定读主还是读从。

---

[01:05:00](https://www.youtube.com/watch?v=SrnRA3JLjIA&t=3900s) 跨设备与跨数据中心的一致性权衡,以及单调读(Monotonic Reads)

当场景拓展至多端接入与跨地域部署时,写后读一致性会变得更加错综复杂。首先是**跨设备写后读一致性(Cross-Device Read-After-Write Consistency)**:用户在 PC 桌面端更新了个人资料或发布了内容,拿起手机端查看时,两台设备发起的网络连接可能由于会话或网络拓扑差异,被网关路由到了完全不同的节点甚至是不同的数据中心。如果用户在桌面端刚写完,手机端连上未同步的从库,依然会遭遇一致性破裂。

在现代高可用架构中,数据库通常要求跨多个数据中心(Cross-Datacenter)部署副本。假设系统在两个数据中心各自维护副本,以防止单机房故障(Complete Outage)。此时客户端路由面临两难抉择(Trade-off): - **强制固定机房路由**:要求特定用户的所有请求必须钉选路由到指定数据中心(如旧金山 Data Center A)。这虽然容易保障写后读一致性,但性能隐患极大。一旦该机房出现网络抖动或性能劣化,用户体验将随之雪崩;而且一旦用户出差旅行(如从旧金山飞到北京),原本就近访问亚洲机房的请求依然被迫跨洋跨网绕路回旧金山,带来不可接受的网络高延迟(High Latency)。多用户共享账号(跨地域同时在线)更是会让固定机房策略彻底失效。 - **就近访问优先**:根据客户端地理位置路由到最近的数据中心,能获得极佳的响应延迟,但只要主从复制存在跨洋延迟,便几乎注定在某一时间窗口内牺牲写后读一致性。这完全取决于业务形态是优先追求一致性还是更看重低延迟性能。

随后,讲师引入了另一个更进阶的一致性模型——**单调读(Monotonic Reads)**。异步复制下的从库延迟差异可能导致时间倒流现象。讲师举例:用户 12345 在系统里发问“现在几点了?”,随后用户 2345 回复“现在是 5 点”。正常的因果逻辑必然是先有提问后有回答;但在多从库复制延迟不均的情况下,如果第三方观察者第一次读请求落在一个同步较快的从库(看到了“现在是 5 点”的回答),刷新页面后的第二次读请求却落在一个延迟严重的慢从库(该从库甚至还没同步到那句提问),观察者就会发现原本已看到的内容离奇消失,仿佛在时间轴上发生了“倒退”。

- **跨设备一致性挑战**:桌面端与移动端网络链路相互独立,难以依赖单机的本地缓存或连接状态维系一致性。 - **跨机房路由权衡**: - 固定归属机房:保障一致性,但长距离跨洋访问性能极差,遭遇机房级降级时缺乏韧性。 - 就近接入机房:网络延迟最低,但在跨数据中心异步复制窗口期必须承担读写不一致的代价。 - **单调读(Monotonic Reads)**:防止观察者在多次连续读取中看到“时间倒退”(先看到新状态,后续查询又落回旧状态),通常要求同一用户的连续读请求尽可能哈希绑定到同一个副本节点。

[01:10:00](https://www.youtube.com/watch?v=SrnRA3JLjIA&t=4200s) 单调读(Monotonic Reads)与因果一致性违背

分布式系统中经常会出现读操作“时光倒流”(read back in time)的异常现象。例如,用户 1 先发问“现在几点了?”,用户 2 回复“现在 5 点”。如果某个 Follower 节点因为复制延迟,只收到了“现在 5 点”的写入,而没同步到最初的发问,那么客户端就会先看到回答再看到提问,甚至刷新之后原本看到的数据又不见了。这种先看到新数据、又读到旧数据的现象严重破坏了用户体验。

```mermaid flowchart TD accTitle: 单调读问题与同副本读取解决方案 accDescr: 展示路由到不同 Replica 导致的时光倒流问题,以及固定路由至单一 Replica 的保障方案。

subgraph Problem["违背单调读(路由到不同副本)"] U1["用户连续发起两次读取"] -->|第 1 次读取: 命中已同步副本| R1["Replica 1 (最新状态)"] U1 -->|第 2 次读取: 命中延迟副本| R2["Replica 2 (延迟滞后)"] R1 -.->|返回最新评论 555| U1 R2 -.->|评论消失: 读回过去| U1 end

subgraph Solution["保障单调读(固定副本路由)"] U2["用户"] -->|所有读请求| R_Fixed["固定 Replica 节点"] R_Fixed -->|数据单调递增| U2 end ```

单调读(Monotonic Reads)是一种比读写一致性(Read-After-Write)更严格的并发控制与一致性保证。单调读保证同一个观察者无论读取哪台机器,观察到的结果都是单调递增的,绝不允许在读到某个时间点的数据后又回退到更早的状态。实现单调读最简单有效的做法是:确保同一个用户的所有读请求都路由到同一个 Replica。因为底层的插入操作是不可逆的(cannot revoke),只要不跨节点乱跳,就不会因节点间复制进度的差异而出现状态回退。

- **因果一致性违背(Causality Violation)案例**:主讲人引用了《星际之门》(Stargate)风格的科幻梗:Mr. Boone 问 Mr. Kick 能预测多久的未来,Mr. Kick 回答“能看到未来 10 秒”,结果系统复制异常导致观察者先读到了结果(Mr. Kick 的回答),后读到原因(Mr. Boone 的提问),彻底颠倒了因果关系。 - **单调读与读写一致性的差异**:单调读要求全局读取递增,更难保证;而读写一致性主要约束用户自身能否立刻读到自己的写入。

---

[01:15:00](https://www.youtube.com/watch?v=SrnRA3JLjIA&t=4500s) 副本复制与数据库事务的解耦关系

关于“副本复制(Replication)是否必须基于事务(Transaction)完成”这一问题,主讲人指出,从理论概念上讲,事务与复制是两个完全独立的概念:事务关注并发控制与原子性,复制关注数据的多副本分发。即使一个事务同时修改了对象 A 和对象 B,理论上复制机制也可以单独同步对 A 或 B 的修改。然而,在实际的业务场景(尤其是关系型数据库)中,如果将事务拆开复制,就会导致严重的数据不一致——比如银行转账场景中,账户 A 扣减 100 元而账户 B 增加 100 元,如果只复制了一半,账目就会失衡。因此,工业界在关系型数据库中做单 Leader 复制时,通常都将事务作为复制的基本单元。

无论采用哪种日志复制技术(如基于 SQL 语句的复制 Statement-based、预写日志 Write-Ahead Log / WAL,还是行级逻辑日志 Row-based log),只要事务已成功 Commit,其包含的全部变更都可以打包成一个单元批量复制给从库。

- **概念独立与工程绑定的统一**:概念上复制不依赖事务,但关系型数据库因业务一致性要求,强制将二者绑定在一起处理。 - **日志打包机制**:一个事务无论包含几十条 SQL 语句还是上千条 WAL 记录,复制层都将其打包为一个单元推送到从库。

---

[01:20:00](https://www.youtube.com/watch?v=SrnRA3JLjIA&t=4800s) 事务边界演进:长事务与短事务的权衡

现场听众提问:既然有了日志,事务内部的语句是否在执行过程中就被逐步写入并同步?主讲人澄清道,并发控制与事务回滚是事务引擎的职责(有冲突就 abort/revert),而复制层只在事务完全 Commit 之后,才把确定生效的数据从 Master 同步到 Slave。

```mermaid flowchart LR accTitle: 事务提交与复制流程 accDescr: 展示应用在 Master 上执行长/短事务提交后,Replication Log 才将数据同步给 Slave 的过程。

subgraph MasterNode["Master 节点"] direction TB T["事务执行中: 多条 Statements / WAL"] -->|检测冲突 / 校验通过| C["Transaction Commit"] C -->|写入复制流| RL["Replication Log"] end

subgraph SlaveNode["Slave 节点"] direction TB RL -->|打包同步| S["应用变更至从库"] end ```

主讲人回顾了数据库架构的历史演进: - **20 世纪 70-80 年代的“长事务”设计**:当时的理念试图用一个数据库事务包裹整个业务流。例如在 Priceline 订机票或订电影票,从选电影、选场次、选时间甚至加购爆米花,整个交互过程长达几十分钟,数据库必须挂起连接等待客户端输入,这在现代高并发架构中被证明是非常糟糕的设计。 - **现代架构的“短事务”设计**:现代系统全面转向短事务。例如用户仅在最终确认选座提交的瞬间开启并完成一个毫秒级的短事务,业务状态则由应用层维护。

---

[01:25:00](https://www.youtube.com/watch?v=SrnRA3JLjIA&t=5100s) 全球化架构选型:Single-Leader、Multi-Leader 与 CRDT

针对听众提出的“全球部署的社交/用户服务(如类似 Facebook 的架构)应如何选择主从架构”的问题,主讲人展开了深度探讨,指出关键取决于具体的用例(Use Case)与一致性约束:

| 维度 / 特性 | 单 Leader 架构(Single-Leader) | 多 Leader 架构(Multi-Leader) | |:--- |:--- |:--- | | **延迟与可用性** | 跨地域写延迟高,主节点故障写可用性降低 | 本地数据中心即可写入,低延迟、高可用 | | **数据一致性** | 天然避免写冲突,一致性保障更强 | 存在并发写冲突(Conflict),解决极其复杂 | | **适用业务形态** | 对一致性要求极高、无法接受冲突回滚的系统 | 允许弱一致、或能通过 CRDT 自动合并冲突的业务 |

主讲人对比了两种典型的用户服务模型: 1. **Uber 模式**:用户与司机之间的交互关系简单直截,没有复杂的全局好友关系网(Friendship)。读写请求通常可以直接根据地理位置路由到同一个数据中心,保证看到自己和司机的最新状态,系统设计偏向简单直接。 2. **Facebook 模式**:读请求量远远大于写请求(读远大于写)。一个用户的主页聚合了数十个好友的动态更新。对于这种系统,自身写操作必须满足写后读(Read-After-Write)一致性(写和读自身数据直连 Master),而读取其他用户的动态则大量从 Slave 节点分流。此外,Facebook 大量采用反范式化(Denormalization)存储,以此换取极高的读取与数据拼装吞吐。

主讲人总结道,系统设计没有银弹。如果业务上有绝对不能妥协的一致性规则,而多 Leader 的冲突又无法优雅解决,就应当优先使用 Single-Leader,或者引入 CRDT(无冲突复制数据类型,Conflict-free Replicated Data Type)等自动化数据结构;CRDT 虽然近年来概念较火,但其核心数学逻辑并不难理解。分布式系统与复制机制细节硬核、复杂度高,需要结合底层原理反复推敲。

## Notable quotes > [00:00](https://www.youtube.com/watch?v=SrnRA3JLjIA&t=0s) 不会的,它只是 generate 已有的 code。 — Scott Shi

> [05:00](https://www.youtube.com/watch?v=SrnRA3JLjIA&t=300s) 数据往往比代码活得更久 — Scott Shi

> [05:00](https://www.youtube.com/watch?v=SrnRA3JLjIA&t=300s) 事物可能会出错与绝对不可能出错之间的主要区别在于:当一个绝对不可能出错的事物出错时,它通常会变成彻底无法挽回的灾难。 — 道格拉斯·亚当斯

> [05:00](https://www.youtube.com/watch?v=SrnRA3JLjIA&t=300s) What could go wrong, eventually will go wrong — Scott Shi

> [14:48](https://www.youtube.com/watch?v=SrnRA3JLjIA&t=888s) Kafka 本质上就是一个简化版的 Database — Scott Shi

> [25:00](https://www.youtube.com/watch?v=SrnRA3JLjIA&t=1500s) 黑社会的旧老大突然死而复生 — Scott Shi

> [55:00](https://www.youtube.com/watch?v=SrnRA3JLjIA&t=3300s) data structure 直接 mess up — Scott Shi

> [01:00:00](https://www.youtube.com/watch?v=SrnRA3JLjIA&t=3600s) 复制(Replication)与隔离级别(Isolation)是完全正交的两个概念 — Scott Shi

> [01:00:00](https://www.youtube.com/watch?v=SrnRA3JLjIA&t=3600s) who is winner, who is loser — Scott Shi

> [01:10:00](https://www.youtube.com/watch?v=SrnRA3JLjIA&t=4200s) 能看到未来 10 秒 — Scott Shi

## Mentioned OpenAI API, Scott Shi, Leo, TinyURL, Craigslist, Facebook, FedEx, EA (Electronic Arts), React, Douglas Adams, AWS, Uber, DynamoDB, MySQL, PostgreSQL, Apache Cassandra, Apache Kafka, Terraform, Redis, GitHub, Lyft, Cloudflare, Oracle, Oracle GoldenGate, Twitter, Priceline, Raft, Paxos, CRDT, Stargate

## Video description Slide: https://docs.google.com/presentation/d/1XfbqOmNh-yRqsVnznYFBd6hdjrreaef43TP9rcb2TXQ/edit

in this video, we talk about replication in distributed system.

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

中文简报

分布式异步复制暗藏脑裂风险 专家解析主从一致性权衡 异步复制虽能降低网络延迟,但主节点故障转移与自增主键结合极易引发数据丢失与跨租户安全泄露,系统架构必须依赖逻辑日志与精准读路由机制兜底一致性。 课程在开篇简要体验了早期 OpenAI API 并探讨其对研发的影响后,全面切入分布式复制的核心议题。讲师系统剖析了单主、多主与无主架构,并对比了同步、异步与链式复制在延迟与可用性上的权衡。随后深入拆解了新节点接入、心跳超时、脑裂风险以及基于语句、物理预写日志(WAL)和行级逻辑日志的演进过程,并结合 GitHub 真实安全事故说明异步复制丢失数据的隐患。最后,内容辨析了复制与事务隔离的边界,阐释了写后读与单调读等经典一致性难题,并结合 Uber 与 Facebook 的实际场景对比了全球化架构的选型思考。 分布式存储的复制(Replication)核心在于缩短跨地域网络延迟、实现高可用容灾及提升读吞吐量,但异步复制带来的最终一致性窗口是并发数据冲突与 Bug 的主要根源。 (5:00) 故障转移(Failover)中超时阈值的设定需权衡系统停顿与误判抖动,硬编码超时易引发脑裂(Split-Brain),工业级实践更倾向于使用自适应故障检测算法。 (25:00) 基于语句(Statement-Based)和预写日志(WAL)的复制方式因副作用及强绑定物理数据页正被淘汰,行级逻辑日志(Logical Log)因与存储引擎解耦和支持零停机滚动升级成为主流。 (55:00) 复制(Replication)与事务隔离级别(Isolation)属于正交问题;写后读不一致与单调读异常可以通过区分自身/他人数据路由、内存时间戳过滤或会话固定路由等工程手段来缓解。 (60:00)

Reading summary

讲师 Scott Shi 指出,Replication(数据复制)是分布式存储中引发一致性混乱与复杂故障的核心温床;在物理法则与异步网络约束下,架构设计必须在延迟、高可用与一致性间权衡,通过合理的日志抽象、拓扑选型与路由策略来防御脑裂与数据错乱。 复制的核心价值与三类主流拓扑 (10:00) Replication 的工程价值主要体现在三方面:缩短物理距离以削减跨地域网络延迟(单机房跨洋访问网络延迟往往增加 100ms 以上)、容灾高可用以及通过多副本分担读请求以提升吞吐量。与缓存不同,副本不仅具备作为新主节点的故障转移就绪能力,还能严格维系跨行事务边界。 主流复制拓扑分为三类:单主复制(Single-Leader)由主节点统一承接写操作并向下同步;多主复制(Multi-Leader)用于跨数据中心并发写入但面临棘手的冲突合并;无主复制(Leaderless)则倾向于尽力而为的弱一致性。作为单主的高一致性演进,链式复制(Chain Replication)强制写请求由链头(Head)接收并向后流水线传递,读请求完全由链尾(Tail)承接,以极简结构确保了读操作必定反映全链路已落盘的数据。 故障转移隐患:脑裂与异步复制漏洞 (25:00) 主节点宕机通常基于心跳超时判定,并通过共识算法选举新主。若旧主节点恢复后未交出写权限,系统将陷入极其危险的“脑裂”(Split-Brain)状态;而在包含数千分区的超大规模集群中,核心网络分区更可能因成百上千个分区同时并发选举而彻底压垮控制平面。此外,固定超时阈值容易在网络抖动或 GC 暂停时引发误判,工业级系统多采用基于统计概率的自适应故障检测器(如 $\Phi$ Accrual Failure Detector)动态评估网络状态。 大多数分布式数据库默认采用异步复制以保障写性能,但这在主节点猝死时会导致未同步数据永久丢失。GitHub 历史上曾因旧主库未同步的自增主键(Auto-increment ID)在从库晋升后被新记录复用,且外部 Redis 缓存仍保留原映射,最终导致 Lyft 团队直接穿透访问到了 Uber 的私有代码仓库。这一事故表明,异步复制环境下若缺乏全局唯一标识(如 UUID)防护,主从不同步极易演变成跨租户的数据安全灾难。 复制日志实现:从物理字节到逻辑行变更 (45:00) 复制日志经历了三代演变。基于语句的复制(Statement-Based Replication)虽然紧凑,但遇到动态非确定性函数(如 NOW()、RAND())、自增列依赖、存储过程或包含外部 API 调用的用户自定义函数(UDF)时,会在从节点产生失控的副作用,工程上已被主流架构淘汰。 预写日志(WAL)直接记录磁盘数据页的二进制变动,但其物理结构与底层存储引擎深度耦合。这导致跨版本滚动升级时,主从节点若数据结构不兼容便无法通信,强制依赖停机维护。为此演进出的行级逻辑日志(Row-Based / Logical Log)与底层解耦,仅按提交序列记录行的插入、更新与删除内容。逻辑日志不仅允许主从节点运行异构引擎(如 B-Tree 与 LSM-Tree 混用)并支持无痛零停机滚动升级,还催生了变更数据捕获(CDC)架构,使数据库变更为下游流计算的事件源。 异步延迟下的读写一致性与单调读控制 (60:00) 事务隔离级别在主节点解决并发胜负,而复制解决主从同步,二者在概念上完全正交。在异步复制下,用户写入后立即刷新容易遭遇“写后读”(Read-Your-Writes)破裂。工程常见解法包括:按实体归属实施路由分流(用户自身资料强行走主库,浏览他人走从库),以及在 Redis 中维护更新时间戳窗口以决定读主还是读从。 当多个从节点延迟进度不同时,客户端轮询读取还会出现因果颠倒的“时光倒流”现象,违背单调读(Monotonic Reads)。最有效的保障方案是将同一用户的读请求哈希路由至固定的副本节点。此外,尽管理论上复制可以脱离事务,但为了避免银行转账等场景出现部分数据暴露的中间态,现代系统均将事务作为复制与打包投递的基本单元,并全面弃用挂起连接的古代长事务,转向毫秒级短事务。 Scott Shi 总结指出分布式架构没有万能解法:对于社交网络等读远大于写且聚合复杂的场景,通常通过写后读路由结合反范式化存储来保障体验;若业务对数据一致性存在刚性约束且无法解决写冲突,应坚持单主模型,或在多主环境下引入无冲突复制数据类型(CRDT)以数学规则自动化解冲突。

Source quotes

[0:00] Scott Shi: 不会的,它只是 generate 已有的 code。 [5:00] Scott Shi: 数据往往比代码活得更久 [5:00] 道格拉斯·亚当斯: 事物可能会出错与绝对不可能出错之间的主要区别在于:当一个绝对不可能出错的事物出错时,它通常会变成彻底无法挽回的灾难。 [5:00] Scott Shi: What could go wrong, eventually will go wrong [14:48] Scott Shi: Kafka 本质上就是一个简化版的 Database [25:00] Scott Shi: 黑社会的旧老大突然死而复生 [55:00] Scott Shi: data structure 直接 mess up [60:00] Scott Shi: 复制(Replication)与隔离级别(Isolation)是完全正交的两个概念 [60:00] Scott Shi: who is winner, who is loser [70:00] Scott Shi: 能看到未来 10 秒

Original transcript

[0:00] 在开始前,我先给大家看一个很有趣的tool,就是我这两天在Xplot的东西,就是OpenAI的这个API。这个OpenAI这个是最近开放一个API,然后它现在的话其实BetaUser只Release了大概四五百人,然后可以做很多东西,我觉得还挺有趣的,比如说比如说 I think 是吧,就是它正常的就是一个填词,但是你可以比如说你说我输点东西然后它给你填然后就是 I think therefore I am 是吧然后他给你填这个词,然后就是他能做的一件事情。比如说,然后他这块可以填一些configuration,比如说他response length,temperature是randomness,然后还有一些NLP的东西是吧。然后我还尝试了一下其他的,比如说,我推呢。是倚谢谢大家谢谢大家谢谢大家我想的是iceberg是吧,然后他就告诉我说,what we know is a typical iceberg然后这种,它可以填词,然后它其实是一种 pattern recognition,它还可以做以下的东西,比如说可以做 classification就是他给的一个例子,比如说他Facebook的话是media和这个technology比如说 FedEx的话他就给你填 FedEx 是什么公司你run的话就告诉你FedEx是什么公司,他告诉你是logistic transportation是吧我们这块可以输一些比较冷门的工资反正我试了一下比如说EAA出过去的话它还是能Pause-ride的就是个 video company 是吧然后好然后就是这个classification,然后它还可以做一些summarization和这个text command 和这个Q&A和chatbot我觉得还是做的比较好的,我仔细去试验了几下,我觉得试的还是比较好的。嗯谢谢大家好对就是这样子的,它反正是个taxing,taxout的一个程序。我现在的话有几个想法想做,但是管理时间比较缺,不知道这个能不能有空做出来。OpenAI有很多例子,我可以把这个放到这块,感兴趣的话可以读下Document,我放到这个chats里面,可以读下Document和已有的例子,我觉得有些例子还是挺有趣的,比如说他可以,他最多用的最多是 generating 嘛他比如说可以 generate music 啊然后可以 generateLabel 可以 generate idea我记得前段时间有人切了一个很有意思的文章说他可以 generate HTML code 他可以 generate code 是吧我觉得还是比较有趣的但我那个没有仔细读就是说你不用写code了你用它generate html code就可以了但是我没有仔细读,我没有看它具体怎么样子然后我觉得它就是一方面是generator,一方面是做这个,你看它这块有个react component generator他帮你generate code 是吧?帮你generate code谢谢大家我看到它能generate code,然后我看到有generate的email,然后 generate summary 什么的,反正就是还是挺有趣的它是挺有趣的反正它这个也是用这个Deep Learning做的这个是领先的比较多的我看说能generate code我们是不是快失业了 不会的它只是generate已有的code 你比如说这种非常简单的HTML给你画个地方,然后弄个 reference 什么的,这个 code 中最复杂的不是那个什么,不是那个 code generation, code 中最复杂的是那种 logic还是比较复杂的,我觉得感兴趣的话可以读一下,然后读一下这个东西,我觉得还是比较revolutionary的。我觉得还是比较Revolutionary的。好,今天讲Replication 2,就是给overview一下一个Progress,Replication 1,Part 1,也overview一下这个Progress,是我们今天讲的这一部分了。然后,就是下次的那个System Design呢,就是Mock Interview的话,可能是讲Tiny URL 或者是 cracklist 中的一个,因为我们下一周的这个interview 的这个 Leo 是吧,candidates 是 new grades 是吧,还没有毕业,还没有毕业。所以他这个项目问题可能会简单一点,但是可能会问到。然后我们今天就看 replication。对刚才那个感兴趣的话,你如果有什么好的 idea,然后你想试一下的话,你可以联系我,我可以share一下我的 API Token。然后你可以试一下,我觉得是比较有趣的,我仔细看了一下我觉得比较有趣,其中几个use case可能还用的挺好的。 [5:00] 他这个比之前的improve很多好好 讲 replicationReplication就是跟Partition是在一块的,就是在内容书上一块的。Replication是在Partition之前,但我觉得我应该把它放到Partition之后。Replication 相对来说是整个Distribute System里面造成麻烦最多的地方因为我们不停地在强调一件事情,就是在distribute system 里最重要的是data嗯data的话有不同的version,是吧?不同的version data,然后有不同的code,不同的version code,然后一般data都是out to code的然后在replication里面,在data中,造成data的inconsistency的时候,很多时候是你做replicate的时候,你有一个replicate的时候不是以下replicate过去的,是吧,是async replicate,然后我们都有一个概念叫eventually consistent,所以这个replication 因为这个eventual consistent和这个async replicate就造成了很多问题你这个 data copy 的时候造成很多问题所以 distribute system 里面这个是单机没有的问题,就是distribute system里面replication是造成很多问题的源泉之一很多问题的原权之一也是有不同的database流派之一所以说你说就是Sycle和NoSycle,其中比如说DynamoDB,DynamoDB这个是我们不说,就是我不喜欢把DynamoDB叫WideColonialDatabase,我也不喜欢把DynamoDB叫做KValueDatabase我觉得DenemoDB更像Leaderless Replication的Database,或者Denemo-like Database因为它主要解决的问题很多时候我觉得它最多解决的问题就是replication的一部分问题,就是它解决replication的问题,focus on replication的问题。然后distribute system 中很多最复杂的问题也是就是比如说那个 concurrency control 啊然后Transaction也是多多少少和replication有点关系的然后replication使它的东西更复杂了一点然后然后说的conflict,distribute system中基本上conflict基本上所有的这个distribute system的conflict也多少来自于replication好所以说这个replication是distribute system中最复杂的一个问题所以这块的话我打算分两次讲 分两次讲然后如果大家 我也希望大家有空的话去读一下这个细节啊 特别很多东西的话 有一些细节的话你可能需要再反复读一下反复思考一下这个是相对比较复杂的一个部分然后我觉得这个艳语写得很好,就copy过来了,说,a major difference between a thing might go wrong and a thing cannot possibly go wrong,是如果一个 thing cannot possibly go wrong,go wrong,then turn out to be impossible to get a repair.就是说可能会出错和不可能出错的区别就是如果一个不可能出错的东西出错了,那就变成一个disaster了是吧,就很有可能impossible to repair 了这也是distribute system 里面我觉得很重要的一个概念就是一定要分清什么东西是可能出错什么东西是不可能出错的如果是一个可能出错的东西在distribute system 里面有个概念就是 what could be go wrong?就是eventually will go wrong 是吧一个东西可能出错,给它足够长的时间它一定会出错的但是不可能出错的话就比如说有一些physical law是不可能出错的是吧那就不可能出错我们就可以我们就可以safely assume不可能出错所以这个是有本质的区别的但是在包括我们设计一个系统的时候,特别你要考虑到这个DataSafetyNess的时候,特别是比如说这个Data要AbsolutelySafe的时候,或者AbsolutelyCorrect的时候,那你一定要想到它什么时候可能会出错,那可能出错这个scenario,这个condition是一定会出现的,给它足够长的时间是一定会出现的。然后从replication的意义上来说,它就是把一个同样的data在不同的 machine 上 copy 了很多份然后这个的话是通过 network 来连接起来的我们为什么要做 replication 呢?replication 主要做有几个原因一个是为了让这个 data 呢就是从 data 呢 geographically close to you比如说你现在的data的话,比如说Amazon的话有十几个,二十几个available room,是吧,Data center available room。如果你现在只有一个Data center,北美只有一个Data center的话,那你是个欧洲的用户,你的lentency就非常高。那比如说Uber只有两个data zone,是吧,Data center,Phoenix和DCA。那如果你去比如说Russia打一辆车的话,那你的lentency就很高。所以你的当你的data center,geographically close to you的时候,lentency也会低很多。这个的话会低,可能average可能低几十毫秒,就是几十毫秒到100毫秒之间,因为这个跨太平洋的这个lentency大概就是 [10:00] Es que no... P99 + 100 mts Y también, hay que replicación de la aplicación, que es para que si el sistema de la data center se desvanece, o que se desvanece, o que se desvanece, o que se desvanece, o que se desvanece. O que el sistema de la data center se puede ayudar a hacer su servicio. Esto es lo que está en el caso de la área de la área de la área. Es decir, que puede, al menos, que en un lugar de la data center, o que hay que tener una data center o que hay que tener un centro de la área de la área. Y el servicio no es de la área de la área. Es decir, que es lo que no es el servicio. Y eso es lo que se ha hecho. Y luego, Otra requiem es que es Si se ha hecho una aplicación, si no hay una garantía reciente de datos, no hay garantía que la red es el primer dato. Si se ha hecho una aplicación, puede servir la red-corred. Esto es muy necesario. Es posible que la red-corred de datos. Y este libro es muy largo, hice un gran cagón, un contenido de la cagón. Hoy vamos a hablar de single leader replication. Single leader replication es un parte más de un parte más de un parte. El single leader replication es más de lo que es usar MySQL o Relational SQL. hay una primera server y dos backup server Y si hay que tener que hablar de multiliter replicator "Multi-leaders replication" es el primer de la aplicación de Uber, es el "multi-leaders replication". en poco a Facebook ato, a multiliter replication. Es una forma de una obra de un gran tipo de trabajo. Es decir, es más que un líder de un líder de un líder de un líder. y entonces se utiliza multileadar replicación. Pero en el caso de una sola leadar replicación, no hay que resolver el conflicto. Es como resolver el conflicto, porque multileadar es muy difícil de conflicto. No es como single leadership, es muy conflicto. Y finalmente, es hablar de un líder. Cuando se hace un líder de replicación, lo que es un líder de replicación es muy diferente. Es decir, en el database, como el DynamoDB, es el Best Effort Database. Es posible que la data se puede hacer un dirty read o dirty write, ¿es verdad? O sea, la data se puede hacer un repair, ¿es verdad? Y la data se puede estar estale. Entonces, no es el dato que la garante es que la garante es el dato que la garante es el dato. El dato consistencia y garantía de compromiso mucho. Es decir, más de lo que más se sienten como el mejor esfuerzo de datos. Gracias. Entonces, vamos a hablar de tres partes. Y ahora, el enfoque es el single leader replicación. En el single leader replicación en MySQL o PostgreSQL, en la aplicación de MySQL, en la aplicación de PostgreSQL, muchas veces, como nosotros estamos haciendo un servicio, no hay ningún servicio, probablemente necesitar un uso de usuario. Entonces, cuando se publicó un usuario de un usuario, cuando se publicó un usuario de un usuario de un usuario, que se puede cambiar el usuario de un usuario de un usuario. Y en el usuario de un usuario de un usuario, que se puede cambiar el usuario de un usuario de un usuario. El nuevo es el nuevo, es el nuevo nuevo. Y en el primer database de nuevo, el nuevo nuevo nuevo nuevo. El Follower puede ser que no se puede ver. Depende de la synchronized replication o asynchronized replication. Entonces, cuando se despegue a la redactoria, cuando se despegue a la redactoria, se puede haber perdido el contenido. y no se han cambiado el dato. ¿Y por qué? Y entonces, es un single leader replicator. Es decir, cuando se ha hecho todo el requerimiento de las preguntas, todos los que hayan un líder replicas. El líder replicas es que es en línea, el show the request es de la respuesta. El show the right request es de la respuesta. El read request es que puede ir a leer replicas y a follow the replica. Cuando se... Replicado en el réplica, lo que tú lo has visto es el día de la data. Cuando se ha visto el seguidor replicas de la página web ¿No lo que no es el caso? Es el caso de los datos. Y ahora, todos los relaciones de datos de base están en la base. Es una herramienta de la construcción. En configuración de MySQL y Postgres, no sé si hay una experiencia de Terrafo. ¿Qué es el balancer? ¿Cómo se coloca en topology? ¿Y el método de mi SQL? ¿Cuáles son los primarios, los master, los slave? Es escribir esto. Lo que se dice con MySQL integración [15:00] 挺好的Terriful.和MySQL的Integregation 写的挺好的,然后你可以这样的话通过Configure,写Terraform,然后Configure一个Architecture,是吧?你可以把这个,比如说可以把LodeBalancer放前面,然后你MySQL primary是哪一个IP address,然后Secondary是哪一个IP address,你是Synchronize replicate还是Asynchronize replicate,这个都是可以Configure的是吧?其次的话,这个Single Leader Replication 不止是用在Database的它也用在 Kafka,Message Queue 等等的我始终认为 Kafka 就是一个简化版的Database,它不能用作Database,但是它就是一个简化版Database,就是它就做了Replication和Partition,就是 Kafka是实现了Replication和Partition的一个就是 Durabody 一个 message broker或者是replication和partition的一个system,实现了replication和partition的一个system。我觉得如果感兴趣的话,读著Cafrica的原代码,看一下设计理念,也能看到它不同的replication和partition的处理方式。我们就还有个问题说CDN向Cache多一点还是向Replica多一点?CDN是Cache今天去看一下我觉得Cache和Replica其中一点很不同的区别是就是有几点不同吧第一点不同是, Replica 是当你 Master 挂掉以后,当你的 Leader 挂掉以后,你的 Replica 要随时 Reddit 成为 Leader 的这是一点不同,你的 Replica 和 Master 本质上是没有区别的你的 replica 随时可以成为 leader 如果 leader permanent 和 offline 的话 这是一点其次的话 在 database 的 transaction 里面 你的一个 transaction 可能 update 是多个 field 是吧你比如说你更新 transaction 做一个 payment transaction 你从 A 账户里面划走钱 要把 B 账户里加上钱 是吧这是一个就是你要在两个record更新,那你就是两个road更新。然后Cash的话是什么呢?Cash的话它是没有这个transaction概念,所以你读A账户的话,你和再读B账户,你可能两个Cash是不同的state但是 Replica的话你是有这个Transaction的概念的当你进行 Replication 的时候你不会出现一个中间State 是A账户少了100块钱B账户没有加到那100块钱你在replica是读不到这个state的这个更像RebeccaReplica 就是这个 transaction 的连续性, transaction consistency 是保证的,就是你进行 replicate 的时候不是简单的,就是 single record 的你去整个 transactions in your replicate如果Single Record进行Replication的话,你的Data在很多时候是broken的,是吧?你不能从A账户里面拿出100块,B账户里面没有加进100块,你的Data是broken的。如果你这时候Master down掉的话,那凭空就少了100块钱。所以这个是 Replica 和 Cache 还是有本质的区别然后我们进化看一下Synchronized和Asynchronized的一个replicationSynchronized replication这种情况下,有一个Leader有两个Follower,当User1234往Leader写Data的时候,他写Data说我要update一个新Message,新Image,然后这种情况下,Leader自己搞定了以后,Leader会告诉Follower,请把这个data更新Follower1返回了以后,Leader返回OK。当我们这种情况下的话,这个Follower1就是Synchronized Replication,也就是说当Leader返回给UserOK的时候它至少有 leader 和 follower 一 是 data 是一致的 就是 in sync 的但是它FollowR是Asynchronized Replicated所以这种情况下如果比如说第二个user比如说我来读这个data的话就是我要看user1,2,3的profile的话Image 的话,如果Corel是User2,就在这个之前CorelUser2的话我读的是旧的 data,尽管这个如果靠这个 follower 1 的话,我会看到新的 data,但是如果靠 follower 2 的话,看到旧的 data因为它是Asynchronized 进行Replicated,所以这个是有很等质的不同的。这里面有个Special Case,如果有一个叫Chain Replication,我觉得设计比较有趣的。什么叫那个Train Replication什么叫Train Replication呢比如说它有若干台机器,第一台机器是Master,第一个Replica,第二个Replica,第三个Replica所有的写,所有的write都是写到master的,就是没有问题的然后写了Master以后就开始replicate了第一个机器,然后replicate了第二个机器,然后replicate了第三个机器所有的读都是从第四个机器里面,就是最后一个机器读的所以当你读到这个data的时候,你能guarantee这个,基本上这个data呢,基本上在之前几个全部就是 [20:00] applicative work. It's just that you read the data, it has at least in the APC on all the replays. So it's consistency guarantee will be higher. Microsoft Azure of chain replication Of course, it may have different topologies Let's think about it. If we set up a new floor, we can simply simply simply copy the data file from a node to another node. There are many reasons. If we just copy the data file, for example, we discussed the A to 100. If you copy the data file, you may see that A to 100. So this result is broken, it makes sense. But if you don't consider performance performance, You can lock the database. Now many traditional companies say it will maintain. When you maintain, you have six hours left. You can do this and then put the database down. and dump it after copy it But you can't do that in a network company. How can you do that? There are several ways. There are several ways. The easiest way to do consistent snapshots is to make a consistent snapshot. So, you can use this data for consistent snapshot, You have transactions from 12345 to 100. You now have the data from the 50 transactions applied after the snapshot. And when you read this, we will talk about concurrency and control. You often support this read, but not block read. When you read the last 50 transactions or snapshots, you actually don't block the other data, so the data availability is no influence. Right? And then when you copy the snapshot to follower node, and follower node copy after you can connect to the leader, and say, I have 50 transactions, I can give you the remaining transactions. And the leader starts to send the transaction to the other. And also send the 51 to 100 transactions to the follower. And the follower is to apply. and then the approach will not change. The approach will change. The approach will change. The approach will change. The approach will change. The approach will change. The approach will change. The transaction can change. When it was done with all the 50 to 100 transactions, the master has already had an extension of 101 to 102. Yeah. Now we can continue processing the replica, just from the replica process. This replica can serve the read request. Of course, if the leader is down, the replica can be held as leader's responsibility. This is the process of setting up new followers. And then we'll see if we have two databases. One is follower and one is leader. When you have follower failure, we call it failure. So if you have follower failure, it is a recovery model. We talked about system design model. Filiere recovery, Filiere fill, right? and I forgot what else is, I have an asynchronous model. So if we can see the follower, we can recover. If we can recover, we can create a new instance, and then instink and master. If it recover, it will keep a list of log change. It will be a list of log change. It will be a list of log change from leader. Then it will be a list of log change from leader. If it is a lot of time, it will then put all transactions in the next few months and sync it. Then it will then say it is in sync. It will be like this. If it is a leader failure, it will be more difficult. There are different ways, depending on what you do, the overall design is what you do. If you are two-faced If you use two-phase commit, it is a serialized, Serialized transaction, it's our consensus model, two-phase commit. If you are two-phase commit, many times you will not support the leader. We will talk about it, if you are full-phase commit, many times you will not support the leader. If you are not a two-phase commit, you don't need two-phase commit restrictions [25:00] 你可以把一个 follower 执行成 leader然后可以重新 configure 一下这个这个过程叫 fill over很多时候这个feel over不是被动的feel over,很多时候是主动的feel over,就是为了,就有点像那个File drill,就是你File drill的次数是远远多于你这个File的次数,像Uber的话我记得之前的话每一周会有一次feel over,后来好像改了,改的更infrequent一点,还是一个月还是两个月一次feel overFillover就是它把Primary datacenter就变成Secondary,然后把Secondary datacenter变成Primary就做这个过程,感觉很多时候更像file drill一点,保证你真的file fill over 的时候,你的service不会到。那么fuel over的话,它会发生以下几个步骤。首先是,它要看一下这个先要判断出这个leader是不是真的fuel掉了。这个leaderfuel掉可能比如说这个机器leadercrush了,或者是power outage,或network issue,是吧?你是没有,如果没有一个oracle的话,你是不知道什么东西可能go wrong的所以大部分时候我们是用timeout的说这个leaderfail掉了当你比如说你timeout的设置成三秒然后你这三秒leader没有反应的话就说这个leaderfail掉了那第二步的话你需要truth一个newleader这个truth newleader的process呢其实是非常非常complicated的因为你要达到consensus在truth newleader的时候很有可能你有新的truth newleader的时候很有可能你有新的这个就是follow也go down掉了是吧可能会出现各种各样很复杂的情况所以这个Consensus Algorithm是其实设计起来是非常复杂的我们再稍后也会看一下这个Consensus Algorithm当这两个都完成的时候,你会 configure 整个系统用你的新的 leader 作为新的 leader 。然后这个是很重要的一部分,就是不只要所有 follower 认识到这个新的 leader, 新的 leader 自己要知道自己是新的 leader 。并且当你旧的 leader 回来的时候,他自己还是以为他自己是leader,他没有意识到已经有一个新的老大了是吧就是旧的黑社会老大挂掉了,然后你选了一个新老大,然后就黑社会老大突然又复活了,他没有意识到自己不是老大了是吧?所以他必须跟其他人沟通,别人都说你不是老大了,你必须成新的老大的小弟是吧?然后你必须要意识到这个问题,然后如果没有意识到这个问题,你如果有一个旧的leader,坚持认为他还是leader,然后同时有一个新的leader,别人都认识他,他新的leader的话,这个问题叫Split SpringsSplit brain 就是脑分裂,Split brain这个问题其实还是很复杂的稍后的话,也会讲,这个系统,这本书里面也会讲一下怎样处理Split Brain的过程。Split Brain在历史上其实出现过挺多次,并且每一次出现都有一些非常catastrophic结果,就是非常糟糕的结果。这个有很多学以类的教训,这个Split Brain是非常惨痛的一个教训。好,这个Leader field还是比较复杂的,我想这块提一下的就是我想这块提一下的问题就是,我们注意到,因为它第一步的detection 是timeout如果我们注意到的话第一步Detection Timeout很有可能有一个机器它的连接很不好比如说三分钟后timeout了,然后过一会儿就回来了经常会有split brain的问题并且比如说我们再举这个复杂度还是很高的比如说再举一个例子我们就是上周四讲了partition我们上次讲了partition,在partition中,如果你比如说像设计Uber Schema类似这样的database,你一上来要有4000多个partition然后4000多个partition,就表示你有8000个follower那就是说,你其实是有多少个Master和两个Slave的?你有4000个一个Master和两个Slave。就是很复杂的,你有4,000个 master 和 8,000 个 slave然后是这样子的,所以在这么大一个系统中,假设你其中的一部分 network出问题了你一部分 network 出问题了,你的 network 有个 partition 是吧,你的 topology 在 database 里面有一部分 network彻底挂掉了,那你很有可能同一时间内有几千个Leader有几百个到上千个leader,同时要重新被election。这个其实复杂度是非常非常高的你的系统也是很有可能你的service就直接dun掉了因为这个一个Leader Election的过程经常要花100到200毫秒,就是200毫秒,当你做一个Leader Election的时候,你做Consensus的时候,不管你是做Rraft还是Paxels,你很多时候要花100到200毫秒。当你有几千个机器同时在做Leader election的时候,你的Service很有可能 [30:00] 是有这个就是很不稳定的所以这个 Network Partition 就是Data Center里面Network如果有一条主干线,这个Network被PART-D,就是这个主干线不通的话其实很有问题严重性很大的好,我们接着讨论这个Synchronized Replication这部分我在讨论这一张这块之前,我想问一下之前有没有问题,大家在这有没有问题,这个fill over, synchronize, and asynchronous replication, and follower setup,没有问题?没有问题的话我们就接着讨论replication的问题好就是我们是做replication的时候可能是asynchronized replication当我们用asynchronized replication的时候我们的新的Leader可能还没有把所有的这个新的Leader可能还没有接收到所有的old leader 的updates比如说你开始做 transaction 是吧银行做transaction,你从A账户转了100块到B账户,你在master写好了以后就返回了,然后所有的master和slive之间transaction是asynchronized做的,当你 master 返回 OK 的时候,结果你 master 刚返回给 OK 然后 master 挂掉了,是吧?然后你的那个 replica, data replication 还没有到 slave 上但是user以为上户转钱完成了然后Yoser就回去了,结果Yoser下一次访问的时候,因为这个Master已经挂掉了,他知道的东西没有人知道,这个人含恨而终,Master含恨而终,然后Slave上来以后他其实并没有新的信息,所以你以为完成了这个Transaction,实际上并没有完成。所以说这个就很严重的一个事故了,但是这个还是大部分database还是采用这个asynchronized replication,因为当你做synchronized replication的时候,你很多时候你的P99的performance要直接层二了,就是你之前是100个mins的lentency的话,现在就直接变成200个mins的lentency了,所以这个Synchronized Transaction, Synchronized Replication,这个Cost是非常非常高的,Synchronized Replication Cost是非常非常高的。然后其次的话,我们刚讨论的这个Asynchronized Replication,当你有新的Leader,就是旧的Leader挂掉以后,新的Leader可能会lost data,这个呢会变得有时候会变得非常非常的dangerous。这块就举了一个GateHub的例子,就是GateHub里面它实际上是,就是GateHub当时有一块设计就是说,他用这个database的这个Autoincrement的这个index 是吧?就是当你insert一个row的时候开始是1,然后是2,然后是3然后是4 然后是做这个就PrimaryK的 牛肉的PrimaryK的然后它发生了一件事情就是它这个PrimaryKey呢,这个Data又连接到了另一个Redis里面,就是它这个Row又连接到了RedisRow里面然后用这个呢作为Prep 就是用这个Redis进行这个Record我这块稍微给大家画一下稍微有点复杂我这块稍微给大家画一下,我们画一个Primary,旧的Primary和新的Primary。然后我们这块假设一个table,之前是1的话写的是,比如说是Uber的因为是Github,假设是Uber的一个Pool Request,是吧?然后这个是Uber的Pool Request,然后它还没有Think到这个New Primary时候,Done掉了。然后这个Primary down掉了,所以说到这个新的Primary的时候呢,你就写了一个Leaf的一个ToolRequest,是吧?也就是说这个Replication并没有完成到这个就是写了旧的primary档架以后这个replication并没有完成所以这个Uber的pr呢就是只有旧的有了然后他就喊恨而踪了然后新的里面呢因为他是UltraincrementID嘛就写了一个新的pr到LiftRequest 是吧所以他这个Lift的pr呢他这块因为是metadata所以他用这个它要连接到这个Redis上的,有一个第三方的就是一个Storage RedisRedis上到的是具体的code具体的code 但是它这个row其实连的是uber的code [40:00] 不好意思啊 电脑死机了不知道怎么会是死机掉了啊啊然后我觉得这个故事很有趣的,这个不是一个故事,真实的例子还挺有趣的,其实跟故事一样嘛。GitHub 里面发生了安全原因,然后安全DOWN的原因就是因为它用这个 MySQL 的 Alter Incremental ID 和 Asynchronized Replication然后并且它这个这个 SQL 的这个 data 是 source of truth 然后 refer 到一个 redis 上做这个具体的这个Data Read的所以这个这个就很有趣啊,就导致了你可能能访问其他公司的代码,是吧?或者是这种很糟糕的事情其实有一种简单的方式可以guarantee这个事情从来不发生,就是当你不用这个AltrIncrementID而用GenerateUID啊,就就,至少Garantee你不能访问,就是当你出现这个事情的时候,你只是不能访问你公司的代码是吧,你不会突然让别人访问了你们公司的代码,如果它copy下来怎么办是吧,你会不会有奇怪的法律纠纷是吧,然后这个这个问题还是很严重的。谢谢大家所以这个包括我们前两天看到这个之前那个整个这个Cloud Flare 弄掉了是吧也是一个configuration 称之它把所有的 data 全部所有的 Cloud Flare 的 data 全部就是分配到Atlantis City了亚特兰大那个City了,然后Data Center了,然后整个这个Cloudflare,这个Backbone Network就全部挂掉了我觉得这个也是一个Human Arrow吧,就是你读这个Human Arrow,就是看到我们能不能从中学到一点东西。我觉得那个文章里面学到一点东西是这个Cloudfire 居然有自己的 private network,它整个backbone的这个network,都是private的network是吧我觉得要我选CDN的话,我可能会有先选他们的吧,因为他毕竟有钱,见得起自己的private network 不管是security还是performance都会好很多我觉得这个我可能会就如果我选CDN的话我可能会优先选他们因为他毕竟是有自己的private network好我们接着谈着replication的问题其次的话就是比如说你旧的老大挂掉,然后新老大上任的话,你可能会有 split-bring 这个问题是吧?然后两个人都认为,都可以认为自己可以接受 rights然后当你两个都接受Rise的时候,那你就有conflict 是吧然后比如说user向A转了100块钱然后User A 向 B 赚了100块钱,然后存到这个 Leader 1 里面了然后user向B又赚了100块钱存到LeaderB里面了那如果这两个有config的时候那到底是user赚了200块钱给B呢还是user只赚了100块钱,其中一个是fuel retry呢,这个问题就很复杂了,这个就很难解决了,是吧,所以他这个当你有speed brain的时候,这个data是很容易出问题的然后再depends on你的use case和business use case的话这个data可能会造成非常catastrophic result 是吧然后其次的话最后一点就是什么样是合适的一个read timeout的时间如果你这个read timeout的时间选择太长的话比如说你这个recover timeout的时间选择三秒的话如果你这个Server不停的这个不停的就是就是Server这个Leader不停的挂掉那你经常的话你的Response Time在三秒以上是吧就比如说当你有Leader挂掉的时候你低点挂掉这段期间,至少有三秒左右,不能送任何request。每当有个Leader挂掉的Events的时候,你三四秒不能serve一个request,或者我们可以把这个时间放的更长点,就是extreme thinking,经常是你做design的时候一个很好的一个标志,就是说假设我们把这个timeout时间设成一个小时,那假设设置成一个小时就一个小时已经forever对抗 [45:00] 就是上次来说一个小时是forever了就是特别request来说是forever了假设你timeout设置成一个小时如果你把timeout设置成一个小时的话你有一个request过来就说明这一个小时这个request我不能得到任何response那这样的话肯定从user experience很糟糕的但你把timeout的时间如果设置特别小的话比如说设置成50毫秒50毫秒的话那么的话当你的那个network稍微有点 congested或者是当你这个server就是master稍微有点 congested稍微有点毛那你这个server就这个master就被declare dead然后就promote一个slave了那你这个performance来说当你做这个你想思考一下你首先要做consensus选成一个新的master是吧先50毫秒然后说这个leader挂掉了然后做consensus选一个新的master需要200毫秒然后要所有人同意他是新的master是吧其实也在consensus里面就是200多毫秒两三百毫秒那你救的回来以后你还要认证它是新的Master就是300毫秒的一个cost 是吧就是从时间来讲大概300毫秒cost然后你还要resolve所有的conflict和data inconsistency 是吧你Master有Data如果你slave没有的话你的Data就会loss你要处理这个问题所以这个时间太短了,可能更糟糕所以说这个选正确的时间是非常困难的一个方式如果你对 network 熟悉的话你记得 network 里面,就是 TCP 那块的话是有一个 timeout 的时间就是你开始是就是按它那个有一个叫有一个叫TTP是吧好像叫TTP的一个东西TTP的一个东西还是叫什么Round Trip TimeRTTP对Round Trip Time然后你可以攻击RTTP然后再加一个具体的计算值比如说你RTTP是平均值是多少然后variance是多少如果你是一个正态分布的话你可以算一下它多少个data然后可以进行计算然后它是个正态分布但很多时候不是正态分布就是你可以通过恐惧学然后得出一个概率来计算出来它到底有设置这个timeout的时间多少比较好是吧我记得Cassandra是有一个Cassandra是有一个小的一个service是自动的计算这个timeout的时间的但很多时候这个timeout时间可能就是hardcode的一个时间是200毫秒300毫秒但Cassandra是可以根据这个RTT和这个Variance来计算自动计算出一个Timeout时间的我觉得Cassandra有一个Cassandra 5什么的Service好我们就是Synchronize和AsynchronizeReplication这块就讲完了然后我们讲这个Replication Log我在讲replication log 之前我想问一下大家有没有问题如果有问题的话直接问就好反正我们直接问就好好没有的话我们直接讲replication logreplication log 的话有多种方式最简单的就是statement based replication这也是最直观的讲做法其实也是很多时候就是database最开始的做法就是做replication最开始的做法什么意思呢就比如说我现在有一个insert statement然后我copy的时候就直接把insert statementcopy到follow比如说我有insert statement到这个leader上我就直接把insert statement copy到follow这个里面是有一个问题的是有很多问题最常见的问题是比如说我InsertStatement有一些non-deterministic functionSculptistic function比如说NOW那你这种情况下呢你就不能把这个NOW copy到Follower因为你这样的话就是在Leader的NOW和在Follower的NOW就不是同一个时间所以很多时候如果有这个non-deterministic function的话比如说Random和NOW它实际上会把这个值呢替代成一个具体的值比如说把NOT就替代成一个具体的时间把random就替换成一个具体的generate的数字放过去然后其次的话你很多时候这个statement呢是有alter incremental column 的比如说它这个它是depends on existing data database 中是吧比如说你这个有alter incremental这个primary key或者是你有这个update然后有condition 是吧然后说你这个statement必须是exactly same order去execute it因为你如果不是Same Order的话你可能之前的一个Statement会改变了这个Condition然后就天原地北了是吧天原就整个就不一样了所以这个是很就是很这个HCase很多其次的话最后的话你可能有一些Side Effect Statement比如说有的Statement是有TriggersTriggers是什么意思比如说常见的Triggers [50:00] 这个 Column A改变的话你要把Column B的值加1比如说Count这个更改多少次所以Column A改变了以后Column B的值要加1这种常见的这种triggersStop procedure什么呢?Stop procedure是也是另一种比较长的一种你可以认为是比较长的一种就是把很多statement写到一起的一种一种一种把很多直信一句写到一起的一种一个一个blockal statement相对比较长一点是吧?比较复杂一点然后它减少了这个就是interaction的过程的stop procedure,现在的话其实stop procedure是之前就是很早之前比较火的,你要是去成传统的公司还会写stop procedure,但基本上新的公司都不写stop procedure,它的弊端太多,它的set effect太多,不管是trigger 还是stop procedure,trigger 可能还好,stop procedure 的弊端太多,也是经常被现在的从事员拿出来说,这个stop procedure 特别容易出现 bug,非常 fragile 的一个常见的批评的对象。然后其次的话,这个 database,relational database 还支持一些 user-defined function,这个也是这个也是,因为它是User Defend Function,你可以在里面写任何东西,你这个Side Defend就很难估算了,比如说你甚至可以User Defend Function,甚至可以做什么API Call也是有可能的。Depends on configuration,API 考甚至有可能的,所以你加上 user define function 的 behavior 就很难估算的,当你进行 replicate 的时候,当你把 statement 从 machine A 考到 machine B 的时候,你就可能会造成 machine A 和 machine B 的 data 有完全不同的完全不同的结果因为你这个set effect如果不是determinist的的话你可能有完全不同的结果当然这些都是可以被解决的,但是它就是会非常非常多的age cases,所以就是为什么说这个statement replication不是一个很好的replication方式,现在基本不用statement replication了。然后第二种replication叫riderheadlog,我们在讲这个Btrade的时候说我们在讲 btrade 时候说怎么样保证你的 data 是变成 durable那实际上在写data在你进行B-TRAID更新的时候在你进行B-Trade光线的时候你的Data是先写了Red Hat Log里面然后如果你的data挂掉的话你会这个database如果你这个database挂掉的话database restart的时候你会先do ready-high log然后把这个ready-high log全部apply完然后你才认为这个database recover了Recover了以后才会所有新的Request所以我们因为这个WriteHeadLog就是一个log嘛,所以这个就有点像记录一下这个log,所以说它可以用来做replication,虽然它不是设计用来做replication, 而是设计用来做durability的。就是当你比如说你当你进行crash recovery的时候你用rider headlog做这个recovery是吧所以它是为了durability设计的它不是为了replication设计的它可以用来做这个replication那不是为replication设计的是吧所以这个这种情况下的话我会看到它的弊端是什么的好B端是什么呢?首先这个rata-hat-log,因为它是做Durability的,所以它其实是一个byte,就是a sequence of bytes,也就是说它不是 human readable,它是一个 binary 的 sequence然后是直接写了database所以我们可以直接把这个log呢就是replicator 另一个replicator呢它的有一个assumption是什么呢?有一个assumption是它的这个internal implementation,internal data structure is exactly the same也就是说你的replicator internal data structure is exactly the same as leader大部分时候这个不是问题因为你写的是 Binary 嘛,你直接是,比如说你从 Database 角度来看的话,你就是直接把一个 File Page 是吧,把一个 File Page 以 Binary 进行修改,然后再写回去 是吧那你写的是 Binary 的话,那你必须 Data Structure 因为它不懂 Data Structure 嘛,它直接 Binary 修改的话,你不懂 Data Structure 。如果你 Data Structure 两个机器不一样的话,那你实际上这个replica是完全不work的,replication是完全不work的。那什么时候你的datastructure会完全不一样的?当你进行database更新的时候,比如说你当你进行database更新,你要把这些,比如说就拿postgres说吧,因为这个postgres是这么用的,postgres和oracle database是怎么用的。比如说你database更新,要从version1更新到version2,然后version2的这个datastructure是不backward compatible,和version1是不backward compatible的。是一定要把它bring off来的,然后等它全部replicate好了以后,然后进行更新。那你这样的话,master和slave都是到同一个version。如果你不把它bring off来的话,那势必会造成你的master和slave在某一个时间window里 [55:00] 在某个time window里面 version 不一样当这个time window里 version 不一样同时你在做replication的时候你这个data structure 直接math up 是吧这个data structure 直接math up所以这个是如果你是想做那个no zero downtime就是 zero downtime 的upgrade database 的话那你很多时候可能你是不是要考虑一下是不是不采用这个 Postgres或者 Oracle database 是吧因为你不知道它这个你不知道它 database这个implementation 里面它是不是backward compatible因为这个implementation特别Oracle你都没有明码的你都不知道它代码怎么实践的它如果不是backward compatible的话你整个replicate就不能用了Data就不能用了好所以说我们看前面两个log这个replicate的方式都是不适合做replication的log为什么呢因为第一个log实践方式是用来执行statement第一个就是replication方式它这个log是用来执行statement第二个是用来做durability的所以它不适合做replication log所以在我们非常多的前人的非常聪明的前人的经验下他们就专门设计了一个用来做replication的log叫logical log replication这个是专门用来做replication的它的main goal就是做replication所以可想而知它这个满足所有replication可想而知它满足所有replication的条件我这块要稍微暂停一分钟不好意思好我们看到这个是这个专门做replication这个logical log就是它这个它就是因为它专门设置着 application所以它这个问题会少很多首先呢比如说它这个你写到 database 的时候它会contentual new就是那个 new value all column 是吧然后你delete 的时候会有它delete的 key如果没有 primary key 的话会把这个 old value 的全部 log 出来如果是 update 的的话就是 identify the update row然后把新的value写过去然后呢其次呢因为它是logical log replication所以它其实是有sequence的也就是说像不管是之前statement像statement是没有sequence但是它这个logical log的话它就说哎我这是这是我transaction1这是transaction2是吧这是transaction3按它commit的顺序来是吧就是我进行replicate的时候这是replicate log1这是replicate log2所以你只要严格的按照它的顺序来的话data是不会有 corrupted 的然后然后因为它并且这个 replication log 呢它是和这个 storage engine internal 是 decoupled 的所以它是 separate concern所以你不管你的 storage engine 是怎么实践的你之前是 b-tray 你全部改成 log structure merge tray 也可以但是从这个全部从这个 logical log 来讲它是完全独立的一套语言它只是用就是同样的 data它用一套独立的语言去描述了一下所以它不管你的Internal是怎么实践的它永远是compatible所以你的Follower和Leader可以run不同的version甚至是可以run完全不同的storage engine是吧你Follower可以runlogstructuremergeTree你的Leader可以runBtree也是可以的因为它完全是为了replication设计的一个log format最后一点的话是这个log format是可以可以被这个external application to post的所以您可以读这个log format然后做这个change data capture就是说你可以把这个database本身变成一个streaming的一个structure比如说这个Cassandra是支持这个的是吧就是当你做任何这个Cassandra的upgrade的时候update的时候,你实际上所有的update可以把它组成一个CDC,然后CDC以后可以接一个reader,然后进行相应的计算和操作。最后一点的话就是这个trigger-based replication,这个就是根据这个database update,然后进行trigger,比如说这个oracle-golden-gate是这么做的,它实际上就是比如说你当update role了以后比如说你update这个 role 然后发生一个trigger这个trigger 因为你可以执行任何code 任何一段code这个code 甚至包含你去replicate上把这个data变得一致所以这个trigger based replication 它的overhide 是比其他样高一点 [60:00] - So, Thank you. really directly hand over to Thank you. So actually, we have a question. Log3 is strong isolation. Replication and isolation are not related. Isolation is related to transaction, it is related to master. Master is related to isolation, concurrency and control. Replication is what is the master and slave. replication and isolation are orthogonal replication or transaction. Yes, sure, I was talking on. So this is an orthogonal problem. You can divide these two. I'll repeat it again Replication is master to slave. You won't have config. Master is what is called slave? This is replication. And isolation and control is at the same time. So the master is to decide who is winner and who is loser. So these two are different questions. So this is not related. I love puppies, but the problem is Thank you. Let's see the question called Read Your Write Problem Radial write problem is that when you do asynchronous replication, like the template, for example, if a user added a comment, You write when you write the leader, but you do when you write the follower. There is a small time window. You user said comments and inserts are just done, but it's just master and inserts. If it's from followers, it will say no results. Right? You just didn't read or write. You just wrote the data and didn't read. This is a problem. Super long, no sound insulation. So in this case, there are several functions that you can do. For example, if your use case is like Facebook, you can do your profile. If you're using the use case or Facebook or Twitter, you can read your profile. You can read your profile. You can read your profile. You can read your profile. You can read your profile. You can read your profile. You can read your profile. You can read your profile. You can read your profile. You can read your profile. You can read your profile. You can read your profile. You can read your profile. You can read your profile. You can read your profile. But when you read other profiles, you don't know what he wrote. For example, when you're new, you'll see it in a minute later, you'll think There is a common sense, if you read it, it will be a little more new. So, even if you read it after, for the 15th minute, you read it after the 60th minute. It's not a problem. But you write your profile, the first time you write it, the first time you can expect it to read it. So the expectation is not the same. So you can read your profile from the master's profile. You can read your profile from the follower's profile. So this can be very simple to solve Facebook and Twitter's problems. And then, the other thing is: You can check the last update time. If this thing is just updated, you can read the master. If you can read the follower, you can read the replica. How do you check the time? Because the database is not the same. If you read the MySQL database, if you read the infrequent data, you will read disk. You read disk is the memory of the time of 100 to 1000 times. So you read disk is the memory of the time of 1000 times. So I [65:00] 从in-memory database上读的话那你这个时间是你就可以减少这个时间所以你可以把这个last update time存在一个in-memory database上然后你甚至可以用一个bloom filter然后这个可以使效率很高就用bloom filter一样的东西效率可以很高那这样的话你就说我读这个肉的时候我track它过去一分钟内没有更新过那我就可以放心地从follow而读如果它过去一分钟内更新过那我可能就得从mask上读所以这也是一种方式然后还有一些其他的方式可以读这块的话有一些额外的complicities是比如说你这个service是可以从多个device上返回的比如说你可以通过desktop返回你可以从mobile返回比如说可以从fone上返回那你怎么样提供cross device的read after write consistency他最近常掉线大家是丢了啥?你丢了你怎么掉下呢?几分钟前的事吧OK 所以你这个read after write就是 multi-device 的read after write 的 consistency是就是 multi-device 的read after write 的 consistency你可以通过其他的方式来解决比如说你就是 Facebook 自己的 profile 从 master 读是可以解决这个 multi-Device的ReadAfterWriteConsistency你可以通过其他的方式来解决比如说你就是Facebook就是自己的profile从Master就可以解决这个MultiDevice的ReadAfterWriteConsistency还有一点的话就是ReadAfterWrite的话还有一个很复杂的问题是如果你的repli卡是在多个datacenter现在要求基本上都有多个datacenter你的repli卡基本上是比如说你有两个datacenter的话你可能有一个repli卡是在同一个datacenter然后另一个repli卡一定是在不同datacenter这样能guarantee就是你一个datacenter全部completed down的话其他datacenter也可以serve你的request所以如果你的repli卡在crossdatacenter的话那你没有guarantee你的connection从不同的device比如说你手机连的是一个Data Center你的那个Phone就是你手机连的一个Data Center你的Browser不一定连的是同一个Data Center是吧可能是不同Data Center那么这样的话你如果RAID的话如果RAID from Leader的话那么你可能你的Performance会比较糟糕如果你RAID from follower的话你基本上guarantee你有一个device有一一个 device 有个 time window 是不能 read afterread or write 的所以说 这种情况下可能你有多种 trade off 是吧你可以说 只要是这个 user 统一从 data center A 里面读 data如果只要是这个 user 统一从 data center A 里面读 data你可能能 read or write但是你可能 performance 会比较糟糕 是吧可能Data Center A可能突然 down掉了或者performance degraded的话那你的performance会很糟糕你不能从最好就是当前最好的Data Center读数据然后其次的话你的write可能都要经过这个Data Center的话你的就是你可能需要一个 extra road 是吧比如说你的Data Center比如说榜第一层 San Francisco但是如果你去其他地方travel的话比如说你是去北京的话那你可能performance就很糟糕 是吧你就不能去读Asia的一个Data Center或者是你这个Account的如果是多人share的话同时有人在北京和San Francisco的话那你就不能就进了就是wrote你的request到就近的Data Center而去wrote到一个固定的Data Center那你可能performance会比较糟糕还是跟你的business case来设计到底是你到底希望consistency更好一点read write还是希望performance更好一点你的lentency 更好一点Read or Write 呢还是希望你 performance 更好一点你的 lantency 更小一点这个还是这个不一样的好Read or Write 呢是一个 consistency level就是你希望相对来说是比较低的一个 consistency level还有一个是 monotonic rate 的一个 consistency levelMonotonic rate 是什么意思呢就是说这块给了一个例子这块给了一个例子就是说当你insert一个comment的时候你这块leaders返回ok了然后开始replicate a follower比如说比如说你说这个比如说这个user12345说现在几点了是吧user12345说现在几点了然后发过来然后你2345看到说现在是现在是5点是吧然后把2345说现在是5点是吧所以正常的时间段的话你看这个电话应该是由于1234说现在几点了然后2345说现在是5点但是你在因为它这个replicate是有延迟的如果你到replicate到2的这个performance的话可能就不一样了变成什么呢 [70:00] 看到是现在5点然后一台温现在几点了可能会这样的结果甚至是一种 casual write 没有 being resolved 的情况这是一种情况,然后可能另一种情况是什么呢?就是比如说这也是一种情况,就是也是1234说现在几点了然后234我说现在5点是吧然后你从这个Follow-on-R角度来看的话,就只有看到现在五点,然后你没有看到它的问题是吧,你没有看到这个,你没有看到它一问他的问题是现在几点了,所以你其实就是导致什么呢?就是你读的时候你是read back to the time,是吧?你是实际上读到之前的data就是先看到一个之后的Data,然后再看到之前的Data先看到之后的data,然后再看到之前的data,这个是很有问题的。这个和ReaderWrite复杂度不太一致不太一致的原因是什么呢?就是假设这个radar可能来自于不同的地方,它这个monotonic rig的总体来说是比radaride更 strict的地方更strict 的一个 concurrency control一个, sorry,一个,一个,一个,一个 transaction control.为什么呢它应该是一个 consensus 要求更严的一个 consensus 为什么呢?因为这个 monotonic read 呢,假设你 reader 不管是 read 哪一台机,不管 reader 是哪一个read的哪一台机器它得到的结果都是连续一致性的,你不可能read back in time你不管ray的是谁,你不管Rate是哪一台机器你Read的是incremental的,是递增的,你不可能Read back in time但Radial Write 可能就是当你进行radial write 的时候好你可能会发生一种状态就是你某些user是能read or write 但某些user可以不read or write就是你可以read一个之前的也可以所以它这个总体来说monotonic read是要求更高一点,更难满足一点。所以Monotonic Read 呢,所以它要求,如果你怎么实践呢?一个简单的实践方法就是所有的User读的时候都是从SIM Replica 读因为你这个replica进行transaction上,除非userreverttransaction,footedtransaction是一个一个patch过来的如果你的所有的user固定的从一个固定的replica读的话他不读不同的replica的话那他是不会发生这个问题的比如说这个user 2345读的时候他是先从 Replica 1 上读到了 Comments 然后 555然后再从RubyCut2上然后因为它replica2还没有被replicated,然后读到没有这个comments,是吧?所以它是从不同的,就是它这个request wrote到不同的followers上,所以才导致它这个readback in time如果他读的是同一个 follower的话,他就不会发生这个事情,因为这个 insert operation 是不能 revoke 的谢谢大家有问题吗?好 所以是所以这个问题啊,这个的话就是,这也是Montotonic Rate的一个,另一个这个casualty violation的一个case就是Mr. Boone就说问一下Mr. Cake是几点了,我觉得这个好像是那个Starrgate的那个梗啊,就是Starrgate的一个梗。然后他就问Mr. Boone 几点了,然后他写的时候呢,写到之后了,就是replicate 之后了,然后Mr.Kig说现在是现在是,就是能看到多久到Future,然后Mr.Kick说大概10秒钟能看到Future10秒钟,然后从这个Observer的角度来讲,Observer的看都是,就是看的这个Data呢,从它的角度来讲,因为它读的时候是先读到这个answer,然后再读到这个question,所以它感觉就是violate了这个casualty rule。就是他先读到这个,先读到COS,然后再读到那个,Result 是吧?一般来说是有因有果嘛他先读到果然后再读到原因那就violet 这个casualty rule好呃我看看还有多少谢谢大家谢谢大家谢谢大家我今天就讲到这边,我觉得这块,因为我这个准备slide有点早,所以我感觉我这块 [75:00] Prepare不是太好,我可能下周稍微多花点时间Prepare一下这个,我感觉多举点例子吧,我觉得这个slide准备别点早,可能Prepare不太好,然后其次的话我觉得这块问题确实比较到这的话问题就比较hardcore一点了是吧不管是replication的问题还是后面的这个transaction的问题还是这个consensus的问题就接下来几周的问题都比较hardcore一点所以有时候我自己看的话我也得需要想一下然后我觉得这个可能需要多花点时间准备一下然后我感觉今天有时候这个逻辑讲的不是很好,我感觉今天逻辑讲的不是很好,可能花的时间有点不够,我可能下周稍微多花点时间准备一下计划。然后我们下周的话,我可能重新再讲一下这个monotonic read的计划。我稍微多准备点例子来做,因为这个其实复杂度挺高的,就是很多时候不太理解的话,复杂度还是蛮高的。我可能还是多花点时间去解释一下这个,多几点例子解释一下。这样的话我们下周差不多有22个slides,今天讲了16个slides,相对比较分布的比较均匀一点。嗯我感觉今天这周时间可能准备的不太充分啊,感觉讲的不是太好,如果大家有什么问题的话可以提一下谢谢大家Scott我有个问题,你说这个replica其实是基于transaction的嘛,你的replica每次都是对于某一个transaction完成了以后,然后他他可以replicate到剩下的地方 还是说这两个是完全独立的可以对,这两个其实是独立的概念,Transaction是Transaction, Replica是 Replica,如果你看这个对,是独立的概念,但是当你进行这个 replica 的时候,如果你 replicate 了这个 transaction 只 replicate 其中一部分,而没有 replic 另一部分的话你会造成这个 data inconsistent就是从概念的角度来讲你replicate 的时候你是没必要完整的把一个transaction 进行replicate从概念讲因为你的 transaction 我们知道 transaction 里面可能有多个 object 能 modify 是吧你可能对Object A和Object B同时进行Modify从概念上讲来说你进行Replication的时候你是没必要把这个transaction就是对A和对B的这个update全部进行replicate,同时进行replicate但是从 Use Case 来讲,从这个guarantee 来讲很多时候你要确实要同时做这件事情否则的话比如说你从银行账户里A减了100块钱B加了100块钱你在replicate的时候如果replicate只replicate一半的话这个账户上钱就对不上了所以就是说如果你是只是从这个概念讲的话从replication这个概念讲的话是没必要按这个transaction的顺序来进行replicate的的如果你没有 transaction 这个 consistency 这个 requirement 的话 你是可以单独 replicate 的但是实际上如果大部分时候 你是有 transaction 特别是这个我们讲的 single leader replication 很多时候就是讲的这个 relational database 你看我们现在讨论说的东西都是 relational database 的如果是relational database replication的话你是有transaction的概念的,所以很多时候你进行replicate的时候,你这个transaction进行replicate。谢谢大家所以我们刚刚讨论的就是任何三种log based他们其实如果我们讨论了是SQL database的话都是都是基于transaction上面的,都是基于transaction的都是GCAN,不管是我们可以看一下,不管是之前的statement,不管是statement,你一个transaction可能多个statement,那这几个statement就同时进行replicate就可以了,就是打包起来进行replicate,就list的statement进行replicate,还是这个write-hand log,你一个transaction可能有几百条上千条write-hand log是吧,也有可能,那你只要打包进行replicate就行了,然后最后一条的话也是一样的,也是可以打包进行replicate的。既然有了,我打断一下,如果既然有了这个log的话,那就说明他们是一条一条之心的得到这个transaction是一条一条之形的就是有有甚实性能你是一个 transaction 里面的 statement 是一条一条制行的吧一个Transaction 可以很大的哦,一个Transaction 很大,你知道就是Database的Transaction设计的时候是什么呢?就是之前最早的70年代80年的Database,它的Transaction设计是整个,你比如说去PriceLite,去订一条机票是吧,或者是你去DataBase, [80:00] 一个电影票,你整个一套是一个transaction你一个transaction是持续几十分钟的可能所以Transaction之前的设计是很大的一个Transaction,就是之前的interaction很多,比如说你要订个电影票,你首先要写上电影,然后开始记录,然后再写上到底哪个场次,然后再写上哪一个实践,甚至还要可能额外订点什么爆米花什么的,然后再把它TransactionComb掉,就是整个是一个Transaction。就是Interactive Transaction 是一个 Transaction,这个 Transaction 可以很大,可以知识声很久里面可以有几十条 statement 哦那也应该是到最后完成的时候再写进去吧就是写到这个replica log 里面去吧对,Transaction,就是我们说这个 concurrency control和这个replication不同的一点就是Concurrency and Control is the responsibility of the transaction. If you have a transaction, you can commit to the same. If there is conflict, you can revert to the same. If there is conflict, you can commit to the same.就是它是 concurrence and control然后这个replication是我知道有哪些transaction,然后transaction123453这个选项 committed我现在要把这个12345 transaction12345从MasterCopy到Slave上懂了,了解。懂了吧,所以我们思考的时候,这个Database之前有一些前因后果,之前的上世纪80年代的这个Database的设计思想就是一个transaction就包含了整个business flow,那你一个sonsession持续几十分钟都可能,因为你不停地等Client输入新的Data现在的话都是讲究这种短的transaction,就是你比如说你选个位置就是一个transaction,不是整个变成一个transaction,这个设计其实很糟糕。好,我们有什么额外的问题吗?所以有没有人在那边好谢谢大家好我还是推荐大家读一下这个次来的,因为这个replication,从replication这一章开始,不管是后面的这个Transaction还是Consensus,而后面第三章可能还稍微好一点点是吧第二张这几张都是非常Hardcode的,就是有时候我自己解释的时候,我也得稍微想一下,稍微想一下,我确实得多花点时间稍微准备一下。谢谢大家稍微额外的看一下这个问题还是很复杂的所以大家其实如果不看的话它确实确实就比较hardcore一点了,比较难一点了好,今天就到这里吧,谢谢大家。Scott,我让你有时间,我还有一个就比较小的一个实际的问题。你问好了。就是假设我们现在比如说做一个,比如说一个系统就比如说类似于像Facebook这种,就是比如说Friendship或者user这种 service,然后这很有可能就是全球性的那个,就是部署的嘛然后这个用户数量也会很大然后如果是这样的话我是在想是你如果是做replica的话你是建议就比如说像用MySQL这么做就是你就把一个挑一个主的stack就是作为那个所有的写都在这个主stack然后把剩下的地区全都变成replica情况会多一点。那 depends on the use case.Multi leader,Multi leader 的好处就是,就是availability,就是你的performance更好一点,但是你可能要resolve the conflict.谢谢大家你可能会有conflict来resolve,那你希望你的performance更好一点吗?就是你的availability更高一点,然后lentency更低一点,还是你希望这个data就是更consistent的一点,就是你读的时候你的data更consistent的一点就是看你的 Use Case 相关谢谢大家好OK 但是如果从我刚刚取决于这种application level来说的话是怎么样去我知道你刚说的两个一个availability一个是consistency但是如果你分析的就是就是user service这样一个服务的话就到底具体到业务上面有可能会什么样的影响User Service的话,你看是什么User Service了,你像Uber的User Service和Facebook的User Service是完全两种概念。我为什么说Uber User Service完全两种概念?因为Uber User是没有Friendship这种说法的。你是你看不到其他user的,你最多看到一个driver和看到一个driver,他的interaction比较少你看到 driver 就是你比如说 [85:00] 你直接上只看到就是你 driver 的一个 user 是吧?你会看到自己的 user那这种情况下的话我觉得就是你所有的写和读,你可能都得看最新的嘛,你比如说这个,你可能看最新的,你写和读可能就是Rode到同一个Data Center上然后你就是一个user就是roll的同一个代表上,然后从这个最新的读我觉得是可能比较make sense 的。然后像Facebook的话你这个你其实读的时候你其实写和读的时候你这个读是远大于写的你读诗远大学写的是吧你读的时候你可能要读几十个User的updateHLets然后你可能经常去访问其他优责是吧然后其实Facebook的话是基本上你是希望一定要订个read or write 吗所以你写的话,你写和读自己的user的话写和读可能都是从master的,你读别人的话你可能从slave上读这是一点,然后其次的话Facebook可能他自己应该就是那个Face 还是做了一些denomalization的,就是他实际上是把那个data呢,就是denomalize 一下,他不是normalize data,是denomalize data,然后这样的话他读的一个就是DNOM 的 data 所以它放弄的时候的数字可以更快一点这个还是 我觉得从 High Level 来讲 其实怎么样都可以 但是你有些东西可能一定要满足的话比如radial write一定要满足的话那你可能得考虑一下是不是做一些优化HIGH LEVEL的话我觉得这个还是太HIGH LEVEL了,我个人觉得我个人觉得不管你是从Leader上读还是从Follower 读还是不管是Single Leader Replication还是Multiable Leader Replication,从High Level来说都还是可以的。这个还是跟具体Use Case来相关的。可能还是要说,哪些论则是绝对不能break 的。那如果比如说有些论则是某种Consistency,某种Consistency是绝对不能break 的。那你如果是Multi Leader的话,有一些conflict是很难resolve 的。好multi leader 的一些 config 是很难 resolve 的所以你那种情况下的话你可能要么就考虑用一个 CRDT 的一个 data structure 是吧要么就直接考虑用 single leader 来做谢谢大家就是CRDT的这个Dial Structure和这个MultiLeader这个Architecture我们下周也会讲所以这块不懂的话其实也没事好好,CRDC相对来说比较新,其实最近炒的比较火,但是其实本质上也不是很复杂我自己没用过,但我读了一下它这个concept,我觉得本账不是很复杂的谢谢大家谢谢大家好的 如果大家没有问外问题的话 今天就到这了 我下周可能多花点时间 然后还是希望大家读一下 这个真的是有点复杂的 解释一遍也挺复杂的 希望大家读一下多点自己的思考 然后有问题的话 我可以多讨论一下好,谢谢大家。