【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参考上面的例子就可以了,没什么特殊的。

 

 

 

 

 

 

持续更新!!!

posted @ 2021-10-25 09:20  夏夜凉凉  阅读(110)  评论(0编辑  收藏  举报