可靠消息最终一致性

理解可靠消息最终一致性事务

可靠消息最终一致性方案是指事务发起方执行完成本地事务后并发出一条消息,事务参与者(消息消费者)一定能够接收消息并处理事务成功,此方案强调的是只要消息发给事务参与方最终事务要达到一致。

此方案是利用消息中间件完成。事务发起方(消息生产方)将消息发给消息中间件,事务参与方从消息中间件接收消息,事务发起方和消息中间件之间,事务参与方(消息消费方)和消息中间件之间都是通过网络通信,由于网络通信的不确定性会导致分布式事务问题。

因此可靠消息最终一致性方案要解决以下几个问题:

  1. 本地事务与消息发送的原子性问题

    本地事务与消息发送的原子性问题即:事务发起方在本地事务执行成功后消息必须发出去,否则就丢弃消息。即实现本地事务和消息发送的原子性,要么都成功,要么都失败。本地事务与消息发送的原子性问题是实现可靠消息最终一致性方案的关键问题。

  2. 事务参与方接收消息的可靠性

    事务参与方必须能够从消息队列接收到消息,如果接收消息失败可以重复接收消息

  3. 消息重复消费问题

    由于网络2的存在,若某一个消费节点超时但是消费成功,此时消息中间件会重复投递此消息,就导致了消息的重复消费。

    要解决消息重复消费的问题就要实现事务参与方的方法幂等性。

解决方案

本地消息表方案

本地消息表这个方案最初是eBay提出的,此方案的核心是通过本地事务保证数据业务操作和消息的一致性,然后通过定时任务将消息发送至消息中间件,待确认消息发送给消费方成功再将消息删除。

以注册送积分为例。

交互流程:

  1. 用户注册

    begin Transaction;
    	// 1.注册用户
    	// 2.存储积分消息日志
    commit Transaction;
    

    本地数据库操作与存储积分消息日志处于同一个事务中,本地数据库操作与记录日志操作具备原子性。

  2. 定时任务扫描日志

    可以启动独立的线程,定时对消息日志表中的消息进行扫描并发送至消息中间件,再消息中间件反馈发送成功后删除该消息日志,否则等待定时任务下一周期重试。

  3. 消费消息

    可以使用MQ的ack(即消息确认机制),消费者监听MQ,如果消费者接收到消息并且业务处理完成后向MQ发送ack(即消息确认),此时说明消费者正常消费消息完成,MQ将不再向消费者推送消息,否则消费者会不断重试向消费者来发送消息。

    积分服务接收到“增加积分”消息,开始增加积分,积分增加成功后向消息中间件回应ack,否则消息中间件将重复投递此消息。

    由于消息会重复投递,积分服务的“增加积分”功能需要实现幂等。

RocketMQ事务消息方案

RocketMQ事务消息设计主要是为了解决Producer端的消息发送与本地事务执行的原子性问题。RcoketMQ的设计中broker与producer端的双向通信能力,使得broker天生可以作为一个事务协调者存在;而RocketMQ本身提供的存储机制为事务消息提供了持久化能力;RocketMQ的高可用机制以及可靠消息设计则为事务消息在系统发生异常时仍然能够保证达成事务的最终一致性。

在RocketMQ4.3后实现了完整的事务消息,实际上其实是对本地消息表的一个封装,将本地消息表移动到了MQ内部,解决Producer端的消息发送与本地事务执行的原子性问题。

执行流程:

  1. Producer 发送事务消息

    Producer(MQ发送方)发送事务消息至MQ Server,MQ Server将消息状态标记为Prepared(预备状态),注意此时这条消息消费者(MQ订阅方)是无法消费到的。

  2. MQ Server 回应消息发送成功

    MQ Server接收到Producer发送的消息则回应发送成功表示MQ已接收到消息。

  3. Producer执行本地事务

    Producer端执行业务代码逻辑,通过本地数据库事务控制。

  4. 消息投递

    • 若Producer本地事务执行成功则自动向MQ Server发送commit消息,MQ Server接收到commit消息后将“增加积分信息”状态标记为可消费,此时MQ订阅方(积分服务)即正常消费消息;
    • 若Producer本地事务执行失败则自动向MQ Server发送Rollback消息,MQ Server接收到rollback消息后,将删除“增加积分消息”。
    • MQ订阅方(积分服务)消费消息,消费成功则向MQ回应Ack,否则将重复接收消息。这里ack默认自动开启。
  5. 事务回查

    如果执行Producer端本地事务过程中,执行端挂掉,或者超时,MQ Server将会不停的询问同组的其它Producer来获取事务执行状态,这个过程叫事务回查。MQ Server会根据事务回查结果来决定是否投递消息。以上主干流程已由RocketMQ实现,对用户侧来说,用户需要分别实现本地事务执行以及本地事务回查方法,因此只需关注本地事务的执行状态即可。

    RocketMQ 提供 RocketMQLocalTransactionListerner接口

    public interface RocketMQLocalTransactionListener {
        /**
        - 发送prepare消息成功此方法被回调,该方法用于执行本地事务
        - @param msg 回传的消息,利用transactionId即可获取到该消息的唯一Id
        - @param arg 调用send方法时传递的参数,当send时候若有额外的参数可以传递到send方法中,这里能获取		到
        - @return 返回事务状态,COMMIT:提交 ROLLBACK:回滚 UNKNOW:回调
        */
        RocketMQLocalTransactionState executeLocalTransaction(final Message msg, final Object arg);
    
        /**
        - @param msg 通过获取transactionId来判断这条消息的本地事务执行状态
        - @return 返回事务状态,COMMIT:提交 ROLLBACK:回滚 UNKNOW:回调
        */
        RocketMQLocalTransactionState checkLocalTransaction(final Message msg);
    }
    

可靠消息最终一致性就是保证消息从生产方经过消息中间件传递到消费方的一致性,RocketMQ主要解决了两个问题:

  1. 本地事务与消息发送的原子性问题
  2. 事务参与方接收消息的可靠性。

可靠消息最终一致性事务适合执行周期长且实时性要求不高的场景。引入消息机制后,同步的事务操作变为基于消息执行的异步操作,避免了分布式事务中的同步阻塞操作的影响,并实现了两个服务的解耦。

posted @ 2022-04-24 20:14  fjhnb  阅读(657)  评论(0)    收藏  举报