• 【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("消息消费者已启动");
        }
    }
    View Code

    二、合并到项目里完成基础的生产消费功能

    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
    View Code

    生产者配置类

    /**
     * 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;
        }
    }
    View Code

    消费者配置类

    @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;
        }
    
    }
    View Code

    生产者生产消息

    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("发送失败");
            }
        }
    
    }
    View Code

    消费者消费消息的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;
        }
    }
    View Code

    三、根据自己的算法,选择MessageQueue发送 (顺序消息)

    除了生产者生产消息需要修改以外,其他都与上面的一样即可。

    application.yml

    rocketmq:
      log:
        topic: log_notify_topic
        producer:
          group: log_notify_producer_group
        consumer:
          group: log_notify_consumer_group
    View Code

    生产者配置类

    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;
        }
    
    }
    View Code

    消费者配置类

    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;
        }
    
    }
    View Code

    生产者生产消息*

    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("发送失败");
            }
        }
    
    }
    View Code

    消费者消费消息的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;
        }
    }
    View Code

    四、发送一条延时消息

    同样除了生产者生产消息需要修改以外,其他都与上面的一样即可。

    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
    View Code

    生产者配置类

    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;
        }
    
    }
    View Code

    消费者配置类

    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;
        }
    
    }
    View Code

    生产者生产消息*

    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("发送失败");
            }
    
        }
    
    }
    View Code

    消费者消费消息的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;
        }
    }
    View Code

    五、发送一条事务消息

    application.yml

    rocketmq:
      order:
        finished:
          topic: order_finished_topic
          producer:
            group: order_finished_producer_group
          consumer:
            group: order_finished_consumer_group
    View Code

    生产者配置类*

    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;
        }
    
    }
    View Code

    生产者监听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;
            }
    
        }
    }
    View Code

    生产者生产消息*

    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);
            }
    
        }
    
    }
    View Code

    消费者配置类和消费者消费消息的listener参考上面的例子就可以了,没什么特殊的。

    持续更新!!!

  • 相关阅读:
    有关UDP与TCP的一些疑问?
    UNP Ch 11, Name and Address Conversion
    C语言中的static关键字
    Typcical code to enable nonblocking I/O
    UNPv1_r3读书笔记: SCTP编程[转]
    用gcc链接重复定义的函数
    C语言编码风格 样例
    Chapter 3: File I/O
    getsockopt函数的使用
    开博客了
  • 原文地址:https://www.cnblogs.com/flyinghome/p/15456936.html
Copyright © 2020-2023  润新知