分布式事务-基于可靠消息的最终一致性

消息发送一致性(可靠消息的前提保障)

一、消息中间件的应用场景

消息中间件在分布式系统中的主要作用:异步通讯、解耦、并发缓冲
如图:通过引入消息中间件来解耦应用间(服务间)的直接调用,同时也会起到异步通讯和缓冲并发的作用
二、消息发送和投递的不可靠性

分布式部署环境下,需要通过网络进行通讯,就引入了数据传输的不确定性也就是CAP理论中的P(分区容错性的问题)

三、消息发送一致性
消息发送一致性:是指产生消息的业务动作与消息发送的一致。

(也就是说,如果业务操作成功,那么由这个业务操作所产生的消息一定要成功投递出去,否则就丢消息) 

四、消息发送一致性如何保障?
1. 处理方式1
/** 支付订单处理 **/
public void completeOrder() {
// 订单处理(业务操作)
orderBiz.process();
// 发送会记原始凭证消息(发送消息)
sendAccountingVoucherMsg ();
}
如果业务操作成功,执行消息发送前应用故障,消息发不出去,导致消息丢失(订单系统与会计系统的数据不一致)
如果业务操作成功,应用正常,但消息系统故障或网络故障,也会导致消息发不出去(订单系统与会计系统的数据不一致)
2. 处理方式2
/** 支付订单处理 **/
public void completeOrder() {
// 发送会记原始凭证消息(发送消息)
sendAccountingVoucherMsg ();
// 订单处理(业务操作)
orderBiz.process();
}
这种情况下,更不可控,消息发出去了,但业务可能会失败(订单系统与会计系统的数据不一致)
前面两种方式,都不能保证业务数据的一致性。

五、JMS标准中的XA协议方式是否可以保障发送一致性?
JMS协议标准的API中,有很多以XA开头的接口,其实就是前面课程讲到的支持XA协议(基于两阶段提交协议)的 全局事务型接口。JMS中的XA系列接口,可以提供分布式事务支持。 但引用了XA方式的分布式事务,又会带来很多的局限:

要求业务操作的资源必须支持XA协议(并不是所有资源都支持XA)
两阶段提交协议的成本
持久化成本等DTP模型的局限性(全局锁定,成本高,性能低)
引入XA,违背了柔性事务的初衷 

六、消息发送一致性:变通的做法

主动方应用先把消息发给消息中间件,消息状态标记为“待确认”;
消息中间件收到消息后,把消息持久化到消息存储中,但并不向被动方应用投递消息;
消息中间件返回消息持久化结果(成功/失败),主动方应用根据返回结果进行判断如何进行业务操作处理:
失败:放弃业务操作处理,结束(必要时向上层返回失败结果);
成功:执行业务操作处理;
业务操作完成后,把业务操作结果(成功/失败)发送给消息中间件;
消息中间件收到业务操作结果后,根据业务结果进行处理;
失败:删除消息存储中的消息,结束;
成功:更新消息存储中的消息状态为“待发送(可发送)”,紧接着执行消息投递;
前面的正向流程都成功后,向被动方应用投递消息;
七、待解决问题
消息发送一致性方案的正向流程是可行的,但异常流程怎么处理呢? 消息发送到消息中间件中能得到保障了,但消息的准确消费(投递)又如何保障呢? 有没有支持这种发送一致性流程的现成消息中间件?

一、消息发送一致性流程中的异常点

任何一个环节都可能会出问题!

二、消息发送一致性的异常情况分析


1.从主动方应用的角度来分析:
异常情况 可能的状态 一致性
预发送消息失败 消息未进存储,业务操作未执行(可能的原因:主动方应用、网络、消息中间件、消息存储) 一致                         
预发送消息后,主动方应用没有收到返回消息存储结果 (1)消息未进存储,业务操作未执行 一致
同上 (2)消息已进存储(待确认),业务操作未执行 不一致
收到消息存储成功的返回结果,但未执行业务操作就失败 消息已进存储(待确认),业务操作未执行 不一致

2.消息发送一致性的异常情况分析


异常情况 可能的状态 一致性     
消息中间件没有收到主动方应用的业务操作处理结果 (1)消息已进存储(待确认),业务操作未执行(或业务操作出错回滚了)
不一致              

同上 (2)消息已进存储(待确认),业务操作成功 不一致
消息中间件收到业务操作结果(成功/失败),但处理消息存储中的消息状态失败 (1)消息已进存储(待确认),业务操作未执行(或业务操作出错回滚了) 不一致                           
同上 (2)消息已进存储(待确认),业务操作成功 不一致

3.消息发送一致性的异常情况总结
异常情况 一致性 异常处理方法
消息未进存储,业务操作未执行 一致                         
消息已进存储(状态待确认),业务操作未执行 不一致 确认业务操作结果,处理消息(删除消息)
消息已进存储(状态待确认),业务操作成功 不一致 确认业务操作结果,处理消息(更新消息状态,执行消息投递)

4.消息发送一致性的异常处理

MQ队列消息模型的特点

队列消息模型的特点:

消息生产者将消息发送到Queue中,然后消息消费者监听Queue并接收消息;
消息被确认消费以后,就会从Queue中删除,所以消息消费者不会消费到已经被消费的消息;
Queue支持存在多个消费者,但是对某一个消息而言,只会有一个消费者成功消费。
常用的MQ中间件产品 ActiveMQ、RabbitMQ、RocketMQ等
基本都是这样的流程,具体实现上有各自的差异。规范协议
实现上有JMS、AMQP或自定义规范等。 
Producer生成消息并发送给MQ(同步、异步);
MQ接收消息并将消息数据持久化到消息存储(持久化操作为可选配置);
MQ向Producer返回消息的接收结果(返回值、异常);
Consumer监听并消费MQ中的消息;
Consumer获取到消息后执行业务处理;
Consumer对已成功消费的消息向MQ进行ACK确认(确认后的消息将从MQ中删除)
与消息发送一致性流程的对比
常规MQ队列消息的处理流程无法实现消息发送一致性;
投递消息的流程其实就是消息的消费流程,可细化。
总结
常规MQ队列消息的处理流程无法实现消息发送一致性,因此直接使用现成的MQ中间件产品无法实现可靠消息最终一致性的分布式事务解决方案

幂等性设计 

消息的消费确认流程中,任何一个环节都可能会出问题!

方法:对于未确认的消息,采用按规则重新投递的方式进行处理。
问题:消息的重复发送会导致业务处理接口出现重复调用的问题。
消息重复发送的原因
被动方应用接收到消息,业务处理完成后应用出问题,消息中间件不知道消息处理结果,会重新投递消息。
被动方应用接收到消息,业务处理完成后网络出问题,消息中间件收不到消息处理结果,会重新投递消息。
被动方应用接收到消息,业务处理时间过长,消息中间件因消息超时未确认,会再次投递消息。
被动方应用接收到消息,业务处理完成,消息中间件问题导致收不到消息处理结果,消息会重新投递。
被动方应用接收到消息,业务处理完成,消息中间件收到了消息处理结果,但由于消息存储故障导致消息没能成功确认,消息会再次投递。
总结
消息消费过程中产生消息重复发送主要是因为消息接收者成功处理完消息后,消息中间件没能及时更新消息投递状态(也就是消息没能及时ACK确认)导致的。

约束:被动方应用对于消息的业务处理要实现幂等

业务接口的幂等性设计
对于存在同一请求数据会发生重复调用的业务接口,接口的业务逻辑要实现幂等性设计。 在实际的业务应用场景中,业务接口的幂等性设计,常结合可查询操作一起使用。 场景举例

支付订单创建:商户编号 + 商户订单号 + 订单状态
订单更新处理:平台订单号 + 订单状态
会计系统记账:系统来源 + 请求号
极端情况:消息重发也得有次数限制,要不然就变成了死循环。
对于超过重发次限制的消息,进入DLQ,等待人工干预或延后定期处理
方式
根据业务来实现幂等
增加消息表来实现幂等

 

面临的问题
通过前面文章的分析可以得到一下结论

现成的MQ中间件产品不支持消息发送一致性流程(先进存储,在被确认后才能发送的2步流程)
直接改造或者开发MQ中间件的难度大
基于本地消息服务的设计


实现思路
主动方应用系统加入消息存储模块。即业务操作和消息存储与发送在同一个本地事务中。先将需要发送的消息存储在本地数据库,设置其状态为代发送
将消息数据中为待发送的消息发送到MQ,MQ 推送到其他应用系统处理
其他应用系统处理结束后回调主动发系统并修改消息的状态或者删除消息
消息恢复系统:用来查询那些超时未处理状态的消息并设置延时发送
优点
1.消息时效性比较高 2.从应用设计开发的角度实现了消息数据的可靠性,消息数据的可靠性不依赖于MQ中间件,弱化了对MQ中间件特性的依赖 3.方案轻量,容易实现

独立消息服务的设计


实现思路
与上面的方案相比独立实现了消息服务子系统

主动发应用发送预发送消息,消息服务子系统 存储预发送消息状态未预发送,并返回操作结果

主动发应用在预发送消息成功的前提下开始进行业务操作

主动发应用发送业务操作结果给消息系统

消息系统确认并发送消息,并设置消息状态为发送中

MQ将消息转发到其他业务系统

消息状态确认子系统 查询消息服务子系统中状态还是预发送的超时消息,并查询此消息在主动方应用系统的处理情况。如果主动发业务操作失败则删除该消息。如果主动方业务操作成功则修改状态为待发送,且将信息发送到MQ

消息恢复系统:用来查询那些超时未处理状态的消息并设置延时发送

优点:
消息服务独立部署、独立维护、独立伸缩
消息存储可以按需选择不同的数据库来集成实现
消息服务可以被相同的使用场景公用,降低重复建设消息服务的成本
从应用(分布式服务)设计开发的角度实现了消息数据的可靠性,消息数据的可靠性不依赖与MQ中间件,弱化了对MQ中间件特性的依赖
降低了业务系统与消息系统间的耦合,有利于系统的扩展维护
可靠消息最终一致性方案的优化
数据库: Redis(可靠性、可用性、性能)
特别注意持久化的配置 appendfsynnc always 只要有新添加的数据就将数据缓存同步到磁盘
消息日志表:适用于被动方应用业务的幂等性判断比较麻烦或比较消耗性能的情况,但会增加一定的开发工作量等
分布式调度
独立业务使用独立消息服务(提高性能、隔离解耦、但增加维护成本和工作量)
弊端/局限
一次消息发送需要两次请求
主动发应用系统需要实现业务操作状态校验接口

posted @ 2021-03-01 21:59  hanease  阅读(217)  评论(0编辑  收藏  举报