【RocketMQ】一、基本写法总结
一、demo级别
//生产者 public class Producer { public static void main(String[] args) throws MQClientException, UnsupportedEncodingException, RemotingException, InterruptedException, MQBrokerException { //创建一个消息生产者,并设置一个消息生产者组 DefaultMQProducer producer = new DefaultMQProducer("zs_producer_group"); //指定NameServer地址 producer.setNamesrvAddr("127.0.0.1:9876"); //初始化Producer,在整个应用生命周期中只需要初始化一次 producer.start(); for (int i = 0; i < 10; i++) { //创建一个消息对象,指定其主题、标签和消息内容 Message msg = new Message("topic_example_java","TagA",("Hello Java demo RocketMQ" + i+i).getBytes(RemotingHelper.DEFAULT_CHARSET)); //发送消息并返回结果 //SendResult [sendStatus=SEND_OK, msgId=00000000000000000000000000000001000078308DB164304A2C0000, offsetMsgId=AC10026300002A9F000000000000097E, messageQueue=MessageQueue [topic=topic_example_java, brokerName=DESKTOP-BIBQEM5, queueId=0], queueOffset=2] SendResult sendResult = producer.send(msg); System.out.printf("%s%n",sendResult); } //一旦生产者实例不再被使用,则将其关闭,包括清理资源、关闭网络连接等。 producer.shutdown(); } } //消费者 import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.common.message.MessageExt; import java.io.UnsupportedEncodingException; import java.util.List; public class Consumer { public static void main(String[] args) throws MQClientException { //创建一个消息消费者,并设置一个消息消费者组 DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("zs_consumer_group"); //指定NameServer地址 consumer.setNamesrvAddr("127.0.0.1:9876"); //设置Consumer第一次启动时是从队列头部还是队列尾部开始消费的 consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); //订阅指定Topic下的所有消息 consumer.subscribe("topic_example_java","*"); //注册消息监听器 consumer.registerMessageListener((List<MessageExt> list, ConsumeConcurrentlyContext context)->{ //默认list里只有一条消息,可以通过设置参数来批量接收消息 if(list != null){ for (int i = 0; i < list.size(); i++) { MessageExt ext = list.get(i); try { System.out.println(new String(ext.getBody(),"UTF-8")); } catch (UnsupportedEncodingException e) { } } } return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; }); //消费者对象在使用之前必须要调用start方法初始化 consumer.start(); System.out.println("消息消费者已启动"); } }
二、合并到项目里完成基础的生产消费功能
application.yml
rocketmq:
namesrv:
address: 127.0.0.1:9876
warn:
topic: warn_notify_topic
producer:
group: warn_notify_producer_group
consumer:
group: warn_notify_consumer_group
生产者配置类
/** * Description:告警消息的rocketmq生产者配置类 **/ @Slf4j @Configuration public class WarnMsgProducerConfiguration { @Value("${rocketmq.namesrv.address}") private String namesrvAddress; @Value("${rocketmq.warn.producer.group}") private String warnProducerGroup; /** * 登录生产者 * * @return 告警消息rocketmq的生产者对象 */ @Bean(value = "warnMsgMqProducer") public DefaultMQProducer warnMsgMqProducer() throws MQClientException { log.info("告警消息rocketmq的生产者对象初始化开始......"); DefaultMQProducer producer = new DefaultMQProducer(warnProducerGroup); producer.setNamesrvAddr(namesrvAddress); producer.start(); log.info("告警消息rocketmq的生产者对象初始化结束......"); return producer; } }
消费者配置类
@Configuration public class WarnMsgConsumerConfiguration { @Value("${rocketmq.namesrv.address}") private String namesrvAddress; @Value("${rocketmq.warn.consumer.group}") private String warnConsumerGroup; @Value("${rocketmq.warn.topic}") private String warnTopic; /** * 登录消息的consumer bean * * @return 登录消息的consumer bean */ @Bean(value = "warnMsgConsumer") public DefaultMQPushConsumer warnMsgConsumer(@Qualifier(value = "warnMsgMessageListener") WarnMsgMessageListener warnMsgMessageListener) throws MQClientException { DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(warnConsumerGroup); consumer.setNamesrvAddr(namesrvAddress); consumer.subscribe(warnTopic, "*"); consumer.setMessageListener(warnMsgMessageListener); consumer.start(); return consumer; } }
生产者生产消息
import com.alibaba.fastjson.JSON; import com.flyinghome.tm.entity.R; import com.flyinghome.tm.rocketmq.ruyuan.dto.WarnMsgDTO; import lombok.extern.slf4j.Slf4j; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.common.message.Message; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.nio.charset.StandardCharsets; @Slf4j @RestController @RequestMapping("/warn") public class WarnMsgSendController { @Autowired @Qualifier(value = "warnMsgMqProducer") private DefaultMQProducer warnMsgMqProducer; @Value("${rocketmq.warn.topic}") private String warnTopic; @PostMapping("/sendWarnMsg") public R sendWarnMsg(@RequestBody WarnMsgDTO warnMsgDTO){ // 场景一:性能提升 异步发送一个告警的消息到mq中 Message message = new Message(); message.setTopic(warnTopic); // 消息内容用户id message.setBody(JSON.toJSONString(warnMsgDTO).getBytes(StandardCharsets.UTF_8)); try { log.info("start send warn success notify message"); SendResult sendResult = warnMsgMqProducer.send(message); log.info("end send warn success notify message, sendResult:{}", JSON.toJSONString(sendResult)); return R.ok(sendResult,"发送成功"); } catch (Exception e) { log.error("send warn success notify message fail, error message:{}", e); return R.failed("发送失败"); } } }
消费者消费消息的listener
import com.alibaba.fastjson.JSON; import com.flyinghome.tm.rocketmq.ruyuan.Enum.ErrorCodeEnum; import com.flyinghome.tm.rocketmq.ruyuan.dto.CommonResponse; import com.flyinghome.tm.rocketmq.ruyuan.dto.WarnMsgDTO; import lombok.extern.slf4j.Slf4j; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.common.message.MessageExt; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Objects; /** * Description:告警消息的listener **/ @Slf4j @Component public class WarnMsgMessageListener implements MessageListenerConcurrently { @Autowired private RedisTemplate<String, Object> redisTemplate; private static Integer num = 0; @Override public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) { num ++ ; log.info("num====================="+num); log.info("开始消费-------------------------------"); for (MessageExt msg : msgs) { String body = new String(msg.getBody(), StandardCharsets.UTF_8); try { log.info("received warn success message:{}", body); //告警消息内容 WarnMsgDTO warnMsgDTO = JSON.parseObject(body, WarnMsgDTO.class); //此处通过redis的setnx保证幂等,返回一个状态码 CommonResponse<Boolean> response = new CommonResponse<>(); // redis操作失败 过一段时间重试 if (Objects.equals(response.getCode(), ErrorCodeEnum.FAIL.getCode())) { // if (num < 2) { //测试延迟消息 log.info("consumer warn message redis interface fail"); return ConsumeConcurrentlyStatus.RECONSUME_LATER; } //每次从MQ中获取告警消息并成功处理之后,都会存入redis一份,说明处理过此消息了,不要重复消费了。 //redis操作成功,并且返回false,即redis中有这个数据 if (Objects.equals(response.getCode(), ErrorCodeEnum.SUCCESS.getCode()) && Objects.equals(response.getData(), Boolean.FALSE)) { // 此消息redis中有,即之前已经被处理过,不会重复消费 log.info("forbid duplicate consumer"); } else { //此消费未消费过,下面的代码写,执行对应数据库落库、redis、告警规则判断是否告警、发短信、邮件、微信等逻辑即可 log.info("mysql redis insert warn msg:{}",body); } } catch (Exception e) { // 消费失败,删除redis中幂等key redisTemplate.delete("xxxx"); // 消费失败 log.info("received warn success message:{}, consumer fail", body); log.info("结束消费,消费失败-------------------------------"); // Failure consumption,later try to consume 消费失败,以后尝试消费 return ConsumeConcurrentlyStatus.RECONSUME_LATER; } } log.info("结束消费,消费成功-------------------------------"); return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } }
三、根据自己的算法,选择MessageQueue发送 (顺序消息)
除了生产者生产消息需要修改以外,其他都与上面的一样即可。
application.yml
rocketmq:
log:
topic: log_notify_topic
producer:
group: log_notify_producer_group
consumer:
group: log_notify_consumer_group
生产者配置类
import lombok.extern.slf4j.Slf4j; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * Description:日志消息的rocketmq生产者配置类 **/ @Slf4j @Configuration public class LogMsgProducerConfiguration { @Value("${rocketmq.namesrv.address}") private String namesrvAddress; @Value("${rocketmq.log.producer.group}") private String logProducerGroup; /** * 日志消息生产者 * * @return 日志消息rocketmq的生产者对象 */ @Bean(value = "logMsgMqProducer") public DefaultMQProducer logMsgMqProducer() throws MQClientException { log.info("日志消息rocketmq的生产者对象初始化开始......"); DefaultMQProducer producer = new DefaultMQProducer(logProducerGroup); producer.setNamesrvAddr(namesrvAddress); producer.start(); log.info("日志消息rocketmq的生产者对象初始化结束......"); return producer; } }
消费者配置类
import com.flyinghome.tm.rocketmq.ruyuan.listener.LogMsgMessageListener; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.exception.MQClientException; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class LogMsgConsumerConfiguration { @Value("${rocketmq.namesrv.address}") private String namesrvAddress; @Value("${rocketmq.log.consumer.group}") private String logConsumerGroup; @Value("${rocketmq.log.topic}") private String logTopic; /** * 日志消息的consumer bean * * @return 日志消息的consumer bean */ @Bean(value = "logMsgConsumer") public DefaultMQPushConsumer logMsgConsumer(@Qualifier(value = "logMsgMessageListener") LogMsgMessageListener logMsgMessageListener) throws MQClientException { DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(logConsumerGroup); consumer.setNamesrvAddr(namesrvAddress); consumer.subscribe(logTopic, "*"); consumer.setMessageListener(logMsgMessageListener); consumer.start(); return consumer; } }
生产者生产消息*
import com.alibaba.fastjson.JSON; import com.flyinghome.tm.entity.R; import com.flyinghome.tm.rocketmq.ruyuan.dto.LogMsgDTO; import lombok.extern.slf4j.Slf4j; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.MessageQueueSelector; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageQueue; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.nio.charset.StandardCharsets; import java.util.List; @Slf4j @RestController @RequestMapping("/log") public class LogMsgSendController { @Autowired @Qualifier(value = "logMsgMqProducer") private DefaultMQProducer logMsgMqProducer; @Value("${rocketmq.log.topic}") private String logTopic; //一般这里不会写在controller里,因为是一个指定MessageQueue发送消息以实现将来可以顺序消费的方法 //所以,一般会写在impl里,供其他方法调用。这里写在此处是为了方便测试调用。 @PostMapping("/sendLogMsg") public R sendLogMsg(@RequestBody LogMsgDTO logMsgDTO){ // 场景二:根据自己的算法,选择MessageQueue (顺序消息) 异步发送一个日志的消息到mq中 Message message = new Message(); message.setTopic(logTopic); message.setBody(JSON.toJSONString(logMsgDTO).getBytes(StandardCharsets.UTF_8)); try { SendResult sendResult = logMsgMqProducer.send(message, new MessageQueueSelector() { //这里 Object logId 参数会取传入logMsgDTO里的id,不传id 值为"", @Override public MessageQueue select(List<MessageQueue> mqs, Message msg, Object logId) { log.info("-----------logId------------"+logId); //防止不传Id,导致下面的转化报错 if(logId == null || "".equals(logId.toString())) return mqs.get(0); // 日志id Integer id = Integer.parseInt(logId.toString()); int index = id % mqs.size(); return mqs.get(index); } }, logMsgDTO.getId()); log.info("send log message success,finished,sendResult:{}",sendResult); return R.ok(sendResult,"发送成功"); } catch (Exception e) { // 发送日志消息失败 log.error("send log message fail,error message:{}", e.getMessage()); return R.failed("发送失败"); } } }
消费者消费消息的listener
import com.alibaba.fastjson.JSON; import com.flyinghome.tm.rocketmq.ruyuan.Enum.ErrorCodeEnum; import com.flyinghome.tm.rocketmq.ruyuan.dto.CommonResponse; import com.flyinghome.tm.rocketmq.ruyuan.dto.LogMsgDTO; import lombok.extern.slf4j.Slf4j; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.common.message.MessageExt; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Objects; /** * Description:日志消息的listener **/ @Slf4j @Component public class LogMsgMessageListener implements MessageListenerConcurrently { @Autowired private RedisTemplate<String, Object> redisTemplate; private static Integer num = 0; @Override public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) { num ++ ; log.info("num====================="+num); log.info("开始消费-------------------------------"); for (MessageExt msg : msgs) { String body = new String(msg.getBody(), StandardCharsets.UTF_8); try { log.info("received log success message:{}", body); //日志消息内容 LogMsgDTO logMsgDTO = JSON.parseObject(body, LogMsgDTO.class); //此处通过redis的setnx保证幂等,返回一个状态码 CommonResponse<Boolean> response = new CommonResponse<>(); // redis操作失败 过一段时间重试 if (Objects.equals(response.getCode(), ErrorCodeEnum.FAIL.getCode())) { // if (num < 2) { //测试延迟消息 log.info("consumer log message redis interface fail"); return ConsumeConcurrentlyStatus.RECONSUME_LATER; } //每次从MQ中获取告警消息并成功处理之后,都会存入redis一份,说明处理过此消息了,不要重复消费了。 //redis操作成功,并且返回false,即redis中有这个数据 if (Objects.equals(response.getCode(), ErrorCodeEnum.SUCCESS.getCode()) && Objects.equals(response.getData(), Boolean.FALSE)) { // 此消息redis中有,即之前已经被处理过,不会重复消费 log.info("forbid duplicate consumer"); } else { //此消费未消费过,下面的代码写,执行对应数据库落库、redis、告警规则判断是否告警、发短信、邮件、微信等逻辑即可 log.info("mysql redis insert warn msg:{}",body); } } catch (Exception e) { // 消费失败,删除redis中幂等key redisTemplate.delete("xxxx"); // 消费失败 log.info("received log success message:{}, consumer fail", body); log.info("结束消费,消费失败-------------------------------"); // Failure consumption,later try to consume 消费失败,以后尝试消费 return ConsumeConcurrentlyStatus.RECONSUME_LATER; } } log.info("结束消费,消费成功-------------------------------"); return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } }
四、发送一条延时消息
同样除了生产者生产消息需要修改以外,其他都与上面的一样即可。
application.yml
rocketmq:
delay:
topic: delay_notify_topic
producer:
group: delay_notify_producer_group
consumer:
group: delay_notify_consumer_group
# 1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
# 消息延时等级 从1开始
level: 3
生产者配置类
import lombok.extern.slf4j.Slf4j; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * Description:延时消息的rocketmq生产者配置类 **/ @Slf4j @Configuration public class DelayMsgProducerConfiguration { @Value("${rocketmq.namesrv.address}") private String namesrvAddress; @Value("${rocketmq.delay.producer.group}") private String delayProducerGroup; /** * 延时消息生产者 * * @return 延时消息rocketmq的生产者对象 */ @Bean(value = "delayMsgMqProducer") public DefaultMQProducer delayMsgMqProducer() throws MQClientException { log.info("延时消息rocketmq的生产者对象初始化开始......"); DefaultMQProducer producer = new DefaultMQProducer(delayProducerGroup); producer.setNamesrvAddr(namesrvAddress); producer.start(); log.info("延时消息rocketmq的生产者对象初始化结束......"); return producer; } }
消费者配置类
import com.flyinghome.tm.rocketmq.ruyuan.listener.DelayMsgMessageListener; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.exception.MQClientException; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class DelayMsgConsumerConfiguration { @Value("${rocketmq.namesrv.address}") private String namesrvAddress; @Value("${rocketmq.delay.consumer.group}") private String delayConsumerGroup; @Value("${rocketmq.delay.topic}") private String delayTopic; /** * 延时消息的consumer bean * * @return 延时消息的consumer bean */ @Bean(value = "delayMsgConsumer") public DefaultMQPushConsumer delayMsgConsumer(@Qualifier(value = "delayMsgMessageListener") DelayMsgMessageListener delayMsgMessageListener) throws MQClientException { DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(delayConsumerGroup); consumer.setNamesrvAddr(namesrvAddress); consumer.subscribe(delayTopic, "*"); consumer.setMessageListener(delayMsgMessageListener); consumer.start(); return consumer; } }
生产者生产消息*
import com.alibaba.fastjson.JSON; import com.flyinghome.tm.entity.R; import com.flyinghome.tm.rocketmq.ruyuan.dto.DelayMsgDTO; import lombok.extern.slf4j.Slf4j; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.common.message.Message; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.nio.charset.StandardCharsets; @Slf4j @RestController @RequestMapping("/delay") public class DelayMsgSendController { @Autowired @Qualifier(value = "delayMsgMqProducer") private DefaultMQProducer delayMsgMqProducer; @Value("${rocketmq.delay.topic}") private String delayTopic; /** * 延时消息等级 最好是根据业务配置多个延时变量 比如warnDelayLevel logDelayLevel等 */ @Value("${rocketmq.delay.level}") private Integer delayLevel; @PostMapping("/sendDelayMsg") public R sendDelayMsg(@RequestBody DelayMsgDTO delayMsgDTO){ //场景三:发送一个延时的消息到mq中 Message message = new Message(); message.setTopic(delayTopic); // 10秒钟 // private String messageDelayLevel = "1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h"; // 延时等级从1开始 message.setDelayTimeLevel(delayLevel); message.setBody(JSON.toJSONString(delayMsgDTO).getBytes(StandardCharsets.UTF_8)); try { SendResult sendResult = delayMsgMqProducer.send(message); log.info("send delay message finished delayTimeLevel:{}",delayLevel); return R.ok(sendResult,"发送成功"); } catch (Exception e) { // 发送xxx延时消息失败 log.error("send delay message fail,error message:{}", e.getMessage()); return R.failed("发送失败"); } } }
消费者消费消息的listener
import com.alibaba.fastjson.JSON; import com.flyinghome.tm.rocketmq.ruyuan.Enum.ErrorCodeEnum; import com.flyinghome.tm.rocketmq.ruyuan.dto.CommonResponse; import com.flyinghome.tm.rocketmq.ruyuan.dto.DelayMsgDTO; import lombok.extern.slf4j.Slf4j; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.common.message.MessageExt; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Objects; /** * Description:延时消息的listener **/ @Slf4j @Component public class DelayMsgMessageListener implements MessageListenerConcurrently { @Autowired private RedisTemplate<String, Object> redisTemplate; private static Integer num = 0; @Override public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) { num ++ ; log.info("num====================="+num); log.info("开始消费-------------------------------"); for (MessageExt msg : msgs) { String body = new String(msg.getBody(), StandardCharsets.UTF_8); try { log.info("received delay success message:{}", body); //日志消息内容 DelayMsgDTO logMsgDTO = JSON.parseObject(body, DelayMsgDTO.class); //此处通过redis的setnx保证幂等,返回一个状态码 CommonResponse<Boolean> response = new CommonResponse<>(); // redis操作失败 过一段时间重试 if (Objects.equals(response.getCode(), ErrorCodeEnum.FAIL.getCode())) { // if (num < 2) { //测试延迟消息 log.info("consumer delay message redis interface fail"); return ConsumeConcurrentlyStatus.RECONSUME_LATER; } //每次从MQ中获取告警消息并成功处理之后,都会存入redis一份,说明处理过此消息了,不要重复消费了。 //redis操作成功,并且返回false,即redis中有这个数据 if (Objects.equals(response.getCode(), ErrorCodeEnum.SUCCESS.getCode()) && Objects.equals(response.getData(), Boolean.FALSE)) { // 此消息redis中有,即之前已经被处理过,不会重复消费 log.info("forbid duplicate consumer"); } else { //此消费未消费过,下面的代码写,执行对应数据库落库、redis、告警规则判断是否告警、发短信、邮件、微信等逻辑即可 log.info("mysql redis insert warn msg:{}",body); } } catch (Exception e) { // 消费失败,删除redis中幂等key redisTemplate.delete("xxxx"); // 消费失败 log.info("received delay success message:{}, consumer fail", body); log.info("结束消费,消费失败-------------------------------"); // Failure consumption,later try to consume 消费失败,以后尝试消费 return ConsumeConcurrentlyStatus.RECONSUME_LATER; } } log.info("结束消费,消费成功-------------------------------"); return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } }
五、发送一条事务消息
application.yml
rocketmq:
order:
finished:
topic: order_finished_topic
producer:
group: order_finished_producer_group
consumer:
group: order_finished_consumer_group
生产者配置类*
import com.flyinghome.tm.rocketmq.ruyuan.listener.FinishedOrderTransactionListener; import lombok.extern.slf4j.Slf4j; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.TransactionMQProducer; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.concurrent.*; /** * Description:订单的rocketmq生产者配置类 **/ @Slf4j @Configuration public class OrderProducerConfiguration { @Value("${rocketmq.namesrv.address}") private String namesrvAddress; @Value("${rocketmq.order.finished.producer.group}") private String orderFinishedProducerGroup; /** * 订单事务消息生产者 * @return 订单事务消息rocketmq的生产者对象 */ @Bean(value = "orderFinishedTransactionMqProducer") public TransactionMQProducer orderTransactionMqProducer(@Qualifier(value = "finishedOrderTransactionListener") FinishedOrderTransactionListener finishedOrderTransactionListener) throws MQClientException { log.info("事务订单消息rocketmq的生产者对象初始化开始......"); TransactionMQProducer producer = new TransactionMQProducer(orderFinishedProducerGroup); producer.setNamesrvAddr(namesrvAddress); // 事务回调线程池处理 ExecutorService executorService = new ThreadPoolExecutor(2, 5, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(2000), new ThreadFactory() { @Override public Thread newThread(Runnable r) { Thread thread = new Thread(r); thread.setName("client-transaction-msg-check-thread"); return thread; } }); producer.setExecutorService(executorService); // 回调函数 producer.setTransactionListener(finishedOrderTransactionListener); producer.start(); log.info("事务订单消息rocketmq的生产者对象初始化结束......"); return producer; } }
生产者监听half消息的listener类*
import com.alibaba.fastjson.JSON; import com.flyinghome.tm.rocketmq.ruyuan.dto.OrderInfoDTO; import lombok.extern.slf4j.Slf4j; import org.apache.rocketmq.client.producer.LocalTransactionState; import org.apache.rocketmq.client.producer.TransactionListener; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageExt; import org.springframework.stereotype.Component; import java.nio.charset.StandardCharsets; import java.util.Objects; /** * Description:订单事务消息listener **/ @Slf4j @Component public class FinishedOrderTransactionListener implements TransactionListener { /** * 执行本地事务 * * @param msg * @param arg * @return */ @Override public LocalTransactionState executeLocalTransaction(Message msg, Object arg) { log.info("发送half事务订单消息到rocketmq成功......开始"); //此方法是half消息发送成功后的回调方法 // TODO 可以通过状态模式来校验订单的流转和保存订单操作日志 String body = new String(msg.getBody(), StandardCharsets.UTF_8); OrderInfoDTO orderInfoDTO = JSON.parseObject(body, OrderInfoDTO.class); log.info("callback execute finished order local transaction orderInfoId:{}", orderInfoDTO.getId()); try { //此处写操作mysql数据库修改订单的状态相关逻辑代码 //这里写发送退单成功的消息到MQ里的逻辑代码 //成功 提交prepare消息 log.info("finished order local transaction execute success commit orderInfoId:{}", orderInfoDTO.getId()); log.info("发送half事务订单消息到rocketmq成功......COMMIT......"); return LocalTransactionState.COMMIT_MESSAGE; } catch (Exception e) { // 执行本地事务失败 回滚prepare消息 log.info("finished order local transaction execute fail rollback orderInfoId:{}", orderInfoDTO.getId()); log.info("发送half事务订单消息到rocketmq成功......ROLLBACK......"); return LocalTransactionState.ROLLBACK_MESSAGE; } } /** * 检查本地事务 如果由于各种原因导致mq没收到commit或者rollback消息回调检查本地事务结果 * * @param msg * @return */ @Override public LocalTransactionState checkLocalTransaction(MessageExt msg) { log.info("发送half事务订单消息到rocketmq一直没有收到commit/rollback......补偿机制开始"); String body = new String(msg.getBody(), StandardCharsets.UTF_8); OrderInfoDTO orderInfoDTO = JSON.parseObject(body, OrderInfoDTO.class); log.info("callback check finished order local transaction status orderInfoId:{}", orderInfoDTO.getId()); try { //此处写查询mysql数据库,获取该订单的状态的逻辑 Integer orderStatus = 1; //从数据库中查到了有此条数据 那么提交 if (Objects.equals(orderStatus, 1)) { log.info("finished order local transaction check result success commit orderInfoId:{}", orderInfoDTO.getId()); log.info("发送half事务订单消息到rocketmq一直没有收到commit/rollback......COMMIT处理"); return LocalTransactionState.COMMIT_MESSAGE; } else {//没有查到此条数据,说明之前因为某种原因没有保存上 log.info("finished order local transaction check result fail rollback orderInfoId:{}", orderInfoDTO.getId()); log.info("发送half事务订单消息到rocketmq一直没有收到commit/rollback......ROLLBACK处理"); return LocalTransactionState.ROLLBACK_MESSAGE; } } catch (Exception e) { // 查询订单状态失败 log.info("finished order local transaction check result fail rollback orderInfoId:{}", orderInfoDTO.getId()); log.info("发送half事务订单消息到rocketmq一直没有收到commit/rollback......ROLLBACK处理"); return LocalTransactionState.ROLLBACK_MESSAGE; } } }
生产者生产消息*
import com.alibaba.fastjson.JSON; import com.flyinghome.tm.rocketmq.ruyuan.dto.OrderInfoDTO; import lombok.extern.slf4j.Slf4j; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.common.message.Message; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.nio.charset.StandardCharsets; @Slf4j @RestController @RequestMapping("/orderFinished") public class OrderFinishedTransactionController { @Autowired @Qualifier(value = "orderFinishedTransactionMqProducer") private DefaultMQProducer orderFinishedTransactionMqProducer; @Value("${rocketmq.order.finished.topic}") private String orderFinishedTopic; @PostMapping("/orderFinishedTransaction") public void orderFinishedTransaction(Integer orderId){ //场景三:发送一个事务half消息到mq中 //这里写的逻辑是 访问mysql,通过订单ID获取订单信息详情 OrderInfoDTO orderInfo = new OrderInfoDTO("1","1","模拟根据orderId从mysql查出来的数据"); //退单事务消息 Message msg = new Message(orderFinishedTopic, JSON.toJSONString(orderInfo).getBytes(StandardCharsets.UTF_8)); try { //发送prepare/half消息 orderFinishedTransactionMqProducer.sendMessageInTransaction(msg, null); } catch (MQClientException e) { //发送half消息失败 log.info("finished order send half message fail error:{}", e); } } }
消费者配置类和消费者消费消息的listener参考上面的例子就可以了,没什么特殊的。
持续更新!!!