RocketMq(四) -- 使用问题
保证消息顺序
生产者:保证消息发送前后顺序,选择固定的queue
//根据hashcode求余选择 extends SelectMessageQueueByHash //extends SelectMessageQueueByRandom 随机选择
消费者:串行消费,消费者会把消息放到本地队列并加锁,定时任务保证锁同步
//串行消费
implements MessageListenerOrderly
消息重复消费
重复消费原因:
重复发送:消息发送后返回异常,再次发送导致mq有两条messageid一样的消息
重复消费:消费者已经消费到消息,但是发送给mq的应答失败。mq会再一起发送这条消息。
办法:
消费者接口设计幂等,支持多次消费消息
分布式锁,redis
事务消息 - 执行流程
1. producer使用TransactionMQProducer发送一条half消息给mq, mq将这条消息存在半消息队列:RMQ_SYS_TRANS_HALF_TOPIC
2. mq返回确认消息给producer
3. 如果成功,producer会执行本地事务操作,本地操作成功后,发送COMMIT给mq
4. mq接受到producer的COMMIT后,把topic换成producer设置的topic, 这就表示消息发送成功
5. mq发送消息给consumer,
6. comsumer消费后 发消息给mq,表示消费成功。
7. 如果消息没有消费成功,mq会再次发送消息。因而消费者要做好幂等和redis校验。
producer代码:
package com.jason.base.rocketmq; /** * 事务消息 * * @author:heyaolei * @create: 2023-03-15 */ import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.LocalTransactionState; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.client.producer.TransactionListener; import org.apache.rocketmq.client.producer.TransactionMQProducer; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.remoting.common.RemotingHelper; import java.io.IOException; /** * 事务消息生产者 */ public class TransactionMessageProducer { /** * 事务消息监听实现 */ private final static TransactionListener transactionListenerImpl = new TransactionListener() { /** * 在发送消息成功时执行本地事务 * @param msg * @param arg producer.sendMessageInTransaction的第二个参数 * @return 返回事务状态 * LocalTransactionState.COMMIT_MESSAGE:提交事务,提交后broker才允许消费者使用 * LocalTransactionState.RollbackTransaction:回滚事务,回滚后消息将被删除,并且不允许别消费 * LocalTransactionState.Unknown:中间状态,表示MQ需要核对,以确定状态 */ @Override public LocalTransactionState executeLocalTransaction(Message msg, Object arg) { // TODO 开启本地事务(实际就是我们的jdbc操作) // TODO 执行业务代码(插入订单数据库表) // int i = orderDatabaseService.insert(....) // TODO 提交或回滚本地事务(如果用spring事务注解,这些都不需要我们手工去操作) // 模拟一个处理结果 int index = 8; /** * 模拟返回事务状态 */ switch (index) { case 3: System.out.printf("本地事务回滚,回滚消息,id:%s%n", msg.getKeys()); return LocalTransactionState.ROLLBACK_MESSAGE; case 5: case 8: return LocalTransactionState.UNKNOW; default: System.out.println("事务提交,消息正常处理"); return LocalTransactionState.COMMIT_MESSAGE; } } /** * Broker端对未确定状态的消息发起回查,将消息发送到对应的Producer端(同一个Group的Producer), * 由Producer根据消息来检查本地事务的状态,进而执行Commit或者Rollback * @param msg * @return 返回事务状态 */ @Override public LocalTransactionState checkLocalTransaction(MessageExt msg) { // 根据业务,正确处理: 订单场景,只要数据库有了这条记录,消息应该被commit String transactionId = msg.getTransactionId(); String key = msg.getKeys(); System.out.printf("回查事务状态 key:%-5s msgId:%-10s transactionId:%-10s %n", key, msg.getMsgId(), transactionId); if ("id_0".equals(key)) { // 刚刚测试的10条消息, 把id_5这条消息提交,其他的全部回滚。 System.out.printf("回查到本地事务已提交,提交消息,id:%s%n", msg.getKeys()); return LocalTransactionState.COMMIT_MESSAGE; } else { System.out.printf("未查到本地事务状态,回滚消息,id:%s%n", msg.getKeys()); return LocalTransactionState.ROLLBACK_MESSAGE; } } }; public static void main(String[] args) throws MQClientException, IOException { // 1. 创建事务生产者对象 // 和普通消息生产者有所区别,这里使用的是TransactionMQProducer TransactionMQProducer producer = new TransactionMQProducer("GROUP_TEST"); // 2. 设置NameServer的地址,如果设置了环境变量NAMESRV_ADDR,可以省略此步 producer.setNamesrvAddr("127.0.0.1:9876"); // 3. 设置事务监听器 producer.setTransactionListener(transactionListenerImpl); // 4. 启动生产者 producer.start(); for (int i = 0; i < 10; i++) { String content = "Hello transaction message " + i; Message message = new Message("TopicTest", "TagA01", "id_" + i, content.getBytes(RemotingHelper.DEFAULT_CHARSET)); // 5. 发送消息(发送一条新订单生成的通知) SendResult result = producer.sendMessageInTransaction(message, i); System.out.printf("发送结果:%s%n", result); } System.in.read(); // 6. 停止生产者 producer.shutdown(); } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话