19 状态管理:无状态化、状态机、事件溯源
开篇提问
先给你一个场景,你来判断哪种做法更好:
你要部署一个服务,需要跑 10 个实例来做负载均衡。现在有两种设计:
- 方案 A(有状态):每个实例在内存里保存用户的会话信息。用户登录后,后续请求必须路由到同一个实例(否则要重新登录)。
- 方案 B(无状态):实例不保存任何会话数据,会话存在外部的 Redis 里。任何实例都能处理任何请求。
直觉上方案 A 更快(内存访问 vs 网络访问 Redis)。但几乎所有现代架构都会选 B。
请想清楚为什么:方案 A 到底”坏”在哪?它带来的麻烦,是不是比”多一次 Redis 访问”的代价更大?
先别急着看答案。我给你三个追问,你顺着想下去:
- 方案 A 里,如果你要加第 11 台实例,会发生什么?原来那 10 台里的会话,要不要搬到新机器上?
- 方案 A 里,如果第 7 台实例突然宕机,那台机器上的用户会话去哪了?他们是不是全都要重新登录?
- 方案 A 里,如果你想做滚动发布(先更新一半实例,验证没问题再更新另一半),会话会不会断?
你会发现,这三个问题的答案指向同一个词:状态把你拴住了。方案 A 省下的是”一次内存访问”,换来的却是”扩容、故障、部署这三个动作全部变得复杂”。而这三个动作,恰恰是分布式系统日常运维里最高频、最危险的事。
这个问题背后,是整个分布式架构最重要的一条设计原则:状态是所有麻烦的根源。 理解了这一点,你就理解了为什么要”无状态化”,以及——什么时候无状态化是错误的。
这后半句,才是这一章真正想让你带走的。绝大多数人记住了”要无状态”,却说不清”什么时候不能无状态”,于是把原则用成了教条。
子主题一:状态为什么是”万恶之源”
先定义:状态(State)就是”系统在两次请求之间需要记住的东西”。
别小看这个定义。它精确地圈定了问题:只要你的系统需要”记住上一次发生了什么”,它就有了状态。而你一旦有了状态,四个连锁麻烦就会像骨牌一样倒下来。
麻烦一:状态绑定了”身份”
一旦某个实例持有状态,后续相关请求就必须回到这个实例。这就是会话粘滞(Sticky Session)。
你可能会想:加个粘滞不就完了?负载均衡器记住”用户 A 的请求永远发给实例 3”,不就行了?
问题是:粘滞本身就是一个”状态”。负载均衡器得维护一张”用户 → 实例”的映射表,这张表自己又会遇到前面说的所有麻烦(表存哪?挂了怎么办?用户多了怎么办?)。你用一个状态去解决另一个状态,麻烦只是被转移了,没有消失。
更深一层:一旦请求和实例绑定了身份,你就失去了”随便挑一个实例处理”的能力。而”随便挑一个”,恰恰是水平扩展(Scale Out)的灵魂——扩展开来能”随便挑”,你才能靠加机器来扛流量。粘滞把这扇门焊死了。
麻烦二:状态让扩容变难
对比一下两种服务的扩容:
- 无状态服务扩容:加机器 → 挂到负载均衡后面 → 立刻能接流量。前后可能只要几分钟,甚至几秒钟(配合容器编排,就是”声明一下副本数”的事)。
- 有状态服务扩容:加机器 → 还要迁移状态。数据要重新分片、要同步、要保证迁移期间不丢请求。慢,而且危险——历史上大量线上事故,都发生在”有状态服务扩容时数据没迁对”。
这里有个你 5 年 Go 后端一定感受过的点:无状态服务的扩容是”算力”的复制,有状态服务的扩容是”数据”的搬迁。复制是廉价的(多起几个进程),搬迁是昂贵的(涉及一致性、停机窗口、回滚预案)。
麻烦三:状态让故障恢复变难
- 无状态实例挂了:负载均衡器把它摘掉,换一个新实例顶上,用户无感。
- 有状态实例挂了:它持有的状态丢了。除非有持久化和副本,否则用户数据丢失或需要重建。
你想想线上的实际情况:无状态服务挂了,你甚至可以不报警——反正下一台自动顶上。但有状态服务挂了,就是 P0:因为”丢了什么”这个问题,往往要等用户来告诉你。
麻烦四:状态让部署变复杂
- 无状态服务可以滚动更新、蓝绿部署、随时回滚。
- 有状态服务的更新要考虑:状态格式兼容吗?数据要迁移吗?回滚时状态怎么处理?
举一个真实的例子:你给订单表加了个字段,旧版本的服务读不懂新字段怎么办?你得做向后兼容。而如果你敢在无状态服务上加个字段,压根没这个问题——因为无状态服务不持久化任何东西,升级就是换个进程。
真正的价值:分离”计算”和”数据”
把这四个麻烦合起来看,你会得出一个更深的结论:
“无状态化”的真正价值,不是”省内存”,而是把”计算”和”数据”分离,让计算可以随意伸缩、随意故障、随意替换。
这才是云计算时代最重要的架构思想之一。它的完整表述是:
让计算层无状态,把状态下沉到专门的状态层(数据库、缓存、对象存储)。
- 计算层(你的 Go 服务、Serverless 函数):无状态,可弹性伸缩,挂了就重启。
- 状态层(MySQL、Redis、S3、Kafka):有状态,但由专门的机制保证可靠性(主从复制、持久化、快照)。
你发现没有,这其实是又一次”把问题转移到更合适的地方”(第 1 章的思想)。状态这个麻烦没有被消灭——它只是从”你管不好的地方”(易失的计算进程内存)挪到了”专门管这个的地方”(数据库、缓存)。
这就是为什么说”状态管理”的第一课,是”管理状态的归属”。 先想清楚”这个状态该住在谁家”,再想”怎么保证它不出错”。
子主题二:无状态化的三种做法
既然要”把状态赶出计算层”,那具体有哪几条路?工业界总结下来是三条,各有各的适用场景和代价。
做法一:把状态外置(最主流)
- 会话存 Redis、数据存数据库、文件存对象存储
- 服务实例完全无状态
- 代价:每次访问状态要网络往返(可以用本地缓存来缓解,但本地缓存又引入了新的不一致问题——见第 8 章)
这是最”正”的一条路。它的思想内核是:既然计算层管不好状态,那就造一个专门管状态的层,让专业的人(专用的存储系统)干专业的事。
你注意一个细节:外置的存储,本身又是有状态的。Redis、MySQL 它们自己也有副本、有持久化、有故障转移。但区别在于——这些”管状态”的复杂度,被集中在了一个地方,由专门的、经过千锤百炼的系统来扛,而不是散落在你每一行业务代码里。
做法二:把状态推给客户端
典型代表:JWT(JSON Web Token)——把用户信息签名后放在 token 里,客户端每次请求带上,服务端不存会话,只需验签。
这条路很巧妙:服务端彻底不用记任何东西,状态被打包进了请求本身。
优点:
- 彻底无状态,扩展性极好——任何实例都能验签,不存在粘滞问题。
- 省掉了一次查 Redis 的往返。
缺点(这是关键,很多人用 JWT 只记得优点):
- 无法主动失效。token 发出去后,在过期前一直有效,服务端无法”踢人”。用户改密码了、被封号了,旧的 token 依然能用到过期。
- token 体积较大——如果往里面塞了太多 claim,每次请求的 header 都会变大。
- 敏感信息不能放里面——token 是可解码的(base64),只是防篡改(有签名)。别把密码、身份证号塞进去。
JWT 的这个”无法主动失效”的缺陷,是一个绝佳的思考题:它说明”把状态推给客户端”这条路,本质是用”无法撤回”换”无需存储”。你要不要做这个交换,取决于你的业务:如果你的业务对”立刻踢人”有强需求(比如支付、安全敏感),JWT 的缺陷就会咬你;如果只是普通登录态,那这个缺陷可以忍受。
做法三:把状态编码在请求里(REST 的无状态约束)
REST 架构风格明确要求:“请求必须包含服务端处理它所需的全部信息”。每个请求自带上下文,服务端不保存”上一次请求”的痕迹。
这就是 REST 里那个经常被误解的 “stateless” 约束。注意,REST 说的”无状态”不是”你的系统没有数据”——数据当然在数据库里。它说的是**“服务端不保存客户端在两次请求之间的会话上下文”**:每一次请求都是独立的、自包含的,服务端不需要”回忆”上一个请求是什么。
这三种做法的共同点,值得你停下来想一下:
它们都在做同一件事——把状态”挪”出易失的地方。 做法一是挪到专门的状态层,做法二是挪到客户端,做法三是干脆要求”每次请求都带上全部状态”。
所以你回头看那个重要的提醒:无状态化不是”消灭状态”,而是”把状态挪到专门的地方”。状态总得存在某个地方——你只是把它从”易失的计算层”挪到了”可靠的状态层”。这是又一次”把问题转移到更合适的地方”(第 1 章的思想)。
什么时候无状态化是错的? 这是前面埋下的问题,现在回答:
当你为了”无状态”而付出的代价,大于”有状态”带来的麻烦时。比如:
- 状态本身极小、极局部,把它外置反而是杀鸡用牛刀(比如一个单机脚本里的计数器)。
- 对延迟极度敏感、又不允许任何网络往返的场景(这时”内存里的状态”反而是正确选择,代价就是放弃弹性伸缩)。
原则永远是看约束,而不是背教条。无状态化是手段,不是目的。
子主题三:状态机——让状态变化变得可控
既然状态无法完全消灭(业务本质上就是有状态的:订单有状态、账户有余额),那怎么管理它?
答案是状态机(State Machine)——这是管理状态最强大、最经典的工具。
状态机的核心思想
把”状态”和”状态之间的转换规则”显式化。系统只能在一组预定义的状态之间,按照预定义的规则迁移。
一个订单的状态机:
待支付 --支付--> 已支付 --发货--> 已发货 --确认收货--> 已完成
| |
--取消--> 已取消 --退款--> 已退款
别小看这张图。它把一件”散落在代码里、谁也说不清全貌”的东西,变成了一张一眼能看懂的图。
价值一:让非法状态无法出现
没有状态机时,你的代码里到处都是 if status == X 的散落判断,很容易出现”已发货的订单被取消了”这种荒谬状态。
为什么荒谬状态会出现?因为它不是一个 bug,而是结构缺陷:你在 A 处判断”已发货不能取消”,但在 B 处忘了判断。状态规则散落各处,彼此之间没有约束,于是”漏一处,就崩一处”。
有了状态机,“已发货 → 已取消”这条转换根本不存在——从结构上杜绝了。你不需要”记得”在每一处加判断,因为状态机这个结构本身就”不允许”那条转换发生。
这里有个深刻的洞见,值得你反复咀嚼:
最好的约束,不是”让人记得遵守”,而是”让人无法违反”。
这和第 20 章讲的并发安全(Rust 靠类型系统让”写出数据竞争”变得不可能)是同一个思想的不同侧面——把安全从”纪律”变成”结构”。
价值二:让状态变化可追溯
每个转换都是一个事件,可以记录、可以追踪、可以回放。订单从”待支付”到”已完成”的每一步,都有据可查。
价值三:天然支持幂等
“支付”操作只在”待支付”状态有效。重复调用时状态已经是”已支付”,直接返回成功——这就是幂等(第 11 章讲过)。
你注意这里的神来之笔:幂等不是靠”判断这次调用是不是重复的”来实现的,而是靠”状态机天然让重复调用变成无害”来实现的。第一次调用把状态从”待支付”推到”已支付”,第二次调用发现已经是”已支付”,直接返回成功。你甚至不需要知道”这次是不是重复的”——状态机帮你把这个判断消解掉了。
价值四:让并发安全
状态转换可以用乐观锁(版本号 / CAS)实现:
UPDATE order SET status='paid' WHERE id=? AND status='unpaid'只有一个事务能成功(因为只有一个事务能匹配到 status='unpaid'),天然防并发。这又是一个”结构保证安全”的例子——见第 20 章。
状态机是”把隐式的业务规则显式化”的典范。 很多系统的 bug,本质都是”状态规则散落在代码各处、相互矛盾”——状态机把这些规则集中到一个地方,让它们可以被审查、被测试。
状态机在计算机史上的位置
状态机不是一个”业务开发技巧”,它是计算机科学的地基之一:
- 编译器的词法分析:用有限状态机(Finite State Machine, FSM)识别 token。
- 网络协议:TCP 的 11 个状态(LISTEN、SYN-SENT、ESTABLISHED、FIN-WAIT 等)就是一个状态机;HTTP 也是有状态机的。
- 正则表达式:本质就是状态机(能被编译成 DFA/NFA)。
- 图灵机:就是一台带无限纸带的、更强大的状态机——整个”可计算性”理论,都建立在状态机之上。
所以当你用状态机管理订单状态时,你不是在用一个”新潮框架”,你是在用计算机科学最古老、最可靠的工具之一。这也解释了为什么状态机如此好用:它经过了从图灵、冯·诺依曼到现代编译器几十年的验证。
子主题四:事件溯源——把”变化”而不是”状态”存下来
事件溯源(Event Sourcing)是一个反直觉但极其强大的思想。先看对比:
传统做法:存”当前状态”。
账户表:{ id: 1, balance: 500 }
你只知道”现在余额是 500”,不知道它是怎么变成 500 的。
事件溯源:存”所有发生过的事件”。
事件流:
1. 开户,余额 0
2. 存入 1000,余额 1000
3. 取出 300,余额 700
4. 取出 200,余额 500
当前状态 = 把所有事件**重放(replay)**一遍的结果。
这个思路的革命性在哪?
先别急着列优点。你先感受一下这个思路的**“违和感”在哪里**:
传统数据库几十年来的默认,都是”存当前值”——update 一条记录,旧值就被覆盖了。事件溯源反着来:它拒绝覆盖,只追加。旧值永远留在那里,成为历史的一部分。
这个”拒绝覆盖、只追加”的选择,带来了一系列连锁的好处:
价值一:完整的历史,可审计。 你能回答”这个余额是怎么变成 500 的”、“上周三下午 3 点余额是多少”。在金融、医疗等需要审计的领域,这是刚需——不是”nice to have”,是合规要求。
价值二:可以”时间旅行”。 重放到任意一个时间点,就能重建当时的状态。这对调试、对账、合规无比有用。你甚至能问:“如果上个月那笔 300 元的退款没发生,现在余额是多少?“——这在传统”只存当前值”的模型下,是不可回答的。
价值三:事件是”事实”,可以衍生出多种视图。 同一份事件流,可以投影出”当前余额”、“月度流水”、“用户行为分析”等不同的读模型。这就是 CQRS(命令查询职责分离):写侧存事件,读侧按需投影出不同的视图。
价值四:天然支持”撤销”(用补偿事件)。 不能删除事件(事件是已发生的事实),但可以加一个”撤销”事件。这也符合会计里的”红字冲销”——错误不是抹掉,而是记一笔反向的。
代价(很重要,别只看优点)
- 查询变难:要查当前状态,得重放事件。所以需要**快照(snapshot)**机制——定期保存当前状态,查询时从最近的快照开始重放,而不是从头。
- 事件结构演进困难:历史事件的结构不能随便改(旧事件还在那),需要版本化和兼容处理。
- 存储增长:事件只增不改,数据量持续增长。
- 心智负担:和传统的 CRUD 思维完全不同,团队需要适应。
什么时候用事件溯源?
- 需要完整审计追踪(金融、医疗、法务)
- 需要”时间旅行”或回放能力
- 业务逻辑本身就是”一系列事件”(比如交易系统、工作流)
- 不适合简单的 CRUD 应用——用它就是过度设计
这里有一条普适的判断标准,比”事件溯源”本身更值钱:
你的业务,究竟是”关心最终状态”还是”关心变化过程”?
- 如果一个购物网站的”购物车”丢了历史,没人关心——它只需要”当前车里有什么”,那就用普通状态存储。
- 如果一个银行账户的”每一笔流水”都是审计对象——它关心的是”钱是怎么进出的全貌”,那事件溯源的价值就出来了。
工具的选择,永远由”你真正关心什么”决定。 事件溯源不是”更高级”,它只是更匹配”关心变化过程”的那类业务。
子主题五:状态的一致性——最难的部分
讲状态管理不能绕开最难的问题:当状态被复制到多个地方(缓存、从库、多个服务),怎么保持一致?
这就是第 2 章”一致性与性能的权衡”在状态管理上的具体体现。几个实用模式:
模式一:单一数据源(Single Source of Truth)
- 每份数据只有一个”权威”存储,其他地方都是副本(缓存、从库)。
- 副本通过同步机制追赶。
- 这是最重要的原则:先确定”谁说了算”,再讨论同步。
这条原则听起来像废话,但它是无数线上事故的根因。你想想:缓存里一份、数据库里一份、ES 里一份,如果三处不一致,你到底信谁?“单一数据源”回答的就是这个问题:先指定一个”权威”,其余都是”可以被丢弃重建的缓存”。
模式二:写穿 / 写回 / 旁路缓存
第 8 章讲过——缓存和源的一致性策略。核心是决定”写的时候,先写源还是先写缓存,怎么处理缓存和源之间的时间差”。
模式三:事件驱动的状态同步
- 数据变更时发事件,订阅方更新自己的副本。
- 典型:订阅 MySQL binlog(Canal、Debezium)来同步缓存/搜索引擎。
- 优点:解耦、实时性较好。
- 缺点:最终一致,且有延迟。
模式四:对账与补偿
- 承认不一致会发生,通过定期对账发现差异并修复。
- 这是金融系统的标配——不追求”永远一致”,追求”能发现不一致并修正”。
这第四个模式,藏着这一章最重要的一个心态转变:
与其追求”永不不一致”(几乎做不到),不如追求”不一致了能发现、能修正”。
这和第 11 章(失败检测与容错)、第 13 章(隔离与降级)一脉相承:真实世界没有完美,成熟的系统不是”不让问题发生”,而是”假设问题会发生,并准备好应对”。 你从”我要做到 100% 一致”转向”我要做到’99.99% 一致 + 0.01% 能被对账抓出来修正’“,工程难度立刻从”不可能”降到”可行”。
演进脉络
这一节,我们把状态管理的演进,从头到尾串一遍。你会看到一条清晰的主线,以及每个”里程碑”背后那个”当年的人是怎么想通”的时刻。
第一阶段:单体时代,状态在进程里(最早的做法)
最早的程序,状态就是进程内存里的全局变量。简单、直接、快。用户会话、计数器、配置,全都堆在内存里。
这时的状态管理没有”问题”,因为还没有”分布式”。一台机器、一个进程,状态和计算天然在一起,谈不上”分离”。
第二阶段:水平扩展逼出”无状态化”(1990s-2000s)
问题出在要扛更多流量。一台机器扛不住了,你想加机器做负载均衡。然后你就撞上了开篇那个场景:状态把请求拴在了某一台机器上,加机器没用,因为新机器没有会话。
于是”无状态化”的思想兴起:把会话从进程内存里赶出去,外置到数据库、缓存。这样任何一台机器都能处理任何请求,加机器才真正有用。
同一时期,Roy Fielding 在 2000 年的博士论文里提出 REST,把”无状态”作为架构约束明确写下来。注意,REST 的”无状态”是一个有意的设计选择,不是为了省内存,而是为了”可伸缩性”——只有无状态,才能水平扩展。
第三阶段:状态机从理论走向工程(2000s)
状态机的理论早就成熟(图灵机、自动机理论),但把它系统地用到业务系统,是 2000 年代的事。工作流引擎(BPM)把状态机产品化,订单、审批、物流这些”有明显状态流转”的业务,开始用显式状态机来建模。
这一阶段的突破在于”显式化”:过去状态规则散落在 if/else 里,现在集中到一张状态图上。这是”从隐式到显式”的一次认知升级。
第四阶段:事件溯源 + CQRS(2010s)
事件溯源的思想并不新(会计的”流水账”、银行的”交易日志”古已有之),但它作为软件架构模式被系统化,是 2010 年代 DDD(领域驱动设计)社区推动的。Greg Young、Martin Fowler 等人的文章让”存事件而非存状态”这个思路流行起来。
它流行有一个技术前提:Kafka 等消息系统让”事件流”的存储和消费变得可行。过去你想存事件流、重放事件,缺乏趁手的基础设施;有了 Kafka,事件成了”一等公民”。
同一时期,微服务架构把”无状态计算 + 状态外置”变成默认范式——每个服务尽量无状态,状态统一交给数据库、缓存、消息队列。
第五阶段:Serverless 把无状态推到极致(现代)
Serverless(函数计算)把无状态推到了极致:函数完全无状态,连”进程”这个载体都消失了,状态全靠外部服务。函数每次调用都可能是全新的容器,你甚至不能假设”这个函数还记得上一次调用”。
同时,事件溯源在新兴的”事件驱动架构”中重新受到重视——因为事件驱动架构天然需要”事件”作为一等公民,而事件溯源正好提供了”事件即事实”的语义。
主线总结
状态管理的演进,是”不断把状态从计算中剥离、并让状态的变化变得显式可控”的过程。
- 从内存状态 → 外置存储(无状态化):把状态从”易失的计算层”剥离。
- 从散落的 if 判断 → 显式状态机:让状态的变化规则显式可控。
- 从只存当前值 → 存完整事件流:让”系统怎么变成现在这样”这件事可理解、可追溯。
每一步,都在让”系统怎么变成现在这样”这件事变得更可理解、更可追溯。 而这条主线的驱动力,始终是同一个矛盾:系统越来越分布式,而分布式让”隐式的、易失的、散落的状态”变得越来越危险。 状态管理的所有演进,都是对这个危险的回应。
本章思考题
【思考题】
-
状态为什么是”万恶之源”?无状态化的真正价值是什么?“无状态化”在什么情况下是错误的选择?
-
无状态化有哪三种做法?JWT 这种”状态推给客户端”的做法有什么缺点?它的缺点暴露了什么本质?
-
状态机为什么能天然支持幂等?“最好的约束是让人无法违反,而不是让人记得遵守”这句话,和状态机有什么关系?
-
事件溯源和传统”存当前状态”的区别是什么?“你的业务是关心最终状态,还是关心变化过程”这个判断标准,怎么帮你决定要不要用事件溯源?
-
状态一致性里,为什么说”追求’不一致了能发现、能修正’比追求’永不不一致’更现实”?这和全书的哪个主题一脉相承?
【参考答案】
-
状态带来四个连锁问题:绑定身份(后续请求必须路由到同一实例,破坏负载均衡自由,而”粘滞”本身又是一种状态,用一个状态解决另一个状态,麻烦只是转移)、扩容变难(加机器还要迁移状态,复制算力廉价、搬迁数据昂贵)、故障恢复变难(实例挂了状态就丢)、部署变复杂(要考虑状态格式兼容和迁移)。无状态化的真正价值不是省内存,而是把”计算”和”数据”分离——让计算层可以随意伸缩、故障、替换,把状态下沉到专门保证可靠性的状态层。但无状态化是手段不是目的,当”为无状态付出的代价”大于”有状态的麻烦”时(状态极小极局部、或对延迟极度敏感不允许网络往返),它就是错的——原则是看约束,不是背教条。
-
三种做法:状态外置(会话存 Redis、数据存数据库、文件存对象存储,最主流,代价是网络往返)、状态推给客户端(JWT,服务端只验签)、状态编码在请求里(REST 的无状态约束)。JWT 的缺点:无法主动失效(token 过期前一直有效,服务端无法”踢人”)、token 体积较大、敏感信息不能放(token 可解码只是防篡改)。它的”无法主动失效”暴露的本质是:“把状态推给客户端”是用”无法撤回”换”无需存储”——要不要做这个交换取决于业务对”立刻踢人”的强需求程度。
-
状态机天然支持幂等,因为操作只在特定状态下有效:如”支付”只在”待支付”有效,重复调用时状态已是”已支付”,直接返回成功——幂等不是靠”判断这次是否重复”实现的,而是靠”状态机让重复调用天然无害”实现的。与”最好的约束是让人无法违反”的关系:状态机从结构上让”非法状态转换”根本不存在(如”已发货→已取消”这条边不存在),你不需要”记得”在每一处加判断,因为结构本身不允许——这把安全从”纪律”(人记得遵守)变成了”结构”(人无法违反),和第 20 章 Rust 用类型系统消灭数据竞争是同一个思想。
-
区别:传统只存”当前状态”(知道余额是 500,不知怎么变成的),事件溯源存”所有发生过的事件”(开户、存 1000、取 300、取 200),当前状态 = 重放事件的结果。判断标准”关心最终状态还是变化过程”:若只关心当前值(如购物车),用普通状态存储即可;若关心变化全貌(如银行流水需审计),事件溯源的价值就出来了——工具选择由”你真正关心什么”决定,事件溯源不是”更高级”,只是更匹配”关心变化过程”的业务。适合:需要完整审计追踪(金融/医疗/法务)、需要时间旅行或回放、业务本身就是事件流(交易系统、工作流)。不适合:简单 CRUD(过度设计)。代价是查询需重放(要快照)、事件结构演进困难、存储持续增长、心智负担重。
-
因为真实世界没有完美,“永不不一致”在分布式下几乎做不到(副本、缓存、从库之间总有时间差);而”不一致了能发现、能修正”(通过定期对账发现差异并修复)把工程难度从”不可能”降到”可行”。这与第 11 章(失败检测与容错)、第 13 章(隔离与降级)一脉相承:成熟系统不是”不让问题发生”,而是”假设问题会发生,并准备好应对”。这是从”追求 100% 一致”到”99.99% 一致 + 0.01% 能被对账抓出修正”的心态转变。