Dapper 模型:TraceId 与 SpanId 的传播之道

Dapper 模型 本文是分布式算法科普系列第七篇,也是收官之作。前面六篇从服务发现、共识、流控、事务、消息、负载均衡一路讲过来——现在整个分布式系统已经跑起来了。但最后一个问题:一个请求跨了十几个服务,慢了,到底是哪个服务慢了? 一、故事:Google 搜索到底慢在哪 2008 年前后,Google 的搜索基础设施已经是一个超级复杂的分布式系统——一个用户搜索请求从前端 Web 服务器进入后,要经过拼写检查、查询改写、广告检索、文档索引查询、图片搜索、个性化排序等几十个服务,每个服务又有数十到数百台机器。 问题来了——运维团队收到告警:“搜索延迟上涨了 200ms”。全链路跨了几十个服务,研发团队只能挨个翻日志、看监控、拍脑门猜测到底是哪个服务变慢了。运气不好的时候——一个延迟问题排查一天是常有的事,而且经验依赖极高——只有老员工大概知道"这种情况一般是索引服务慢了"。 2010 年,Google 发表了《Dapper, a Large-Scale Distributed Systems Tracing Infrastructure》技术报告,公布了他们内部从 2005 年就开始使用的分布式追踪系统。Dapper 的核心贡献不是工程实现,而是一个极其简洁的数据模型——用两个 ID 和一个父引用,就能还原出任意复杂度的调用链路。 这个模型后来成为所有现代分布式追踪系统的理论基础——Twitter 的 Zipkin(2012)、Uber 的 Jaeger(2017)、Apache SkyWalking(2015)、以及 OpenTelemetry 标准(2019),全部沿用了 Dapper 的 TraceId + SpanId 模型。 二、前置:没有追踪时,排查有多痛苦 先感受一下一个典型的微服务调用链: 用户点击"下单" → API Gateway(网关——接收HTTP请求) → OrderService(订单服务——创建订单) → InventoryService(库存服务——扣库存) → Redis(缓存——检查库存标记) → AccountService(账户服务——扣余额) → CouponService(优惠券服务——核销优惠券) → NotificationService(通知服务——发短信) 总共 7 个服务节点。如果用户反馈"下单等了 3 秒才成功"——研发需要翻 7 个服务的日志,靠时间戳手工对——“订单服务的这条日志是 14:03:52.123,库存服务好像对应的日志是 14:03:52.245……这两条是同一个请求吗?"——没人知道。 写过的都懂——凌晨三点被叫起来排查线上问题,对着七八个服务的日志靠 grep + 时间戳对,好不容易对出大概链路,发现只是 Redis 慢了一下。没有追踪系统的日子,就是这么过的。 ...

一月 24, 2023 · 3 分钟 · 538 字 · yaomingye

负载均衡三剑客:加权随机、最少活跃与一致性哈希

负载均衡三剑客 本文是分布式算法科普系列第六篇。前面讲了服务怎么发现、怎么保证一致性、怎么限流、怎么处理事务——现在一个请求终于要发出去了。但目标服务部署了 5 个实例,请求该打到哪一个上面?这就是负载均衡要回答的问题。 一、故事:缓存集群增减机器时的雪崩 1997 年,MIT 的 David Karger 和他的同事们遇到了一个实际问题。当时的 Web 缓存系统(比如 Akamai 这样的 CDN 前身)由几十上百台服务器组成,每台存一部分网页缓存。浏览器请求一个页面时——先算哈希——根据哈希值决定去哪台缓存服务器取数据。 问题出在服务器数量变化的时候。假设有 10 台服务器——用 hash(key) % 10 决定数据落在哪台机器。当一台机器宕机——变成了 9 台——几乎所有 key 的 hash % 9 结果都和之前不一样了——几乎所有缓存同时失效,所有请求打向后端源站,源站瞬间被冲垮。 这就是所谓的“缓存雪崩”——不是因为流量突增,而是因为集群规模变化导致哈希取模结果大面积重映射。Karger 等人在 1997 年的论文《Consistent Hashing and Random Trees》中提出了一致性哈希——当节点增减时,只有少部分数据需要重新分配,而不是全部。 一致性哈希解决的只是负载均衡算法要处理的众多问题之一。在这之前,加权随机和最少活跃已经在各自的场景中发挥作用——它们共同构成了负载均衡算法的核心工具箱。 二、前置:负载均衡到底在均衡什么 在一个典型的微服务调用链中: Consumer → [从注册中心拿到 Provider 列表] → 选一个 Provider → 发请求 ↑ 负载均衡算法在这一步起作用 注册中心(比如 Nacos)返回了服务实例的列表——5 个 IP 加端口。Consumer 要从中挑一个发请求。怎么挑——就是负载均衡算法的事。 不同的挑法对应不同的目标: 目标 对应算法 后端实例配置不同(有的机器性能好、有的差) 加权随机 后端实例忙闲不均(有些正在处理慢请求) 最少活跃 需要同一类请求总是打到同一台机器 一致性哈希 三种算法不是"谁更好"的关系——它们是三种不同的策略,各解决各的问题。 ...

一月 23, 2023 · 3 分钟 · 533 字 · yaomingye

事务消息:半消息与回查

事务消息 本文是分布式算法科普系列第五篇。上一篇讲了 2PC 和 TCC——处理"同步调用"场景下的分布式事务。这一篇换一个跑道——当业务逻辑和消息发送需要原子化,但发消息本身是异步的,怎么保证一致性? 一、故事:“先写数据库还是先发消息"的终极难题 在消息队列成为微服务通信标配之后,开发者很快撞上了一个死结。一个极其常见的场景:订单创建成功 → 需要发一条消息通知下游(发优惠券、发短信、记录日志)。代码看起来人畜无害: // 伪代码——演示问题——不要在生产里这么写 BEGIN TRANSACTION INSERT INTO orders (...) COMMIT // ↓ 事务已经提交了 mq.send("order_created", order) // 如果这里执行之前——进程突然挂了? 数据库写入了,消息没发出去——下游永远不知道这笔订单。 那把发消息放进事务里? BEGIN TRANSACTION INSERT INTO orders (...) mq.send("order_created", order) // 消息队列有自己的事务吗? COMMIT 数据库事务和消息队列是两套独立的系统——没有"联合事务"这种东西。数据库的 ROLLBACK 不会撤回已经发到 Broker 的消息。 那反过来——先发消息再写数据库? mq.send("order_created", order) // 消息发出去了 // ↓ 然后写数据库时——数据库挂了 INSERT INTO orders (...) // 失败! 消息发出去了,数据库没写入——下游收到消息后来查订单——发现根本没有这笔订单。 写过的都懂——这个"先有鸡还是先有蛋"的问题在异步场景下几乎无解。早期方案是在数据库里建一张"消息发件箱"表(outbox),把消息和业务数据在同一个事务里写入,再用一个独立的进程轮询这张表来真正发送。但这个方案太重了——需要额外的轮询进程、需要处理重复投递、需要清理已发送的消息。 2016 年前后,RocketMQ 的团队给出了一个更优雅的方案——让 Broker 自己承担"协调者"的角色,引入"半消息"和"回查"两个机制,一举解决了这个难题。这就是事务消息(Transactional Message)。 二、前置:同步事务 vs 异步事务 在深入事务消息之前,先理清它和上一篇讲的 2PC/TCC 之间的分工: 场景 用哪种方案 特点 服务 A 同步调用服务 B——需要 B 的操作和 A 的操作一起成功或回滚 2PC / TCC 同步——A 等 B 的返回结果 服务 A 发消息给服务 B——需要消息的发送和 A 的本地事务原子化 事务消息 异步——A 不关心 B 什么时候消费 2PC/TCC 处理的是"请求-响应"模式下的分布式事务,事务消息处理的是"发布-订阅"模式下的分布式事务。它们解决的是同一个问题(一致性)的两个不同侧面。 ...

一月 22, 2023 · 3 分钟 · 510 字 · yaomingye

分布式事务:两阶段提交与 TCC

分布式事务 本文是分布式算法科普系列第四篇。前三篇讲了服务发现、共识算法、流控——都是"怎么把活分下去"和"怎么保护自己不被冲垮"。这一篇回到一个老问题:一笔业务操作跨越了多个服务,怎么保证数据要么全成功、要么全回滚? 一、故事:数据库拆了,事务怎么办 1970 年代,随着数据库从单机走向网络化,一个此前不存在的问题浮现出来——一笔业务需要同时修改两台机器上的数据,怎么保证原子性? 在单机数据库上,事务是再自然不过的事情——BEGIN → 改 A 表 → 改 B 表 → COMMIT。数据库内部用 undo log 和 redo log 保证崩溃恢复后数据的一致性。但如果 A 表在机器 1 上,B 表在机器 2 上——COMMIT 只对机器 1 生效,机器 2 没收到,或者收到了但执行到一半宕机了——怎么办? Jim Gray 在 1978 年的《Notes on Data Base Operating Systems》中首次系统描述了两阶段提交(2PC,Two-Phase Commit)——用一个"协调者"站在所有参与者中间,分两步确认:第一步问所有人"准备好了没",第二步根据所有人的答复决定"一起提交"还是"一起回滚"。 这个设计的影响延续至今。XA 规范(1991 年由 X/Open 组织发布)将 2PC 标准化为分布式事务处理的工业协议。几乎所有关系型数据库(MySQL、Oracle、PostgreSQL)都支持 XA 事务。 但 2PC 有一个众所周知的痛点——同步阻塞。协调者挂了,参与者只能干等。于是在微服务时代,一种更灵活的方案出现了——TCC(Try-Confirm-Cancel),把二阶段的"锁资源"升级为"预留资源 + 确认或释放"。 二、前置:单机事务不够用了 先从业务场景开始。一个典型的电商下单流程: 下单(Order 服务)→ 扣库存(Inventory 服务)→ 扣余额(Account 服务) 三个操作跨了三个服务、三套数据库。如果在"扣库存"成功后、“扣余额"之前——Account 服务宕机了——库存扣了,但余额没扣,钱没收,货没了。 这就是分布式事务要解决的问题——跨多个服务(多个数据库)的一组操作,要么全部成功,要么全部回滚。 单机事务靠 ACID 保证——原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。分布式事务的目标也是 ACID,但实现手段完全不同——它不靠数据库内部的 undo/redo log,而是靠多个参与者之间的协调协议。 ...

一月 21, 2023 · 4 分钟 · 694 字 · yaomingye

流控算法三件套:滑动窗口、漏桶与令牌桶

流控算法三件套 本文是分布式算法科普系列第三篇。前两篇讲了服务怎么找到彼此(Distro)和怎么对数据达成一致(Raft)。这一篇换一个角度——找到服务了、数据也一致了,但如果请求来得太快太多,怎么保护系统不被冲垮? 一、故事:互联网的拥塞崩溃 1986 年 10 月,互联网历史上发生了一次著名的事故——拥塞崩溃(Congestion Collapse)。劳伦斯伯克利实验室和加州大学伯克利分校之间的网络链路,带宽从通常的 32Kbps 骤降到 40bps——没错,不是 40K,是 40,下降了近三个数量级。 原因并不复杂:发送方在拼命重传丢失的数据包,但这些重传又进一步加剧了网络拥堵,导致更多丢包——恶性循环。链路上跑的全是重传包,几乎没有有效数据到达对端。 这次事件促使 Van Jacobson 在 1988 年发表了《Congestion Avoidance and Control》,提出了 TCP 拥塞控制的几个核心算法——慢启动、拥塞避免、快速重传。而 TCP 里的滑动窗口,正是用来控制"同一时刻最多有多少数据在传输途中"的机制。 同一个问题,换个场景照样发生。微服务架构普及后,服务 A 调用服务 B——如果服务 B 处理能力有限,服务 A 还一个劲地往里灌请求,服务 B 的响应会越来越慢,进而拖慢服务 A 的线程池,再拖慢服务 A 的调用方……一路传导,整个系统雪崩。 这就是流控要解决的核心问题:系统处理能力有限,请求来得太猛太快,必须有一个机制把多余的请求挡在外面——宁可拒绝一部分,也不能让整个系统被冲垮。 二、前置:固定窗口的"边界作弊" 在讲滑动窗口之前,先看一眼最简单的限流方案——固定窗口。理解它的缺陷,才能理解为什么需要滑动窗口。 固定窗口的思路很简单:把时间切成一段一段(比如每秒一段),每段内计数,超过阈值就拒绝。 窗口: [0秒 ~ 1秒) → 计数器 = 0 → 请求来了 → 计数器+1 → 计数器≤阈值 → 放行 窗口: [1秒 ~ 2秒) → 计数器归零 → 重新计数 问题出在窗口边界。假设阈值是每秒 100 个请求。有人在 0.95 秒到 1.05 秒之间发了 150 个请求——0.95 到 1 秒 80 个,1 到 1.05 秒 70 个。两个窗口各自的计数器都没超阈值(80 < 100,70 < 100),但实际上在 0.95 ~ 1.05 这 0.1 秒内系统实际承受了 150 个请求。 ...

一月 20, 2023 · 3 分钟 · 529 字 · yaomingye

Raft 协议:选举、日志复制与强一致

Raft 协议 本文是分布式算法科普系列第二篇。上一篇讲了 Distro 协议如何用"去中心化 + 异步同步"实现 AP 模型——写完立刻返回、事后慢慢对齐。这一篇讲它的反面:Raft 如何用"选出一个老板 + 事事多数同意"实现 CP 模型——宁可暂时不可用、绝不返回错误数据。 一、故事:Paxos 太难了,于是有了 Raft 在 Raft 出现之前,分布式共识领域有一个"上古神器"——Paxos。Paxos 由 Leslie Lamport(就是写 LaTeX 的那位)在 1989 年提出,理论正确性无可挑剔,但有一个致命的工程问题:几乎没有人能真正看懂它。 Lamport 在 1998 年发表了一篇补充论文《Paxos Made Simple》,摘要第一句话就是——“The Paxos algorithm, when presented in plain English, is very simple."(用大白话讲,Paxos 其实很简单。)但工程界的反馈很统一:不,它一点也不 simple。 这不是段子,是真实历史。Google 的 Chubby 分布式锁系统在实现 Paxos 的过程中遇到了大量问题,Chubby 的作者 Mike Burrows 有一句著名的吐槽:“世界上只有两种共识算法——Paxos 和那些没人能证明正确的算法。” 2013 年,斯坦福大学的博士生 Diego Ongaro 和导师 John Ousterhout 决定正面解决这个问题。他们的出发点和前面所有人都不一样——把"可理解性"作为算法的首要设计目标,而不是附带的副产品。 Ongaro 从头设计了一个全新的共识算法,刻意把整个协议拆成三个相对独立的模块——领导者选举、日志复制、安全保证——每个模块都可以单独理解。2014 年,他们发表了论文《In Search of an Understandable Consensus Algorithm》(寻找一个可理解的共识算法),Raft 正式诞生。 ...

一月 19, 2023 · 4 分钟 · 803 字 · yaomingye

Distro 协议:去中心化与最终一致

Distro 协议 本文是分布式算法科普系列第一篇。系列面向完全没接触过分布式的业务开发者,用历史故事开场、比喻辅助理解、不涉及数学证明和代码实现。 一、故事:微服务来了,电话号码本怎么办 早年的单体应用,一个进程内部互相调用,不需要"发现"对方——函数调用就行。微服务架构来了,服务实例的数量和位置开始动态变化:扩容加几台、某台机器宕机撤掉、滚动发布换一批新实例。服务 A 要调用服务 B,必须知道此时此刻服务 B 在哪些 IP 和端口上。 最朴素的想法是——搞一个电话号码本。所有服务启动后把自己的地址登记上去,调用方去电话本里查。这个"电话本"就是注册中心。 但问题来了:电话本自己怎么保证不挂?如果只有一个电话本,挂了所有服务都变瞎子。那就多搞几个电话本,每个存一份完整的地址副本。新问题又来了——服务 A 的地址变了,怎么保证所有电话本上写的都一样? 用个比方来理解这个场景: 一个小区有三家传达室,每家都有一本住户登记簿。住户搬家了会通知最近的那家传达室更新记录。有人来访时,随便问哪家传达室都能查到住户的门牌号——哪怕其中一家的登记簿还没来得及更新。 这就是 Distro 协议要解决的问题:在一个多节点集群中,如何让写入请求快速得到响应(高可用),同时保证各节点上的数据最终会变得一致(最终一致)。 二、前置:为什么不能又一致又可用 在深入 Distro 之前,需要先理解一个约束——CAP 定理。 📌 前置知识:CAP 定理说的是,在一个分布式系统中,当网络发生分区(Partition,即节点之间网络不通)时,你只能在一致性(Consistency)和可用性(Availability)之间二选一。网络没出问题时,一致性和可用性可以同时满足。 用电话本的比方说:一号传达室和二号传达室之间电话线断了(网络分区)。此时有人去一号传达室改了一个住户的门牌号(写操作)。一号传达室有两个选择: 选一致性(C):拒绝这个修改请求,因为无法同步给二号传达室。结果:修改失败,但所有传达室的数据保持一致。 选可用性(A):先接受修改,等电话线恢复后再同步给二号传达室。结果:修改成功,但二号传达室暂时还是旧数据。 注册中心这个场景天然更适合选 AP(可用 + 分区容忍)。原因很现实:返回一个略微过期的实例地址(可能已经下线了),调用方最多重试一次换另一个实例;但注册中心如果拒绝查询,整个调用链直接断了。两害相权取其轻。 Raft 协议选了 CP(后面一篇会讲),Distro 协议选了 AP。这就是它们在同一套 Nacos 系统里分工的原因——服务发现走 Distro(AP),配置中心走 Raft(CP)。 三、Distro 的核心设计 3.1 没有主节点 这是 Distro 和 Raft 最根本的区别。Raft 通过选举产生一个主节点(Leader),所有写操作必须经过主节点——主节点把日志复制给从节点,多数确认后提交。如果主节点挂了,必须重新选举,选举期间集群无法写入。 Distro 没有主节点。集群里每个节点都是平等的。写请求可以打到任意一个节点,该节点立刻返回成功,然后异步把变更同步给其他节点。 用一个比方来理解这个差异: Raft 像公司报销流程——所有报销单必须部门经理(Leader)签字才能入账。经理出差了?等着,等他回来或者换新经理。 Distro 像小组共享文档——任何人改了一段,改了就先保存,其他同事打开文档时看到最新版就行。就算有人离线没同步到,等他上线后会自动补上。 去中心化带来的直接好处:没有主节点,就不存在主节点宕机后的"选举窗口"。任何时候任何节点都能处理读写。 3.2 数据分片与一致性哈希 Distro 虽然每个节点都能独立处理写请求,但为了减少冲突和降低同步开销,它对数据做了分片——每个服务实例的注册信息只由一个"负责节点"来权威维护,其他节点虽然也存了这份数据,但只是副本。 分片机制用了一致性哈希。一致性哈希把存储空间组织成一个首尾相连的环(0 ~ 2^32-1)。每个节点在环上占据一个位置,每条数据根据 Key 的哈希值落在环上的某个点,顺时针方向遇到的第一个节点就是这条数据的负责节点。 ...

一月 18, 2023 · 2 分钟 · 376 字 · yaomingye

流控三板斧——Sentinel滑动窗口、令牌桶与Dubbo负载均衡

流控三板斧 前三篇讲了一个核心矛盾:分布式系统里——网络和时钟不可靠——所以你必须在一致性和可用性之间做取舍——Raft 用 majority 保证 CP——Nacos Distro 用最终一致性取 AP。 但取舍不只发生在数据一致性层面——流量控制层面同样存在。每个服务有自己的承载上限——超过上限就必须拒绝一部分请求——这就是限流。拒绝哪些请求?以什么粒度计数?桶还是窗口? 📌 前置知识:需要有 Sentinel 基本概念(知道它是限流熔断组件)和 Dubbo 基本用法(知道 @DubboReference 怎么调用远程服务)。如果还没用过 Sentinel 的 Dashboard——建议先对着官方文档跑一遍 Quick Start——不需要深入——但得知道控制台里"流控规则"长什么样。 一、为什么"每秒 100 个请求"这种限流方式有 Bug——固定窗口的边界突刺 对限流最直观的理解:系统处理能力是每秒 100 个——超过就拒绝。实现这个最简单的办法——搞一个计数器——每秒归零。 // 固定窗口计数器——最朴素的想法 class FixedWindowRateLimiter { private long windowStart = System.currentTimeMillis(); private int counter = 0; private final int limit = 100; public synchronized boolean tryAcquire() { long now = System.currentTimeMillis(); if (now - windowStart > 1000) { windowStart = now; // 新窗口——计数器归零 counter = 0; } if (counter < limit) { counter++; return true; // 放行 } return false; // 限流 } } 看起来没毛病——每秒最多通过 100 个——超过就拒绝。问题出在窗口边界: ...

一月 4, 2023 · 4 分钟 · 701 字 · yaomingye

谁说了算——Raft选举、心跳与故障检测在Nacos/Dubbo中的应用

谁说了算 前两篇讲了一个道理:网络和时钟不可靠 → 必须做取舍 → CAP 把取舍定了性。那具体怎么做取舍呢? 如果集群里只有一台机器——不存在一致性问题——所有写操作都在同一块硬盘上——谁先谁后清清楚楚。但只有一台机器的代价是——这台机器宕机——系统全挂。所以需要多台机器——而多台机器就需要一个机制来决定“谁的版本算数”。 这个机制在分布式系统里有一个正式的名字——共识算法(Consensus Algorithm)。Raft 是目前工程界最广泛使用的共识算法——不是因为它理论上最完美——而是因为它可以让人看得懂。 📌 前置知识:建议先读上篇 CAP 定理——理解 CP vs AP 的区别。Raft 是典型的 CP 实现——本文的 Raft 部分主要解释它如何实现 C(一致性)。 一、为什么要有人"说了算"——分布式写操作的困境 先看一个最简单的集群:三台机器——每台都存一份数据——都可以接受写请求。 客户端写入 x=1 → 节点 A 收到——更新本地 x=1 客户端写入 x=2 → 节点 B 收到——更新本地 x=2 (几乎同时——两个客户端连到了两个不同的节点) A 认为 x=1——B 认为 x=2——到底 x 是多少? 两者各自都认为自己的数据正确——没有人有权限说"听我的"——这就是分布式系统里最核心的问题——没有单点权威——写操作需要协调。 flowchart TD start["两个客户端——两个写请求——\n到达两个不同节点"]:::startEnd start --> c1["客户端 1 → 节点 A\nSET x=1"]:::data start --> c2["客户端 2 → 节点 B\nSET x=2"]:::data c1 --> conflict["节点 A:x=1\n节点 B:x=2\n⚡ 冲突——x 到底等于几?"]:::highlight c2 --> conflict conflict --> naive["最简单的方案:\n规定只有一台机器能接受写——\n这台机器叫 Leader"]:::data naive --> next_q["新问题:Leader 宕机了呢?\n谁当新 Leader?\n怎么告诉大家?"]:::condition classDef startEnd fill:#701a4c,stroke:#e11d48,stroke-width:2px,color:#fce7f3,font-weight:bold; classDef data fill:#052e16,stroke:#16a34a,stroke-width:1.5px,color:#bbf7d0,font-weight:bold; classDef highlight fill:#450a0a,stroke:#dc2626,stroke-width:1.5px,color:#fecaca,font-weight:bold; classDef condition fill:#2a1147,stroke:#a855f7,stroke-width:1.5px,color:#ede9fe,font-weight:bold; Raft 要解决的就是这两个问题合在一起:(1) 选出一个大家都认可的 Leader——(2) Leader 挂了以后——自动选出新 Leader。 ...

一月 3, 2023 · 4 分钟 · 844 字 · yaomingye

CAP定理与一致性模型——从Nacos AP/CP双模理解取舍

CAP 定理与一致性模型 上篇讲了两件事:网络不可靠、时钟不可信。结尾留了一句话——这两个不确定性叠加——迫使你在"等精确答案"和"快速给大致答案"之间选边站。 这句话有一个更正式的名字:CAP 定理。 但 CAP 被误解的程度——大概仅次于"TCP 三次握手"——绝大多数文章都把它简化成"一致性、可用性、分区容错性三者选其二"——就像点菜时三选二。 真正的 CAP 远比这复杂——而且它不是一个开关——而是一条光谱。 📌 前置知识:建议先读上篇——理解网络分区和时钟漂移的成因。另外需要有 Nacos 的基本使用经验(知道它可以做注册中心和配置中心即可)。 一、CAP 的经典定义——先搞清楚每个字母到底在说什么 CAP 是 Eric Brewer 在 2000 年提出的——后来由 Gilbert 和 Lynch 在 2002 年给出了形式化证明。注意——CAP 里的"证明"不是实验验证——是数学上严格证明了这三个性质不可能同时满足。 先搞清楚每个字母的精确含义: 字母 全称 经典定义 一句话翻译 C Consistency 每次读操作——都能读到最近一次写操作的结果——所有节点在同一时刻看到的数据完全一致 “你刚写的——马上就能读到” A Availability 每个发给非故障节点的请求——都能在有限时间内得到一个非错误的响应 “请求一定有人接——不会晾着你” P Partition Tolerance 系统在部分节点之间的网络被切断后——仍然能继续对外提供服务 “网线拔了——系统还能撑——不至于完全挂掉” ⚠️ 新手提示:CAP 里的 P(分区容错)不是"系统可以容忍多少台机器宕机"——那叫容错。P 的精确含义是——任意数量的消息丢失或延迟——系统不能进入不可恢复的状态。换句话说——P 不是在问"系统会不会出分区"——分区是客观物理现象——P 是在问"分区发生时——系统还能不能运转"。 现在用一张图看清楚:没有分区时的理想状态 vs 分区发生时的两难。 flowchart TD subgraph nopartition["无网络分区——理想状态"] direction TB c1["客户端写 x=1"]:::startEnd --> n1["节点 A\nx=1"]:::data c1 -.-> n2["节点 B\nx=1\n从 A 同步"]:::data r1["客户端读 x"]:::startEnd --> n2 n2 --> res1["返回 x=1 ✅\nC 和 A 都满足"]:::data end subgraph partition["网络分区发生——A 和 B 互相不可达"] direction TB c2["客户端写 x=2"]:::startEnd --> p1["节点 A\nx=2"]:::data p1 -.->|"❌ 分区——无法同步"| p2["节点 B\nx=1(旧值)"]:::data r2["客户端读 x"]:::startEnd --> p2 p2 --> choice{"节点 B 怎么回复?"}:::condition choice -->|"返回 x=1\n(旧值——保留可用性)"| ap["选了 A——牺牲 C\n❌ 一致性被破坏"]:::reject choice -->|"拒绝响应——\n等网络恢复"| cp["选了 C——牺牲 A\n❌ 可用性被破坏"]:::reject end classDef startEnd fill:#701a4c,stroke:#e11d48,stroke-width:2px,color:#fce7f3,font-weight:bold; classDef data fill:#052e16,stroke:#16a34a,stroke-width:1.5px,color:#bbf7d0,font-weight:bold; classDef condition fill:#2a1147,stroke:#a855f7,stroke-width:1.5px,color:#ede9fe,font-weight:bold; classDef reject fill:#450a0a,stroke:#dc2626,stroke-width:1.5px,color:#fecaca,font-weight:bold; 分区发生时——你只能在"接受不一致"和"拒绝服务"之间二选一。 ...

一月 2, 2023 · 4 分钟 · 759 字 · yaomingye
Cat Radio