文档参考:书名:《企业it架构转型之道》-钟华
前文如下:
1. 柔性事务如何解决分布式事务问题
(1)引入日志和补偿机制
类似传统数据库,柔性事务的原子性主要由日志保证。事务日志记录事务的开始、结束状态,可能还包括事务参与者信息。参与者节点也需要根据重做或回滚需求记录REDO/UNDO日志。当事务重试、回滚时,可以根据这些日志最终将数据恢复到一致状态。
为避免单点,事务日志是记录在分布式节点上的,数据REDO/UNDO日志一般记录在业务数据库上,可以保证日志与业务操作同时成功/失败。通常柔性事务能通过日志记录找回事务的当前执行状态,并根据状态决定是重试异常步骤(正向补偿),还是回滚前序步骤(反向补偿)。
在互联网业界采用日志方式实现柔性事务的比例非常大,但因为这部分的技术实现并没有如XA这样的技术标准和规范,看到很多互联网应用对这部分的实现非常的粗糙,只是简单的采用数据库进行了分布式事务过程中的状态记录,对于事务中异常处理和补偿回滚支持是明显不够的,并不能完全意义上的满足业务的最终一致性,而且一旦出现问题,所投入的人力维护成本也非常高昂。
(2)可靠消息传递
在分布式环境下,由于“网络通信危险期”(见下面阅读框中内容)的存在,节点间的消息传递会有“成功”、“失败”、“不知道成功还是失败”三种状态。这也给进行分布式事务处理时提出了更多的考虑点和要求。可靠消息投递就是为了解决这类问题产生的服务平台。
根据“不知道成功还是失败”状态的处理,消息投递只有两种模式:1)消息仅投递一次,但是可能会没有收到;2)消息至少投递一次,但可能会投递多次。 在业务一致性的高优先级下,第一种投递方式肯定是无法接受的,因此只能选择第二种投递方式。
由于消息可能会重复投递,这就要求消息处理程序必须实现幂等(幂等=同一操作反复执行多次结果不变),这一要求跟传统应用开发相比是非常具有互联网特征的一种模式,在很多的应用场景下,都会要求程序实现幂等。 每种业务场景不同,实现幂等的方法也会有所不同,最简单的幂等实现方式是根据业务流水号写日志,阿里内部一般把这种日志叫做排重表。在接下来的场景示例中会有更加详细的介绍。
关于网络通信的危险期
由于网络通信故障随时可能发生,任何发出请求后等待回应的程序都会有失去联系的危险。这种危险发生在发出请求之后,服务器返回应答之前,如果在这个期间网络通信发生故障,发出请求一方无法收到回应,于是无法判断服务器是否已经成功地处理请求,因为收不到回应可能是请求没有成功地发送到服务器,也可能是服务器处理完成后的回应无法传回请求方。这段时间称为网络通信的危险期(In-doubt Time)。很显然,网络通信的危险期是分布式系统除单点可靠性之外需要考虑的另一个问题。
(3)实现无锁
现在大家都知道造成数据库性能和吞吐率瓶颈往往是因为强事务带来的资源锁。如何很好地解决数据库锁问题是实现高性能的关键所在。所以选择放弃锁是一个解决问题的思路,但是放弃锁并不意味着放弃隔离性,如果隔离性没有保障,则必然带来大量的数据脏读、幻读等问题,最终导致业务不可控地不一致。
实现事务隔离的方法有很多,在实际的业务场景中可灵活选择以下几种典型的实现方式。
❑避免事务进入回滚。如果事务在出现异常时,可以不回滚也能满足业务的要求,也就是要求业务不管出现任何情况,只能继续朝事务处理流程的顺向继续处理,这样中间状态即使对外可见,由于事务不会回滚,也不会导致脏读。
❑辅助业务变化明细表。比如对资金或商品库存进行增减处理时,可采用记录这些增减变化的明细表的方式,避免所有事务均对同一数据表进行更新操作,造成数据访问热点,同时使得不同事务中处理的数据互不干扰,实现对资金或库存信息处理的隔离。比如在用户进行订单创建操作时,需要对商品的库存进行减扣,如果是在秒杀和大促场景下,大量订单都是对同一商品进行下单操作,如果所有订单创建事务中都是修改商品表中商品数据的库存的信息,则必然会出现该条商品记录访问热点,而且很容易出现锁抢占的情况,避免锁的方式就是在订单创建事务中只是在“库存预减明细表”中添加一条对应商品的库存预减记录,而无需对原商品数据表进行库存修改的操作,一旦用户成功付款,则真正地将商品数据表中的库存减除。 在付款之前当应用要获取该商品的库存信息时,则是通过以下公式获得:
❑乐观锁。数据库的悲观锁对数据访问具有极强的排他性,也是产生数据库处理瓶颈的重要原因,采用乐观锁则在一定程度上解决了这个问题。乐观锁大多是基于数据版本(Version)记录机制实现。例如通过在商品表中增加记录版本号的字段,在事务开始前获取到该商品记录的版本号,在事务处理最后对该商品数据进行数据更新时,可通过在执行最后的修改update语句时进行之前获取版本号的比对,如果版本号一致,则update更新数据成功,修改该数据到新的版本号;如果版本号不一致,则表示数据已经被其他事务修改了,则重试或放弃当前事务。如图所示,当两个事务同时要对商品数据表进行更新操作时,通过版本号的方式实现乐观锁机制的示意。