vlambda博客
学习文章列表

摇摇车项目(第十四章 分布式事务)

2.0平台服务化架构,必然分库,分库又必然面临一个分布式事务处理问题,所以无论是设计还是编码远远比1.0单体应用架构的工作量要大。不过做任何事情,重点不在实施,而是在思路,所以要解决分布式事务问题,还得先想清楚屡清楚怎么去做才是重点之重。

分布式事务处理方法其实大把,无需担忧找不到解决方法,关键是要找到满足自己业务场景,适合自己业务场景的方法,我之前做的项目涉及个分布式事务处理的方法也有好几个,其中记忆较为清晰的是一个移动充值的业务,大概业务就是用户通过银行在线支付完成后,然后给自己的手机号码充值,这个里面就涉及到银行充值和手机话费充值两个一致性的事务,当初用的就是通过每天凌晨的对账来处理这种事务问题,实现思路就是手机话费充值这端每天凌晨会上传一个订单文件到一个FTP服务器,然后通知银行那边去拿这个对账单文件来对账,然后再实行差补各种结算。当然具体问题还得具体分析,如果我们现在的业务场景直接也生搬硬套用这种方式来处理会不会也行呢?肯定不行!

我们的业务场景流程:用户通过APP扫码摇摇车,消费1块钱,针对每条订单马上所有参与方进行算账。

1. 用户账户扣1块钱。

2. 平台分2毛钱。

3. 城市合伙人账户分4毛钱。

4. 商家账户分4毛钱。

备注:前面在2.0架构体系里面已经说过分库分成用户库,平台库,城市合伙人库,商家库。

所以像这种业务场景,我们这边目前采用的是基于日志(事件)的最终一致性来实现分布式事务,当然里面涉及到知识点也较多,包括数据库单机事务,MQ消息通知,定时调度,异步等。

在详细介绍基于日志(事件)的最终一致性之前,必须先稍微说下柔性事务和刚性事务,其实这些概念没那么复杂,所谓的刚性事务是指严格遵循ACID原则的事务, 例如单机环境下的数据库事务,就是之前我们1.0平台spring事务实现那种。柔性事务稍微复杂点,是指遵循BASE理论的事务, 常见的实现方式有很多种: 两阶段提交(2PC),TCC补偿型提交,基于消息的异步确保型,最大努力通知型等。

我说这么多其实就是想说:大事务=小事务(原子事务)+异步(消息通知)。

这个里面每个库里面都有两个事件表,eventPublish(待发布事件表)和eventProcess(待处理事件表),表设计如下,也可以针对自己业务场景具体设计。

好啦,举个简单例子,APP扫码启动摇摇车(涉及两个微服务)。

1. 用户减少资金(数据库A)

2. 商家增加资金(数据库B)

基于日志(事件)的最终一致性实现思路如下:

1.用户服务在接收到用户请求后开启事务, 减少资金, 并且在eventPublish表创建一条status为1(待发布)的记录, payload记录的是事件内容, 提交事务。

2.用户服务中的定时器扫描,方法里面开启事务, 然后查询eventPublish是否有status为1的记录,查询到记录之后,拿到payload信息,通过MQ将消息发布商家(商家端有MQ实时监听)。

3.商家服务监听到MQ传来的用户减少资金事件(先判断下数据库是否已经存在,如果存在,通过MQ发消息给用户,向用户端返回接收成功的消息), 在eventProcess表创建一条status为1(待处理)的记录,payload记录的是事件内容, 如果保存成功, 向用户端(用户端有MQ实时监听)返回接收成功的消息。(目的是用户端将eventPublish的status为2,下次扫描就不会扫描到了)

4.用户端监听到接收成功后的消息,将eventPublish的status为2(已发布),下次扫描就不会扫描到了,要不然会持续往商家那边发消息,告诉商家那边增加资金。

5.商家服务中的定时器扫描,方法里面开启事务, 然后查询eventProcess是否有status为1(待处理)的记录,查询到记录之后, 拿到payload信息,处理业务给商家增加资金. 处理成功之后修改数据库中eventProcess的status为2(已完成),,最后提交事务。

大概实现思路就是这样,这种处理方式网络请求吞吐量是比较高的,基本都是分段式异步处理的。

当然我们的业务场景比这个更加复杂些,一个流程涉及到的原子库更多,但是大概的思路都是类似处理,整个设计如下:

我们的具体业务情况整个流程还有两块也在优化中。

1.异常容灾处理,比如分段式进入到中间某段由于各种原因无法进行下一步,而且连续不断通过MQ和调度在执行多次,这个时候我们会检测,如果尝试重试多次无效,会进入到二级消息队列,并可以后台人工参与处理。

2.单链路方式的灵活性再加强,比如用户的下一段并不是到商家,也有可能是城市合伙人,或者其它,这个时候可以通过MQ的传递参数进行判断,下一段的到达点。

总之基于MQ分布式业务的处理场景,瓶颈稳定性在于MQ本身,所以这块必须保证它的持续高可用和稳定性,后续在项目中碰到的细节问题我会继续分享。