原子性:从总线锁到分布式事务,我踩过的坑和悟出的道

硬件级的原子魔法

先别扯什么数据库事务,那距离真实的硬件太远了。真正的原子性,其实就刻在CPU的硅片上。还记得第一次读《计算机体系结构》时,被“总线锁”这个词击中——对,就是它。你想想,多个CPU核心,同时想改内存里同一个变量,怎么办?没有协调者,它们直接拼刺刀。后来Intel搞出了缓存一致性协议,MESI那一套。其实说到底,就是为了保证那条简单的INC指令真的能原子执行。compare-and-swap,CAS,这绝对算得上计算机科学史上最伟大的黑魔法之一。它把“比较并交换”做成了一个不可分割的操作。硬件直接在电路层面保证了,没得商量。

但别高兴太早。CAS指令背后往往是LOCK前缀,这玩意儿一加,总线就被锁住了,其他核心得靠边站——性能直接打七折。后来有了缓存锁,只在缓存行上做文章,效率翻倍。我第一次看到Michael-Scott无锁队列的实现时,惊为天人。就靠一个CAS,把入队出队玩出花来。但自己一写就崩溃。ABA问题听说过吧?线程A读了一个值,线程B进来改了,又改回去了,线程A傻傻地以为没变化,CAS成功,但链表早就不是那个链表了。我第一次在生产环境遇到ABA,整整debug了两天——那种绝望,就像你以为锁好了门,结果小偷撬锁进去转了一圈又帮你锁上,你还跟警察说我家很安全。从那以后,我发誓,但凡用CAS,一定要带版本号。Java的AtomicStampedReference就是干这个的,多了一层stamp,就像给变量刻上了时间戳。

CPU缓存一致性协议MESI状态转换图
CPU缓存一致性协议MESI状态转换图

分布式原子性:当你的数据库跑在多台机器上

然后时代变天。单机数据库?不能满足C端爆发式的流量。我们开始拆库拆表,搞微服务。一笔订单,可能得同时写入订单库、库存库、积分库。这时候原子性就成了噩梦。一个经典场景:订单创建成功,库存扣减失败,积分加了——数据乱成了狗。你总不能告诉用户“订单可能成功了,也可能没有,你猜”。两阶段提交,2PC,上场了。说实话,2PC的协议看起来优雅,像芭蕾舞:准备,提交。协调者一声令下,各节点要么都执行,要么都回滚。可是,阻塞问题,简直要命。当协调者挂了,参与者就傻等,一直占着锁,整个系统像被冻结了。有一回,我们的支付系统就因为2PC的协调者宕机,导致订单超时雪崩,那一天的复盘会,老板的脸比锅底还黑。

于是人们搞出了三阶段提交,3PC,加了超时机制,让参与者等不到确认就自己提交,减少阻塞。但真能解决问题?引入超时,就意味着可能出现网络分区时的不一致——脑裂,两条数据各自提交,更可怕。所以实际上,面向大规模互联网的分布式事务,2PC基本被放弃了。我们更加推崇最终一致性的补偿模式,比如Saga。把一个长事务拆成多个本地事务,每个有对应的补偿操作。如果中间失败,反向调用补偿。这就像你下馆子,先点菜、再上菜、再结账——发现忘带钱了,你就得把菜退了,还得道歉补偿洗盘子。

两阶段提交协议协调者与参与者交互时序图
两阶段提交协议协调者与参与者交互时序图

不过,也别把Saga捧上天。有人觉得异步补偿就能搞定一切,殊不知补偿操作本身也需要幂等和重试机制。我曾经见过一个退款流程,因为补偿接口没做幂等,退款重复打了三次——财务追着我跑了三天。所以,补偿加分布式锁,或者依赖数据库唯一约束,才算基本功。还有个狠活:Google Spanner直接搞了个TrueTime,用原子钟和GPS把时钟漂移控制在毫秒级,然后硬怼2PC,配合Paxos组,把原子性和外部一致性一把梭。那套架构读完论文,我愣是亢奋了一整晚——这才叫工程美学,用硬件和数学把不可能变成日常。

Saga事务补偿模式示意图
Saga事务补偿模式示意图

三个血淋淋的坑,和爬出来的路

三个血淋淋的坑,和爬出来的路
三个血淋淋的坑,和爬出来的路

第一个坑,上面说了,ABA问题。解法我再重复一遍:永远不要用裸CAS,给值加上版本号。能省你无数个不眠夜。第二个坑,把原子性等同于可线性化。很多人以为一个操作是原子的,就理所当然认为所有线程看到的事件顺序都是一致的。并非如此。原子性保证“全有或全无”,但不保证操作完成的瞬间,所有CPU核的缓存都立刻看到新值。内存屏障,StoreLoad之类的,才是保证可见性的关键。你用了原子变量,但没加volatile或者没正确设置内存序,照样会读到过期的数据。我被这个坑过:一个全局开关,原子地设为true,但别的线程过了好几秒才看到变化,线上故障就是这么出来的。所以,原子操作加内存屏障,才是一条完整的安全链。第三个坑,分布式事务中,盲目相信强一致性。很多初创团队的架构师,被数据库ACID宠坏了,以为跨服务也能像单库那样搞事务。结果引入2PC,性能直接崩溃,延时飙升。后来我们整个公司推行Saga和TCC,虽然代码复杂度高了一些,但系统伸缩性好了不止一个数量级。我手头有个压测数据:用2PC的支付链,TPS只有可怜的一百多;切换到Saga异步模式后,轻松上三千,而且CPU负载反而下降了40%。原因很简单,不需要长时间持有锁。

这些经验,不是书上看来的,都是真金白银——不,真故障堆出来的。我曾经天真的以为理解了概念就能驾驭系统,最后发现,每一个看似简单的原子性保障背后,都牵扯到硬件、编译优化、网络分区、CAP理论。甚至,原子性本身就是一种无奈的妥协。完完全全的原子性在现实物理世界是不存在的,我们只是在近似地、小心翼翼地维持着一种假象。不过,正是这种近似,构建了我们赖以生存的数字时代。得了,就说这么多吧。剩下的,代码会教你。

免责声明:市场有风险,选择需谨慎!此文仅供参考,不作买卖依据。如有侵权请联系删除。
文章名称:原子性:从总线锁到分布式事务,我踩过的坑和悟出的道
文章链接:https://m.lfdjt.com/info_23_7691.html