@
opengps #8 楼说的非常正确,要考虑的情况很多,逻辑要求应当非常严谨。
现在市面上的文章很多都没考虑到数据的一致性。(例如 redis 扣减库存,然后 DB 再慢慢处理订单)
经过分析,不难发现,redis+DB 有以下两个主要 ACID 方面的问题:
1. "ACID.Consistency"(一致性):因为 redis 和 DB 是两个数据储存,所以涉及到分布式事务。
分布式事务一般有如下两种选择:A. 采用某种协议,例如 Paxos,保证强一致性。B. 采用消息队列+补偿来保证最终一致性。
2. "ACID.Duration"(持久性):redis 断电后如何恢复?
一般可以采用的是:A. 副本集 B. 平行系统( Parallel Model - Martin Fowler )
对于“一致性”问题,A 方案(分布式事务协议)是不现实的,因为 redis 本身的事务支持就不完备。如果采用 B 方案,用消息队列,也是无法实现的,因为 outbox pattern (自己去查英文资料)依赖 ACID 的单个数据库的事务,而 redis 本身的事务支持就不完备。
现在,我们发现,万恶之源在于 —— redis 对事务的支持不完备。
-------
但是,我们可以转换一下思路,把 redis 的角色从更偏向 DB 的角色转换成更偏向 cache 的角色。
这时候就应当引入 [软状态] :状态定期过期并刷新。
简单来说,就是定期把剩余库存定期更新到 redis 。
接下来详细说一下:
假设我们的步骤如下:
1. 连接 redis,库存-1,如果成功(可以使用 lua 脚本来保证原子性),那么就进行下一步;如果失败,就提示已售罄。
2. 连接数据库,创建订单(或者为了削峰,写消息队列,事后慢慢在 DB 创建订单)
但是,这两步不是原子性的,会造成 redis 和 DB 的状态不一致:
如果 redis 扣减库存成功了,但是连接数据库失败了,那么就会存在少卖的情况。但是不会超卖。幸运的是,少卖比超卖好解决。
这时候,只需要在消息队列里面的订单处理完成后,将 DB 里面真实的剩余库存同步到 redis 即可。