首先,谢谢各位大神的指点。结合工作中遇到的问题和目前我了解到的分布式处理方案来简单谈谈。
1.事务的特性
事务必须满足传统事务的ACID特性,即原子性,一致性,分离性和持久性。
原子性:即最小单位的原子,要不全部成功,要不全部失败。
一致性:。事务必须始终保持系统处于一致的状态,不管在任何给定的时间并发事务有多少(百度百科)。个人理解:在数据库中的表现就是完整性约束没有被破坏。
隔离性:事务的隔离性将确保每一事务在系统中认为只有该事务在使用系统。这种属性有时称为串行化,为了防止事务操作间的混淆,必须串行化或序列化请求,使得在同一时间仅有一个请求用于同一数据(百度百科)。个人理解:多个事务不应该影响其它事务运行效果。在数据库中是通过加锁和阻塞来保证事务之间不同等级的隔离性。不同的隔离级别是通过加不同的锁,造成阻塞来实现的。值得一提的是:完全的隔离性是不现实的,完全的隔离性要求数据库同一时间只执行一条事务,这样会严重影响性能。
持久性:事务完成以后,持久的的保存在数据库,不会回滚。
2.本地事务
本地事务,基本都是依赖数据库,在同一个数据库内,用数据库本身带的事务来保持事务。操作简单,这里就不说了。
3.分布式事务的出现
随时,数据量的增加,单台数据库,或者简单的主从架构已经没法适应数据量的发展了。这个时候,可能需要做业务的服务化,那靠数据库本身已经做不了事务了。
定义:分布式事务就是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上.
4.分布式事务解决方案
4.1 两阶段提交协议
定义:所谓的两阶段提交协议主要是分为2个阶段。第一阶段:准备阶段(投票阶段)和第二阶段:提交阶段(执行阶段)
通过以上4步完成了一个消息事务。对于以上的4个步骤,每个步骤都可能产生错误,下面一一分析:
- 步骤一出错,则整个事务失败,不会执行A的本地操作
- 步骤二出错,则整个事务失败,不会执行A的本地操作
- 步骤三出错,这时候需要回滚预备消息,怎么回滚?答案是A系统实现一个消息中间件的回调接口,消息中间件会去不断执行回调接口,检查A事务执行是否执行成功,如果失败则回滚预备消息
- 步骤四出错,这时候A的本地事务是成功的,消息中间件能够检查到A执行成功了,消息中间件可以自己对消息进行提交,从而完成整个消息事务
4.1.1消息的保存
如果仔细观察生活的话,生活的很多场景已经给了我们提示。
比如在北京很有名的姚记炒肝点了炒肝并付了钱后,他们并不会直接把你点的炒肝给你,往往是给你一张小票,然后让你拿着小票到出货区排队去取。
为什么他们要将付钱和取货两个动作分开呢?原因很多,其中一个很重要的原因是为了使他们接待能力增强(并发量更高)。
还是回到我们的问题,只要这张小票在,你最终是能拿到炒肝的。同理转账服务也是如此,当用户A账户扣除1万后,
我们只要生成一个凭证(消息)即可,这个凭证(消息)上写着“让用户B账户增加 1万”,只要这个凭证(消息)能可靠保存,
我们最终是可以拿着这个凭证(消息)让用户B账户增加1万的,即我们能依靠这个凭证(消息)完成最终一致性。
- 与业务耦合的模式。即,发送消息和执行本地事务在一个事务中,还是利用数据库本身的事务来保存数据,只是把后期消息的处理拆出来了,这个方案与业务耦合度太高,扩展性,等等都不太好,但是胜在简单。
- 与业务解耦的模式。为了不与业务耦合,使架构更加的优雅,减少诱发其他问题。建议是使用解耦的模式。
1)用户A在扣款事务提交之前,向实时消息服务请求发送消息,实时消息服务只记录消息数据,而不真正发送,只有消息发送成功后才会提交事务;
2)当用户A扣款事务被提交成功后,向实时消息服务确认发送。只有在得到确认发送指令后,实时消息服务才真正发送该消息;
3)当用户A扣款事务提交失败回滚后,向实时消息服务取消发送。在得到取消发送指令后,该消息将不会被发送;
4)对于那些未确认的消息或者取消的消息,需要有一个消息状态确认系统定时去用户A系统查询这个消息的状态并进行更新。为什么需要这一步骤,
举个例子:假设在第2步用户A扣款事务被成功提交后,系统挂了,此时消息状态并未被更新为“确认发送”,从而导致消息不能被发送。
优点:消息数据独立存储,降低业务系统与消息系统间的耦合;
缺点:一次消息发送需要两次请求;业务处理服务需要实现消息状态回查接口。
4.1.2 重复性提交问题
其实,就是我们说的幂等性问题,即:任意多次执行所产生的影响均与一次执行的影响相同。
解决方法很简单,在用户B这边增加消息应用状态表(message_apply),通俗来说就是个账本,用于记录消息的消费情况,每次来一个消息,
在真正执行之前,先去消息应用状态表中查询一遍,如果找到说明是重复消息,丢弃即可,如果没找到才执行,同时插入到消息应用状态表(本地事务)。