rocketmq的分布式事务消息分析
简单介绍
RocketMQ是阿里巴巴在2012年开源的分布式消息中间件。 目前已经捐赠给apache。
剩下的不吹了。。百度上都查得到。。
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消费。
简单阅读了源码后, 画了个稍稍详细点的图
有许多细节并没有画在里面, 比如 步骤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 是最终对消息进行发送的刷盘处理。
画了如下的图 可能会更好的理解一点。 并把比较重要的部分(个人认为) 列在了图中。
-
当然上图只是代码中相对理想的部分,许多细节还没有仔细的品味, 比如各类的验证和容错处理、 消息的包装的设计, 每一步产生错误都会走向其他的容错机制。
-
其实不管是普通的消息(同步sync、异步async、单向one-way)还是事务消息, 代码的复用性很高。
*同步、异步、单向消息在前期的处理几乎一致。
*发送事务的消息其实就是复用了同步sync的消息代码, 只是在基础上打了一层事务的标签(MessageAccessor.putProperty()). -
最后分叉处理的时候也只是稍微有些不同, 异步的消息封装使用一个回调方法确保最后消息的处理, 同步消息则直接等待结果, 单向消息则直接return;
-
当然这只是整个producer的冰山一角, 外部项目还有broker(真正的mq服务器、中间件)、consume, 以及互相之间的协调, 都是非常值得研究的。