分布式事务解决方案(二)【基于可靠消息的最终一致性】

2. 最终一致性(基于可靠消息)

2.1 消息发送的一致性

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

2.1.1 如何保障消息发送一致性

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

2.1.2 正向流程

image

  1. 主动方应用先把消息发给消息中间件,消息状态标记为“待确认”;
  2. 消息中间件收到消息后,把消息持久化到消息存储中,但并不向被动方应用投递消息;
  3. 消息中间件返回消息持久化结果(成功/失败),主动方应用根据返回结果进行判断如何进行业务操作处理:
    • 失败:放弃业务操作处理,结束(必要时向上层返回失败结果);
    • 成功:执行业务操作处理;
  4. 业务操作完成后,把业务操作结果(成功/失败)发送给消息中间件;
  5. 消息中间件收到业务操作结果后,根据业务结果进行处理;
    • 失败:删除消息存储中的消息,结束;
    • 成功:更新消息存储中的消息状态为“待发送(可发送)”,紧接着执行消息投递;
  6. 前面的正向流程都成功后,向被动方应用投递消息

2.1.3 异常流程

  • 异常点分析(任何一个环节都可能会出问题)

image

  • 从主动方应用的角度Fenix

image

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

- 从消息中间件的角度来分析

image

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

- 总结

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

2.2 消息消费一致性

  • 消息消费流程

image

2.2.1 消息消费流程的异常点

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

image

2.2.2 消息消费流程异常处理

2.3 常规MQ处理流程

image

  1. Producer生成消息并发送给MQ(同步、异步);
  2. MQ接收消息并将消息数据持久化到消息存储(持久化操作为可选配置);
  3. MQ向Producer返回消息的接收结果(返回值、异常);
  4. Consumer监听并消费MQ中的消息;
  5. Consumer获取到消息后执行业务处理;
  6. Consumer对已成功消费的消息向MQ进行ACK确认(确认后的消息将从MQ中删除)。

    • 队列消息模型的特点:

      1. 消息生产者将消息发送到Queue中,然后消息消费者监听Queue并接收消息;
      2. 消息被确认消费以后,就会从Queue中删除,所以消息消费者不会消费到已经被消费的消息;
      3. Queue支持存在多个消费者,但是对某一个消息而言,只会有一个消费者成功消费。
    • 常规MQ队列消息的处理流程无法实现消息发送一致性;

    • 投递消息的流程其实就是消息的消费流程,可细化。
    • 解决方案如下

image

常规MQ队列消息的处理流程无法实现消息发送一致性,因此直接使用现成的MQ中间件产品无法实现可靠消息最终一致性的分布式事务解决方案。

2.3 消息幂等性

2.3.2 消息重复发送的原因

  1. 被动方应用接收到消息,业务处理完成后应用出问题,消息中间件不知道消息处理结果,会重新投递消息。
  2. 被动方应用接收到消息,业务处理完成后网络出问题,消息中间件收不到消息处理结果,会重新投递消息。
  3. 被动方应用接收到消息,业务处理时间过长,消息中间件因消息超时未确认,会再次投递消息。
  4. 被动方应用接收到消息,业务处理完成,消息中间件问题导致收不到消息处理结果,消息会重新投递。
  5. 被动方应用接收到消息,业务处理完成,消息中间件收到了消息处理结果,但由于消息存储故障导致消息没能成功确认,消息会再次投递。

2.3.3 业务接口的幂等性设计

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

  • 对于存在同一请求数据会发生重复调用的业务接口,接口的业务逻辑要实现幂等性设计。
  • 在实际的业务应用场景中,业务接口的幂等性设计,常结合可查询操作一起使用。
    • 支付订单创建:商户编号 + 商户订单号 + 订单状态
    • 订单更新处理:平台订单号 + 订单状态
    • 会计系统记账:系统来源 + 请求号

2.4 方案一:本地消息服务的设计

2.4.1 面临问题

  1. 现成MQ中间件不支持消息发送的一致性
  2. 直接改造MQ中间件难度很大
  3. 有什么变通的实现方式?

2.4.2 主要流程

image

  1. 消息存储与业务存储在同一个本地事务中进行,消息存储后设置为待确认状态,并异步将消息发送(注意需要异步发送消息,不要影响主流程).
  2. 通过一定策略不断将待确认的消息重新发送.
  3. 业务方回收到消息,成功处理业务,并持久化完成后调主动方接口,通知主动方此消息已经处理完成,主动方将数据库中消息状态改为已发送.
  4. 实现一个消息管理系统,手动处理多次重发失败已死亡的消息.

2.4.3 优势

  1. 消息实时性较高
  2. 从应用设计开发的角度实现了消息数据的可靠性,消息数据的可靠性不依赖于MQ中间件,弱化了对MQ中间件的依赖
  3. 方案轻量容易实现

2.4.4 劣势

  1. 业务绑定,耦合性强,不通用
  2. 消息数据与业务数据同库,占用业务系统资源
  3. 业务系统在使用关系型数据库的情况下,消息服务性能会受到关系型数据库并发性能的局限

2.5 方案二:独立消息服务

2.5.1 面临问题

  1. 现成MQ中间件不支持消息发送的一致性
  2. 直接改造MQ中间件难度很大
  3. 有什么变通的实现方式?

2.5.2 主要流程

image

  1. 存储预发送消息(主动方业务执行之前进行,预发送的消息存储后状态为待确认)
  2. 确认并发送消息(主动方业务完成之后,主动方或消息状态确认系统通过此接口将消息变为取消或发送中)
  3. 查询状态确认超时的消息(消息状态确认系统使用)
  4. 确认消息已被成功消费(被动方业务执行完成之后调用)
  5. 查询消费确认超时的信息

2.5.3 优势

  1. 消息服务独立部署,独立维护,独立伸缩
  2. 消息存储可以按需选择不同的数据库来集成实现
  3. 消息服务可以被相同的使用场景共用,降低消息重复建设消息服务的成本
  4. 从应用设计开发的角度实现了消息数据的可靠性,消息数据的可靠性不依赖于MQ中间件,弱化了MQ中间件特性的依赖
  5. 降低了业务系统与系统间的耦合,有利于系统的扩展维护

2.5.4 劣势

  1. 一次消息需要发送两次请求
  2. 主动方应用系统需要实现业务操作状态校验查询接口

猜你喜欢

转载自blog.csdn.net/WuLex/article/details/80938711