rocketmq事务消息源码分析 (producer篇)

rocketmq的分布式事务消息分析

简单介绍

RocketMQ是阿里巴巴在2012年开源的分布式消息中间件。 目前已经捐赠给apache。
剩下的不吹了。。百度上都查得到。。

doc: doc地址
源码具体地址: github地址

tcc事务

  • 现在都在讲微服务, 把每个模块单独的拆出去对外提供服务。
  • 虽然减少了许多维护成本和冲突成本。 (之前几十个人维护一个项目的时候,动不动就冲突,一改东西就能让几十个人精神紧张,改动一个分号没准那边的小哥就炸毛了。 上线的时候打包慢, 而且全员都要在生怕出问题。 拆分了之后自己玩自己的, 自己维护自己的,特别舒服)
  • 但是 额外增加的不仅仅是通信的负载压力和延迟, 更多的是这种特殊业务场景导致的更严重的问题。 比如 多服务调用导致的事务问题。

TCC :
是 try-confirm-cancel三个词, 由于涉及到多个服务(可以简单认为一个服务一个库)之间的事务, 使用常规的事务 就没有作用了(咋可能去锁别人的库, 如果锁人家的库的话。。。拆分服务的意义是啥???)

  • try 就是尝试去做一些处理, 可以理解为做一些校验、 锁库存(select column from table for update);
  • confirm 是同时去提交, 因为涉及到多个库, 所以只要任意一个库出现问题,则接受异常 进入cancel阶段
  • cancel 就是失败后的一些处理, 一般直接回滚了。

当然比tcc的严格的实现有许多, 比如2pc, 3pc。 mysql的xa(不要用,超级超级慢) , 一般除了严格要求同步处理的, 比如扣款类的问题(同步处理就是慢,但是准确), 都会选择基于 tcc补偿的最终一致性方案。

tcc方案
既然是最终一致性,当然不会是直接同步处理。

  • 大多选择基于第三方中间件(消息队列居多), 一方面解耦 不需要互相关注具体的问题, 只要确保消息达到即可。
  • 比较麻烦的就是, 在确保发送消息 需要消息生产者去出方案做。
  • 而重复消费导致的幂等性问题,消费过后保证的通知 一般都需要消费者去手动做。

rocketmq在4.*版本推出了事务, 把确保消息发送方面 做在了中间件里

事务流程图

首先上图:
~~ (网上查的) ~~
流程图
具体的实现大概是上图的样子。

具体的分类其实是分为producer(和client包在一块), mq服务端是broker。 通过注册到namesrc上。 最终将消息发送给consumer消费。

简单阅读了源码后, 画了个稍稍详细点的图
rocketmq事务时许图
有许多细节并没有画在里面, 比如 步骤2(implements transaction interface) 是需要实现 rocketmq提供的接口TransactionListener, 而这个listener会在后边也起到很大作用。
简单看一下定义

package ...
import ...

public interface TransactionListener {
    LocalTransactionState executeLocalTransaction(Message var1, Object var2);

    LocalTransactionState checkLocalTransaction(MessageExt var1);
}

需要我们自行实现该接口, 也是在executeLocalTransaction方法中 实现我们自己的本地事务处理逻辑。
checkLocalTransaction是broker后续调用回查本地事务处理结果的方法。(超时了之后还未发送结果,会回调该函数)

从使用上来讲, 只要我们自己实现TransactionListener就可以使用 rocketmq提供的事务操作了,非常的简便。

结合源码和图片的解释

首先看官方给的demo: 稍微简化了一点
// 实现事务接口
TransactionListener transactionListener = new TransactionListenerImpl();
// new 一个生产者
TransactionMQProducer producer = new TransactionMQProducer("please_rename_unique_group_name");
// 线程池
ExecutorService executorService = new ThreadPoolExecutor(2, 5, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(2000), new ThreadFactory() {
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                thread.setName("client-transaction-msg-check-thread");
                return thread;
            }
        });
producer.setExecutorService(executorService);
// 设置listener -- 这一步很有用
producer.setTransactionListener(transactionListener);
// 生产者启动
producer.start();
try {
    // new一个消息
	Message msg = new Message("TopicTest", "Tag", "keys", ("this is message body" + 1).getBytes(RemotingHelper.DEFAULT_CHARSET));
	// 真正的发送事务消息
	SendResult sendResult = producer.sendMessageInTransaction(msg, null);
} catch (MQClientException e) {
    e.printStackTrace();
} catch (UnsupportedEncodingException e) {
    e.printStackTrace();
} finally {
    producer.shutdown();
}

上图中比较重要的两点,

  • 第一点是
TransactionListener transactionListener = new TransactionListenerImpl();
...
producer.setTransactionListener(transactionListener);

创建listener, TransactionListener是rocket提供的一个本地事务的监听接口, 在上述的时序图中 主要在 9 - 11步中起作用, 事务执行成功才会发送commit消息, 事务出现问题则会发送rollback. 该接口需要自己根据自己的情况手动实现。

import ...
public interface TransactionListener {
    LocalTransactionState executeLocalTransaction(Message var1, Object var2);

    LocalTransactionState checkLocalTransaction(MessageExt var1);
}
  • 第二点是
SendResult sendResult = producer.sendMessageInTransaction(msg, null);

是发送事务消息真正的入口。

从入口开始, 整个发送消息的
整个链路的大致为

  • TransactionMQProducer
    *1.sendMessageInTransaction(Message, Object)
  • DefaultMQProducerImpl
    *2. sendMessageInTransaction(Message, TransactionListener, Object)
    *3. send(Message, long);
    *4. sendDefaultImpl(Message, CommunicationMode, SendCallback, long)
    *5. sendKernelImpl(Message, MessageQueue, CommunicationMode, SendCallback, TopicPublishInfo, long)
  • MQClientAPIImpl
    *6. sendMessage(String, String, Message, SendMessageRequestHeader, long, CommunicationMode, SendMessageContext, DefaultMQProducerImpl)
    *7. sendMessage(String, String, Message, SendMessageRequestHeader, long, CommunicationMode, SendCallback, TopicPublishInfo, MQClientInstance, int, SendMessageContext, DefaultMQProducerImpl)
  • NettyRemotingClient
    *8. invoke*(…)
    *9. invoke*Impl(…)
  • AbstractChannel
    *10.writeAndFlush(Object)

大概分10步,
2 sendMessageInTransaction 是整个事务消息的逻辑总管,
4 sendDefaultImpl 处理了消息的发送的包装,涉及到超时的判断、重试机制
5 sendKernelImpl 进行了消息的包装, 标志了消息的事务性质
10 writeAndFlush 是最终对消息进行发送的刷盘处理。

画了如下的图 可能会更好的理解一点。 并把比较重要的部分(个人认为) 列在了图中。

rocketmq producer源码调用图

  • 当然上图只是代码中相对理想的部分,许多细节还没有仔细的品味, 比如各类的验证和容错处理、 消息的包装的设计, 每一步产生错误都会走向其他的容错机制。

  • 其实不管是普通的消息(同步sync、异步async、单向one-way)还是事务消息, 代码的复用性很高。
    *同步、异步、单向消息在前期的处理几乎一致。
    *发送事务的消息其实就是复用了同步sync的消息代码, 只是在基础上打了一层事务的标签(MessageAccessor.putProperty()).

  • 最后分叉处理的时候也只是稍微有些不同, 异步的消息封装使用一个回调方法确保最后消息的处理, 同步消息则直接等待结果, 单向消息则直接return;

  • 当然这只是整个producer的冰山一角, 外部项目还有broker(真正的mq服务器、中间件)、consume, 以及互相之间的协调, 都是非常值得研究的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值