启发:从MNS事务消息谈分布式事务

启发:从MNS事务消息谈分布式事务

事务消息本质上解决的问题是业务系统与消息系统之间的事务问题(跨系统分布式事务),其基本原理即两阶段提交以及最终一致性保障。最近看了下阿里云mns事务消息的实现原理,介绍的蛮简洁透彻的,对了解分布式事务实现原理挺有帮助,在阅读本文前推荐你们先仔细阅读下阿里云"mns事务消息"一文。php

事务消息


背景描述

有时候咱们须要实现本地操做和消息发送的事务一致性功能。即:消息发送成功,则本地操做成功;反之,若是消息发送失败,本地操做失败(成功也须要rollback)。保证不出现操做成功但消息发送失败;或者操做失败但消息发送成功的状况;
另外,消费端,咱们也但愿消息必定被成功处理一次,不会由于消息端程序崩溃而致使消息没有成功处理,进而须要人工重置消费进度。html

解决方案

利用消息服务MNS的延迟消息功能来实现。mysql

准备工做

建立两个队列:

  • 1.事务消息队列sql

    消息的有效期小于消息延迟时间。即若是生产者不主动修改(提交)消息可见时间,消息对消费者不可见;
  • 2.操做日志队列网络

    记录事务消息的操做记录信息。消息延迟时间为事务操做超时时间。日志队列中的消息确认(删除)后将对消费者不可见。

    <!-- more -->分布式

具体步骤

  • 1.发送一条事务准备消息到事务消息队列;
  • 2.写操做日志信息到操做日志队列,日志中包含步骤1消息的消息句柄;
  • 3.执行本地事务操做;
  • 4.若是步骤3成功,提交消息(消息对消费者可见);反之,回滚消息;
  • 5.确认步骤2中的操做日志(删除该日志消息);
  • 6.步骤4后,消费者能够接收到事务消息;
  • 7.消费者处理消息;
  • 8.消费者确认删除消息;
    以下图:

事务

异常分析:

生产者异常(例如:进程重启):

A.读取操做日志队列超时未确认日志
B.检查事务结果
C.若是检查获得事务已经成功,则提交消息(重复提交无反作用,同一句柄的消息只能成功提交一次)
D.确认操做日志阿里云

消费者异常(例如:进程重启):

消息服务提供至少保证消费一次的特性,只要步骤8不成功,消息在一段时间后能够继续可见,被当前消费者或者其余消费者处理。spa

消息服务不可达(例如:断网)

消息发送和接收处理状态以及操做日志都在消息服务端,消息服务自己具有高可靠和高可用的特色,因此只要网络恢复,事务能够继续,能保证只要生产者:操做成功,则消费者必定可以拿到消息并处理成功;或操做失败, 则消费者收不到消息的最终一致性。.net

原文地址日志

在mns消息模型中两阶段提交的体现是:

  • 1.在执行事务前先preSendMessage:其背后的原理是建立一个delay message,可是这个delay message的delaytime > lifetime, 基于这个前提在获得确切的commit/rollback操做前,这个消息对于接受者是永远不可见的;
  • 2.本地事务结束后commit/rollback message:若是本地事务提交成功,须要将以前提交的delay message设置为消费者可见(底层实现应该与将delay变为0相似);对应的若是本地事务提交失败,须要将以前的delay message删除;

这个过程须要注意到,咱们务必保证在preSendMessage没获得最终确认以前不被消费者获取到,所以须要将发送的lifetime小于delaytime。

看到这里也许你有疑问,为何要将过程切分红两阶段提交?咱们先假设若是采用一次提交的策略,很显然此次提交的切入点只能存在于①本地事务开始以前②本地事务中③本地事务结束以后,那么先看这三个切入点各自存在什么问题。

  • ①本地事务开始以前提交消息:在本地事务未完成以前,消息的消费者读取到了message,若是消费者后续的服务调用中存在对该次本地事务提交有依赖,那必然致使数据不一致问题;若是本地事务的执行结果是失败的,却通知了消费者,很显然会致使不可预期的数据错误。
  • ②本地事务中:在本地事务中提交消息一样会存在①中的问题,即使sendmessage是在本地事务的最后执行,由于事务的提交和消息被接受到的时序是没法保证的;
  • ③本地事务结束以后:不一样于①②两个提交点,本地事务完成以后咱们可以明确的知道本地事务的执行结果,所以可以确保事务提交(回滚)与消息被接受是有序的;然而若是消息没有被成功发送消费者接受不到消息,而本地事务却获得了正确执行,这就致使了数据不一致问题,而且若是没有操做日志,这个问题将变得难以追溯;

”单次提交“遇到的主要问题是:没法保障本地事务与消息被接受到的时序问题(或者说两个分布式事务的时序)以及数据的一致性问题。再回到”两阶段提交“,两阶段提交能解决这两个问题吗?两阶段提交的确认操做是在本地事务完成以后(这个相似于③),所以其可以解决时序问题,可是若是这个确认操做执行的过程当中发生了宕机等状况致使确认操做失败,依然会致使数据不一致问题。

在mns事务消息中最终一致性的实现:

mns经过延迟消息机制实现了两阶段提交,其如何保证数据一致性问题呢?通常咱们的策略都是经过操做流水来进行补偿以达到数据的最终一致性,一样的mns也是基于这个原理实现。

  • 在preSendMessage以后,mns会在日志队列中记录一条opLog(opLog经过记录preSendMessage的receipthandle来进行关联),而且将这个opLog的delayTime设置为事务的超时时间;
  • 当本地事务执行结束,而且preSendMessage被commit/rollback以后,再将这条opLog删除;
  • 同时存在一个任务监听日志队列,当接收到opLog的消息,检查对应的preSendMessage相关联的本地事务是否执行成功。若是本地事务执行成功,则经过opLog中保存的receipthandle补偿一次对preSendMessage的commit操做,若是checker发现本地事务执行失败,那对应的补偿一次rollback操做;

经过创建对opLog的监听,咱们可以确保事务的最终一致性吗?回答这个问题前,咱们先看这个问题的本质:最终、一致性。
最终一致性问题的产生是因为发生了一些不可预期的问题,致使一个事务被提交(回滚),但消息没被commit(rollback)。咱们经过opLog来追溯那些没有获得最终确认的消息并进行补偿(最终),而且经过检查本地事务的状态来确认此次补偿是commit或者是rollback(一致性)。正是基于这个补偿的策略,mns事务消息解决了"两阶段提交"所遗留的一致性问题,但这个过程当中咱们须要注意几个细节:

  • 补偿策略执行的时候须要明确知道本地事务的执行结果,所以咱们的本地事务中须要记录preSendMessage所关联的本地事务操做结果。咱们的作法是本地事务中同时记录下preSendMessage的receipthandle, 当补偿任务执行的时候,会经过opLog关联的receipthandle来检查,若是没有找到相关记录,那认为以前的本地事务被rollback了,不然commit;
  • MNS如何创建了preSendMessage<=>Local Transaction<=>opLog之间的关联关系?最简单的实现确定是经过preSendMessage的MessageId来实现,不过mns经过preSendMessage的receipthandle来创建了这个关联(ReceiptHandle含义)同时避免了额外的存储;
  • mns的补偿机制创建在对opLog的监听,那么咱们怎么肯定一个补偿的执行时机是合适的呢?补偿必定要在事务有明确结果以后执行才有意义,那么何时能获得明确的事务执行结果?其实咱们是没法确切的知道这个时间点的,但咱们可以有一个最低指望时间:无论一个事务成功或者失败,它的周期都不能超过事务的超时时间。所以咱们在发送opLog时须要设置opLog的delayTime>TransactionTimeout(如何确认transactionTimeout)来保证补偿任务执行的时候本地事务必定执行完成。

从mns事务消息到分布式事务的启发

上面啰嗦的写了一堆,看到这咱们不妨对思考下mns事务消息解决的是业务系统(本地事务)与消息中间件之间的事务协同问题,若是是两个业务系统之间的分布式事务如何实现?
好吧,若是坚持看到这,你可能以为我标题党了...那么我建议你再读一下”mns事务消息“一文。

更多文章请访问个人博客转载请注明出处

相关文章
相关标签/搜索