数据流动、隔离粒度与一致性
上一篇笔记理清了 Environment、Cell、Data Mesh 这些概念怎么把一个大系统”切开”。但切开只是第一步,切开之后这些独立的单元怎么正常协作,是另一套完全不同的问题。这篇接着记录三块内容:数据怎么从一个 Environment 流到另一个、隔离到底该做多细、跨 Environment 的业务怎么保证不出错。
一、数据怎么”动”起来:CDC 和 Outbox
之前一直用”通过服务集成/分发”这种笼统说法来描述 Environment 之间的协作,但没细说服务具体怎么把数据搬过去。这里其实有明确的技术路线,而且选错了会埋下大坑。
Change Data Capture(CDC)
如果一个 Environment 需要把数据变化实时同步给另一个——比如分公司的交易数据同步到集团 Hub——现代做法不是定时批量导出导入(传统 ETL),而是 CDC:直接读取数据库的事务日志(MySQL 的 binlog、PostgreSQL 的 WAL),把每一条增删改实时转成事件流推送出去。
开源工具 Debezium 是这个领域的标准实现。相比传统批处理,CDC 的优势很直接:延迟低,接近实时;对源数据库压力小,不需要跑大查询扫全表,只是尾随日志。
Transactional Outbox 模式
CDC 解决了”怎么捕获变化”,但还有一个更隐蔽的问题需要配套解决,业界叫双写问题(dual write problem):如果一个服务既要把数据写进自己的数据库,又要发消息通知别的服务,这两个操作没办法保证同时成功。数据库写成功了消息发送失败,或者反过来,都会导致数据不一致,而且这种不一致很难排查。
Outbox 模式的解法是:把要发出去的事件,先作为一条记录,和业务数据在同一个数据库事务里一起写进一张”outbox 表”。业务写和事件写就变成了同一个原子操作,不会出现只成功一半的情况。再用 CDC 之类的机制把 outbox 表的变化异步转发出去,完成真正的跨系统通知。Debezium 的维护者 Gunnar Morling 专门推广过这个组合,现在基本是微服务之间做可靠事件通知的标准配置。
这两个放在一起,”服务协作”就从一句抽象口号变成了具体能落地的技术方案:业务数据和要发出的事件绑在一个事务里写本地,CDC 尾随日志把变化异步转发出去,下游 Environment 订阅这个事件流拿到数据。
二、隔离到底要做多细:Silo / Pool / Bridge
之前说”Environment 具有隔离功能”,这句话其实藏了一个没展开的问题——隔离不是有没有的问题,是做到多深、多贵的问题。AWS 在 SaaS 架构指南里把隔离粒度分成三种模型(AWS SaaS Storage Strategies):
Silo 模型:每个租户/分公司独占一整套物理资源,独立数据库实例甚至独立服务器。隔离最彻底,一个租户出问题绝对不会牵连别人,但成本最高,资源利用率最低。这就是之前说的”完全独立的 Environment”。
Pool 模型:所有租户共享同一套物理资源,靠应用层逻辑区分数据归属——最常见的做法是每张表加一个 tenant_id 字段,查询时靠这个字段过滤。成本最低,资源利用率最高,但隔离性最弱:一旦代码某处忘了加 tenant_id 过滤条件,就是实打实的数据泄露风险。
Bridge 模型:介于两者之间。物理资源共享(同一个数据库实例),但逻辑上给每个租户分配独立的 database 或 schema。兼顾一定的隔离性和资源效率,是很多 SaaS 产品的折中选择。
这里有个特别值得记下来的提醒:数据分区(partitioning)不等于租户隔离(isolation)。把数据分开存放只是第一步,真正的隔离还需要配合访问控制,确保一个租户的代码和凭证,物理上、逻辑上都够不到另一个租户的数据。之前说”Environment 具有隔离功能”这句话,拆开看其实包含了两层决策:粒度选多细(Silo/Pool/Bridge 三选一),以及边界怎么在代码和权限层面真正卡死。这两件事都不是自动发生的,是要花钱、花设计精力去做的工程决策,直接决定了成本结构和风险敞口。
三、跨 Environment 的业务怎么保证不出错:Saga 与最终一致性
这块是最容易被忽略、但影响最大的一块。之前讨论隐含了一个假设:业务操作在自己的 Environment 内闭环完成。但现实中经常不是这样——分公司下单可能需要扣减集团层的信用额度,主数据 Environment 里客户信息一变,可能要同步影响多个分公司 Environment 里正在跑的业务单据。
一旦数据分散在多个独立数据库里,单机数据库那种 ACID 事务的”原子性”就不再成立了。没有任何手段能保证”A 库的写”和”B 库的写”同时成功或同时失败,这是分布式系统的硬约束,不是工程能力不够,是物理上做不到。
业界公认的标准解法是 Saga 模式(Chris Richardson, Microservices Patterns;Azure Architecture Center 的说明):把一个跨 Environment 的业务流程拆成一串本地事务,每一步只在自己的 Environment 内提交,提交后发一个事件触发下一步。如果某一步失败,不是整体回滚,而是依次执行前面几步对应的补偿事务(compensating transaction),把已完成的操作一步步撤销回去——比如前面扣了库存,补偿动作就是把库存加回去,而不是指望有个”全局撤销”按钮。
配合 Saga 模式,必须要接受的一个心理准备是最终一致性(eventual consistency):跨 Environment 的数据在某个时间窗口内注定是不一致的——比如分公司这边已经扣了库存,但集团 Hub 还没收到同步事件。系统设计要能容忍这个窗口存在,同时要有机制保证数据最终收敛一致,而不是追求时时刻刻强一致。对习惯单体数据库强事务的团队来说,这往往是最大的一次心态转变,也是实际项目里踩坑最多的地方——很多”诡异的数据不一致 bug”,追根究底都是有人在分布式场景里,还在按单机事务的直觉写代码。
小结
这三块内容合起来看,其实是在回答同一个问题的三个侧面:系统切开之后,数据怎么流动(CDC + Outbox)、隔离要做到多深(Silo/Pool/Bridge)、跨界的业务怎么保证正确(Saga + 最终一致性)。
如果把上一篇笔记看作”怎么把一个大系统切成能独立管理的单元”,这一篇就是”切开之后怎么让这些单元继续像一个整体一样正常工作”。两篇拼起来,基本是从架构决策到工程落地的一条完整链路——先决定切在哪个维度、切多细,再决定数据怎么流、事务怎么保正确。后面这条链路里的坑,往往比”该不该切”这个决策本身更容易在实际项目里踩到,值得单独花时间理解清楚。