我说分布式事务之最大努力通知型事务
在之前的文章中,我们介绍了基于TCC模式的分布式事务解决方案
TCC适用于公司内部对一致性、实时性要求较高的业务场景,而本文我们讲解的“最大努力通知型事务”是为解决跨网络、跨服务之间的柔性事务的另一种解决方案。
首先看一下最大努力通知型的流程图,如下图
我们根据图中的内容,逐步分析一下该方案的业务流程。
1. 原理
首先阐述一下该方案的原理,根据图中所示,
业务活动的主动方(通知发起方),在完成本地业务活动处理后,会向业务活动被动方发送消息【箭头1】, 将业务执行结果通知给业务被动方【箭头4】。这个过程允许消息丢失,如果发生丢失的情况,服务主动方能够通过重试尽力实现双方的数据一致性。此处的的重试就体现出了–最大努力 的特点。
然后是业务的被动方。业务被动方会暴露一个业务结果接收接口(或者叫回调接口也可以)给业务主动方【箭头4】,对收到的通知消息进行必要的校验,校验通过后执行本地业务,从而使业务达到闭环。
同时,由于主动方对被动方的通知次数是有限的,即我们不可能无限制的通知,因此业务活动主动方需要提供一个业务查询补偿接口供被动方使用【箭头5】,被动方会根据定时策略,向业务活动的主动方发起查询操作,从而对丢失的业务消息达到补偿的目的。
整个过程中,业务被动方暴露给主动方的通知接收接口 以及 业务主动方提供给被动方进行查询操作的接口均需要实现幂等,这样才能保证数据完整性不会被破坏,从而实现最终一致性。
2. 详解
接下来,我们详细展开对该方案的讲解。
在这个方案中,我们要求被动方的业务处理结果不能影响业务主动方的处理,双方的职责是清晰的,例如:我们接入支付宝的支付功能,我方的身份即业务被动方,而支付宝方则为业务主动方。
我方接收到用户的支付请求,等待用户输入支付密码,用户支付确认后,我方向支付宝发起支付请求,同步返回给用户预支付结果。此时,用户看到支付处理中。
当支付宝侧执行转账完成之后(图中的箭头1),结果可以是成功/失败,总之主动方一定是在本地业务有确定的执行结果后,才会发起通知。
支付宝侧会通过某种机制(猜想是图中的消息队列机制)将通知请求扔到通知消息服务中,通知发送核心业务消费通知消息,并记录持久化消息到数据库中,发送通知消息给我方。
我方支付回调接口收到支付完成通知后,会对参数进行签名校验,待签名通过后,取出业务参数,对这笔支付订单返回的结果进行后续操作(修改状态为下单完成并发货或者修改为支付失败,操作回滚/退款)。
这个过程理想状态下是很快的,由于支付宝侧强大的处理能力,我们几乎感觉不到处理中状态,但整个过程确实是异步的过程。
【这里给我们的启示之一便是,对于跨系统的交互,如果能够将同步的业务操作拆分为异步过程,能够大幅度提高业务的灵活度及吞吐量。】
这里存在一种普遍的情况,我们的系统处理能力是有限的,在收到通知后未能及时的处理完成,这时,双方会约定,如果收到通知且处理完成,业务被动方需要返回确定某个状态码,如:“success”,否则认为此次通知失败。
这样,只要我们处理完成就返回“success”,主动方就不会继续通知。否则,主动方会按照一定策略,比如“时间衰减策略”,对通知失败的请求从持久层中取出,比如:24小时内,按照间隔1min、5min、10min、30min、1h、2h、5h、10h的方式,逐步拉大通知间隔,直到达到通知要求的时间窗口上限。这时,就需要被动法主动发起查询。一般这种情况很少,而这么做的目的也是为了使最近的请求更快的被通知回去,我们认为,时间越靠后,通知成功的可能性越少,因为大多数的通知请求在第一次通知发起时就返回了成功success。
PS:这里还有一种措施,就是人工干预,重置通知位点。比如:如果达到最大通知次数依然没有通知成功,那么数据仍旧保存在通知库里面,运营人员在接收到商户的重复通知需求的时候,通过人工的操作,重新重置通知次数,这样消息就可以重新通知了。
这种情况往往是业务被动方未接入主动查询导致的,不建议频繁采用此方案,正常的流程时在开发阶段就强烈要求被动方将通知接口及查询接口均开发完成。
说多了都是泪,笔者曾经在工作中从事过一段时间商户接入工作,就遇到过这种类型的商户,经常要求我们为他们重新发起通知,对工作效率的影响真是肉眼可见的。
我们说回正题,在异常情况下,如果业务主动方在一定时间阈值内未能及时的发起通知,而作为业务被动方的我们又想及时获取到业务结果,这时就需要业务被动方主动发起查询,调用主动方暴露的查询接口获取业务结果,这里一般采用定时作业轮询,从而在业务上达到闭环, 最大可能的保证了数据的一致性。
3. 方案评价
- 方案成本:该方案成本较低,主要花费在业务查询与校对系统建设成本。校对系统是单独的,例如离线的文件对账方式,通过双方定时导出日订单,做线下的互相对账,从而实现平账操作。
- 适用范围:本方案适用于对时间敏感性较低的业务,比如充值、转账业务,在内部系统间就不适合该方式。多适用于跨系统、跨业务、跨网络的服务间数据一致性保证场景。
- 用到的服务模式为: 可查询操作,幂等操作等。
- 方案特点:
- 业务活动的主动方在完成业务处理后,向被动方发送通知消息(允许消息丢失);
- 主动方可以设置时间阶梯型通知规则,在通知失败后按规则重复发起通知,直到通知N次后不再通知
- 主动方提供校对查询接口给被动方,被动方按需校对查询,用于恢复丢失的业务消息
- 应用案例
- 支付渠道接入通知,银行转账通知、商户通知等
- 对账通知(实时/定时)
4. 实现方式
从本文开始的图中可以看出,通常采用两种方式实现
- 定时任务,使用定时作业轮询发起直接通知,通知数据要做持久化操作。几乎无外部依赖。
- 定时消息队列,基于定时消息发起通知,同样需要对通知请求数据做持久化操作,对消息队列有高可用需求。
5. 如何优化
- 在业务系统外增加业务管理系统,对通知失败数据进行管理,实现手动触发重复通知操作。
- 将通知服务通用化,框架化,剥离通知逻辑与业务逻辑, 对通知队列进行区分, 不同队列使用不同通知规则,并且支持通知策略的可配置性操作等。
- 保证通知服务的可用性,必要时建立独立的数据库,这里要注意对通知数据的持久化策略,以及通知失败后重新发起通知的策略的优化,比如时间衰减策略的设置等。这对通知的成功率和实效性有很大影响。
- 要求主动方提供的业务查询接口及被动方暴露的通知处理业务接口均要实现幂等性。
- 最后要注意,内存调优与流量控制,这里主要针对业务主动方,如果通知消息的生产方的速率与通知发送侧的消费方的消费速率不匹配,会导致大量消息驻留在消费端内存中,导致消息服务响应慢,甚至挂掉,从而影响到服务被动方进行业务后续处理,导致更大的商业纠纷等。(这里再插一句,考虑到极端情况,如果真的出现down机,在服务重启后,要能恢复现场,因此对通知消息的持久化操作就是必不可少的。当服务重启后,通知服务能够定位到上次的消费位点,继续通知操作)
6. 小结
本文对分布式事务解决方案的– 最大努力通知型 柔性解决方案进行了较为详细的讲解,结合实例及笔者的工作经历,对方案的各个细节展开讲解,希望能够对读者有所帮助。
后续笔者会着手开发一款分布式事务的轮子,入手点将采用本文提到的模式,并最终达到基本可用。算是立了一个flag吧,完成之后会第一时间发文出来。(又一个有生之年系列)
到目前为止,《我说分布式事务》 系列就过半了,之后我会对可靠消息一致性方案进行讲解,我们不见不散。