场景:A数据库有一个人叫小明,B数据库有一个人叫小花,现在小明要给小花转账100,那么就有两个操作,小明账户-100,小 花账户+100,由于是跨数据库,事务在此时没用,那该如何保证两人账户数据的准确。
方案一:
两次提交,设计理念:在同一个数据库中,我们可以使用事务来保证数据的原子性。现在我们有两个数据库,就有两个事 务,这两个事务分别保证各自的原子性。我们引进一个第三方,事务管理器,管理这两个事务。小明账户-100,事务完成 待提交,反馈一个信息给事务管理器,告知A事务已准备好。小花账户+100,事务完成待 提交,同样反馈一个信息给事务 管理器告知B事务已准备好。两个都准备好了再统一提交,否则一方报错全部回滚。瓶颈:1.多个事务必须等待其他事务共 同完成才能完成,阻塞性太大,再高并发情况下延迟太长。2.事务管理器万一有问题,那么事务都会陷于等待状态。
进化一:
使用中间件消息队列,我们不期望能够实现名义上的原子性,只要最终数据达成一致就行。小明账户-100操作已执行,但 小花账户到账时间可以延长一段时间显示,最终我们数据是一致的。在小明账户-100操作完成了,这时我们向消息队列中 写入一条消息,小花账户+100,。我们消费者读到这条消息时,再向小花转账。那么小明账户-100事务和消息队列是两个东 西,怎么做到原子性呢?把消息插入到队列这块代码放到事务代码的最后面吗?如果事务提交了,消息却未插入到队列就 会有问题了。
进化二:
我们没有办法保证事务和消息队列的原子性,那我们把消息队列出现场景稍微往后挪一下。我们引进一张事件表,当小明 账户-100,这时小花账户+100,把小花账户+100这个待执行事件写入到事件表中。这样都是sql,我们可以做到事务的原 子性了。我们再弄个定时器,将事件表中的待执行操作都写入到消息队列中,把事件置为已执行,然后等待消息消费就 行。这样貌似已经可以完美的处理了,但还是有一个问题。如果我们将待执行事件已写入到消息队列,但这时候停电,事 件表中的数据未被置为已执行,下次读取还是会写入到消息队列中,最终小花可能会+200。
进化三:
我们再引入一个概念:幂等性。大致意思就是:无论我操作一个事务千万次,数据始终不变。比如select * from查询语句,
无论我们执行多少次,数据都不变。那么小花到账+100执行多次就会多次改变,但如果我只让他执行一次呢。上面说到 可能消息队列会有两条甚至多条小花账户+100。在B数据库中同样增加一张已处理事件表,记录已处理的消息。每次处理 消息队列时我们先判断是否已执行过。
总结于:《码农翻身》