架构师之路分布式

一个分布式事务的解决方案

2018-01-11  本文已影响1843人  小盒子的技术分享

下文是为应对我司分布式事务带来的问题所设计的解决方案,已在生产环境应用,由于之前是word版本写的就直接copy来了,其中一些代码和命名不用太在意,可以提供给大家参考的主要是设计和实现思路,当然,这也是基于公司现有业务和本人设计水平而决定的,一定是不完美甚至是不完善的,不足指出,欢迎指正。

一 背景


  库存系统现进行改进优化,目标将以前所有与库存有直接关系的数据库表逐渐拆离出去。将以前各系统对这些表的访问,变为访问远程服务。远程服务由库存系统carInventory提供。如下图如示(以ordercollect表为例):无论“原先的方式”,还是“现在的方式”我们的业务系统程序流程都是同步执行的。


image.png

二 问题


  由于采用远程服务调用方式执行程序,程序的稳定和可靠性相对降低,风险加大,比如:

三 问题分析

  这里我们将整个的调用过程中的调用方称为系统A,将被调用方称为子系统B。子系统B中封装了远程调用的方法,其中针对数据库表的操作是带事务的,如在子系统B的程序执行过程中出现了异常,因为有事务,所以会回滚异常数据。相对的,在系统A中在程序的执行过程中,也是带事务执行的,所以A、B两端分别有两个事务。
尽管在两端我们都有事务,可还是会在一些节点出现问题,如下图所示,是两系统RPC的交互过程。


image.png

  因为两系统的事务解决了两端程序的异常问题(回滚),所以这里我们重点关注的是请求响应异常的情况:
如下图所示:在请求返回异常结果的时候会出现以下两个问题:

  1. 一次请求不成功,有可能子系统B的数据已经入库,只是可能由于网络原因,或其它原因,导致返回结果异常。如果进行多次重试,每一次重试,由于子系统B的程序都能执行成功,将会产生赃数据。只不过还是返回的过程中出现异常
  2. 系统A在所有请求全部失败的情况下,如何回滚子系统B可能已经产生的数据。


    image.png

四 解决方案


  如果系统在调用过程中一切正常,无论在任何节点,任何一段程序代码都不会出现问题,我们就不用考虑了。然而实际的情况是,系统有可能会在各种节点产生不一样的问题,虽然出现问题的概率比较小,但也需要我们准备好相应的解决方案来处理。
  由于之前我们已分析出问题可能出现的位置,所以下面来说明一下针对这些问题的解决办法。
  理论上,我们是可以将这两个事务做成分布式事务的,目前对于不要求强一致性的业务,比较流行的做法是将分布式事务,拆解为异步消息通知方式,通过消息队列处理任务,做到最终一致性。但由于我们的业务系统中的业务流程是同步的,而非异步的,而且库存计算结果是要求比较强的一致性和低延迟的效果,所以不能完全使用这个方法。如果使用分布式事务(深坑),又会增加复杂度和维护成本,所以这里也不建议采用分布式事务。
  综合考虑,我们的方案是:RPC重连机制+构造幂等接口+事务补偿机制

1 PRC 重连机制

  在各系统中通过maven引用的框架包中,关于RPC调用默认采用的是Hession,包中已经帮我们封装好了超时及重试。超时是在配置中心配置,如下图所示:


image.png

重试,则是通过传递自定义参数来进行的,如下代码所示:

 //自定义参数方式
RemoteClientContextVO vo = new RemoteClientContextVO();
    vo.setRepeatCount(5);
    vo.setRemoteType(RemoteType.HESSIAN);
    vo.setUrl("http://localhost:8096/carinventory");
    remoteClient = RemoteClientFactory.getInstance(vo);     
上面代码中repeatCount属性即为重试次数。重试的原理,如下图: image.png

2幂等接口构造

  有关API的幂等性,简单说,就是对同一接口,调用多次和调用一次的情况一样。举例说明:对于ordercollect表的insert操作,如果第一次请求子系统B执行成功但返回异常,那么系统A将发起重试请求,这时子系统B,不会再进行数据库的insert操作,而是直接返回上一次的执行结果。
  具体做法是:我们将请求参加中,多加一个requestid参数,这个参数可以是uuid,即一次请求的唯一标识。每次请求都会带着这个requestid来进行请求。在子系统B中,我们用redis来存储requestid以及相对应的返回值,结构如下:<uuid,返回结果对象>,这样在每次请求的时候,都会先查询redis有没有与requestid相匹配的键值,如果有,直接返回,如果没有再进行程序流程。Redis由于我们具有主备,可以不用担心单点问题,redis中的数据,会定时进行一次清理(可能每天)。

3事务补偿机制

  此处我们来解决问题分析中的第二个问题,即系统A如何回滚子系统B的数据。由于数据库的DML中我们只关心insert、update和delete,所以解决方案也是围绕这些操作设计的。
拿orderCollect表来举例。

综上所述,事务补偿这部分如下所示:


image.png
上一篇 下一篇

猜你喜欢

热点阅读