我们在做业务时,很多时候都用到了事务。最近业务涉及也蛮多的,这里总结下。 单库用事务就可以搞定,但是事务不是万能的。就算隔离级别是可重复读,并发情况下也会出现更新丢失的问题,所以事务并不能一定保证数据一致性。但在事务中使用悲观锁可以办到。但是悲观锁用的不好可能导致死锁,而且从底层上说使用的是排占锁,性能肯定达不到很高。换个思维用乐观锁也可以解决,乐观锁提高了并发性能,但是乐观锁会导致很多修改失败抛出异常,说不定反而会影响性能,这里不多讲这两个东西。
多库或者多服务时问题就稍显复杂。我们怎么保证这种场景下的数据的一致性呢?该场景下数据的一致性分强一致性和弱一致性。一般业务场景下,我们用的多的还是弱一致性,毕竟开发成本低的多。只要允许一定时间窗口数据不一致,但是又能保证数据最终是一致的就可以选这个。弱一致性大多是通过补偿机制来完成。这里总结为三种方式:
- 推.A主动向B推送消息或者主动调B的接口,MQ消息或简单HTTP调用,直到对方给你确认OK为止。
- 拉.B主动取A查询消息结果,直到确认数据OK。
- 冥等性.多次重复操作,必须保证结果是一样的。这里可以为消息加一个唯一标识,并且要存这个消息的状态。相同的消息只处理一次,并做状态判断。
就拿我们调用第三方扣款,扣用户账户的钱为例。
- 直接调用,同步返回结果,改状态最简单。但是这样弊端明显,用户体验不好,可能等半天才返回,最好做成异步。
- 调用过程中超时了。我不知道是请求超时,还是返回超时。怎么办?如果对方支持重复调用肯定很好,再次调用直到成功。如果对方调一次扣一次,那就只有拿上次调用的数据去查结果,如果成功了则改状态,如果对方说你这个数据没收到过,那就再发起一次请求。这样一直类推直到成功。
如果是操作特别敏感的数据,那么就要求保证数据的强一致性。
常用的分布式场景强一致性协议有二阶段提交协议。顾名思义事务的执行分为两个阶段,第一事务执行,第二事务提交。协调者询问A,B库是否可以开始执行事务,A.B都回答是,那么A继续执行事务,B继续执行事务。当A,B都报告执行完毕时,这时第一阶段算是完了。第二阶段协调者告诉AB都去提交刚才的事务,当AB都提交后,各自释放资源。其中执行事务的时候会记录各自的undo和redo日志,这个别忘了。里面存在的问题大家都注意到了吧。
- 第二阶段通知A,B提交事务时,如果A成功通知了,B没通知到,那岂不是数据都不一致了,而且B只有等到超时才会释放资源。
- 所有的过程都是同步阻塞的,这就意味着性能会大大降低。
- 协调者这个角色存在单点问题,如果它挂了其他事务都会一直占用资源直到超时。
虽然工作中我们很少会用到这种协议接口,但是根据这个思路,我们也不难开发自己的分布式事务接口。开发过程中,总是要在数据的正确定和系统的性能间去寻找那个平衡点。我们是单一的把代码搞在一起,避免出现分布式事务,还是把系统拆分开发部署。这个问题没有最好的答案,关键点还是要看你目前的业务量。