RocketMq-Boot整合

Boot-Mq整合

0、公共介绍
1、同步发送消息
2、异步步发送消息
3、单向发送消息
4、延迟发送消息
5、顺序发送消息
6、标签发送消息
7、key值发送消息
8、消费重试
9、发送定时消息可指定时间
10、消息两种模式(集群和广播)
11、如何解决消息堆积
12、消息丢失
13、安全

Boot-Mq整合代码

公共

pom:

        <dependency>
            <groupId>org.apache.rocketmq</groupId>
            <artifactId>rocketmq-spring-boot-starter</artifactId>
            <version>2.2.2</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
@Data
@AllArgsConstructor
@NoArgsConstructor
public class OrderEntity {
    //订单id
    private Integer orderId;
    //订单编号
    private Integer orderNumber;
    //订单价格
    private Double price;
    //订单号创建时间
    private Date createTime;
    //订单描述
    private String desc;

}

消息头中存在:延迟等级,队列id,broker名字,发送时间,重试次数主题定义(生产者主题直接在yml中定义,使用同意的主题,如果想自定义主题,可以使用下面的方法定义主题)
{ MessageBuilder.withPayload(“延迟消息”).setHeader(RocketMQHeaders.KEYS," keyValue").build(); }

消息体:具体的业务内容
{ MessageBuilder.withPayload }
在这里插入图片描述

ACK机制介绍

  • RocketMQ提供了ack机制(默认是手动ack),以保证消息能够被正常消费。为了保证消息肯定消费成功,只有使用方明确表示消费成功,RocketMQ才会认为消息消费成功,然后删除消息。中途断电,抛出异常等都不会认为成功。
  • 这里使用的rocketmq-spring-boot-starter包算是自动ACK了吧,使用@RocketMQMessageListener注解标记一个实现RocketMQListener接口的消费者类并且实现onMessage方法,只要这个onMessage方法执行不抛出异常则由代理类自动进行ACK处理,如果抛出异常则确认失败消息会继续重试消费

RocketMQListener接口介绍

泛型问题

使用MessageExt(可获取完整消息对象:延迟等级,队列id,broker名字,发送时间,重试次数主题定义(生产者主题直接在yml中定义,使用同意的主题,如果想自定义主题,可以使用下面的方法定义主题)

如果需要获取获取除了消息体以外的信息可以使用MessageExt作为RocketMQListener的泛型,在做消息幂等时可能会需要使用到消息ID。

同步发送消息

同步生产者:

# 应用服务 WEB 访问端口
server:
    port: 8888
rocketmq:
#    // 配置 RocketMQ 的 NameServer 地址
    name-server: 192.168.101.128:9876
    producer:
        group: producer-group
#        发送消息超时时间: 3000
        send-message-timeout: 3000
#        失败重试次数同步次数
        retry-times-when-send-failed: 2
#        4194304  4M,发送最大消息字节
        max-message-size: 4194304
package com.example.demo;

import com.alibaba.fastjson.JSON;
import com.example.demo.domain.OrderEntity;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.apache.rocketmq.spring.support.RocketMQHeaders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Arrays;
import java.util.Date;
import java.util.List;

/**
 * 
 * @date 2024/10/2 16:42
 */
@RestController
@RequestMapping("/rocketmq")
public class ProducerRocketMq {
    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    /**
     * 发送同步消息
     */
    @GetMapping("/sendSynMessage")
    public void sendSynMessage() {
        SendResult sendResult = rocketMQTemplate.syncSend("syn-topic-boot", "这是boot结合的一个消息");
        System.out.println("消息msgId" + sendResult.getMsgId());
        System.out.println("消息的状态---->" + sendResult.getSendStatus());
    }

    /**
     * 发送异步消息
     */
    @GetMapping("/sendAsynMessage")
    public void sendAsynMessage() {
        rocketMQTemplate.asyncSend("asyn-topic-boot", "异步消息", new SendCallback() {
            @Override
            public void onSuccess(SendResult sendResult) {
                System.out.println("异步消息发送成功---" + sendResult.getSendStatus());
            }

            @Override
            public void onException(Throwable throwable) {
                System.out.println("异步消息发送失败");
                System.out.println(throwable.getMessage());
            }
        });
    }

    /**
     * 发送单向消息
     */
    @GetMapping("/sendOneWayMessage")
    public void sendOneWayMessage() {
        rocketMQTemplate.sendOneWay("oneway-topic-boot", "单向消息");
    }

    /**
     * 发送延迟消息
     * 设置延迟等级  messageDelayLevel = "1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
     */
    @GetMapping("/sendDelayMessage")
    public void sendDelayMessage() {
        //创建消息
        Message<String> message = MessageBuilder.withPayload("延迟消息").build();
        rocketMQTemplate.syncSend("delay-topic-boot", message, 3, 3);
    }




    /**
     * key值发送消息
     */
    @GetMapping("/sendKeyMessage")
    public void sendKeyMessage() {

        //主题上打key值
        Message<String> message = MessageBuilder.withPayload("这是一个携带key的消息")
                .setHeader(RocketMQHeaders.KEYS, "keyValue").build();

        // 主题上打标签发送消息keyA
        rocketMQTemplate.syncSend("key-topic-boot", message);
    }


}

同步消费者

server:
    port: 8889
rocketmq:
    name-server: 192.168.101.128:9876

使用监听机制,监听主题,

@Component
@RocketMQMessageListener(topic = "syn-topic-boot", consumerGroup = "group-sync")
public class ConsumerListenerRocketMq implements RocketMQListener<MessageExt> {
    @Override
    public void onMessage(MessageExt messageExt) {
        System.out.println("接收到了消息:" + new String(messageExt.getBody()));
    }
}

同步结果:

在这里插入图片描述

在这里插入图片描述
消费者:

方法一:
如果地址没有写错,出现连接不上的情况,本项目换一个低一点的版本可以接受到消息。
在这里插入图片描述


        <dependency>
            <groupId>org.apache.rocketmq</groupId>
            <artifactId>rocketmq-spring-boot-starter</artifactId>
            <version>2.0.2</version>
        </dependency>

在这里插入图片描述
方法二:

在这里插入图片描述

代码执行的时间过长,才报出了invokeSync call timeout异常。首先想到的是延长超时时间。在创建DefaultChannelId的实例时,执行了这个类的静态代码块,就是这段静态代码块比较耗时。

添加一个配置类:

//先加载这个。
@Configuration
public class RocketMqConfiguration {

    @Bean
    public DefaultChannelId newIDefaultChannelId() {
        return DefaultChannelId.newInstance();
    }
}

异步发送消息

生产者:


import com.alibaba.fastjson.JSON;
import com.example.demo.domain.OrderEntity;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.apache.rocketmq.spring.support.RocketMQHeaders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Arrays;
import java.util.Date;
import java.util.List;

/**
 *
 * @date 2024/10/2 16:42
 */
@RestController
@RequestMapping("/rocketmq")
public class ProducerRocketMq {
    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    /**
     * 发送异步消息
     */
    @GetMapping("/sendAsynMessage")
    public void sendAsynMessage() {
        rocketMQTemplate.asyncSend("asyn-topic-boot", "异步消息", new SendCallback() {
            @Override
            public void onSuccess(SendResult sendResult) {
                System.out.println("异步消息发送成功---" + sendResult.getSendStatus());
            }

            @Override
            public void onException(Throwable throwable) {
                System.out.println("异步消息发送失败");
                System.out.println(throwable.getMessage());
            }
        });
    }

}

消费者:

import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;

/**
 * 
 * @date 2024/10/2 16:55
 */
//同步发送消息注解
@Component
//同步监听
// 默认selectorExpression="*" 表示消费所有消息

//异步监听
// 默认selectorExpression="*" 表示消费所有消息
@RocketMQMessageListener(topic = "asyn-topic-boot", consumerGroup = "group-async")

public class ConsumerListenerRocketMq implements RocketMQListener<MessageExt> {

    //MessageExt类型---是消息的所有内容,没有报错就签收,如果报错就拒收。
    @Override
    public void onMessage(MessageExt messageExt) {
        System.out.println("接收到了消息:" + new String(messageExt.getBody()));
    }

}

结果:

生产者:
在这里插入图片描述

消费者:
在这里插入图片描述

3、单向发送消息

发送方只负责发送消息,不等待服务端返回响应且没有回调函数触发,即只发送请求不等待应答。此方式发送消息的过程耗时非常短,一般在微秒级别。适用于某些耗时非常短,但对可靠性要求并不高的场景,例如日志收集。

在这里插入图片描述

生产者

import com.alibaba.fastjson.JSON;
import com.example.demo.domain.OrderEntity;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.apache.rocketmq.spring.support.RocketMQHeaders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Arrays;
import java.util.Date;
import java.util.List;

/**
 *
 * @date 2024/10/2 16:42
 */
@RestController
@RequestMapping("/rocketmq")
public class ProducerRocketMq {
    @Autowired
    private RocketMQTemplate rocketMQTemplate;

     /**
     * 发送单向消息
     */
    @GetMapping("/sendOneWayMessage")
    public void sendOneWayMessage() {
        rocketMQTemplate.sendOneWay("oneway-topic-boot", "单向消息");
    }


}

消费者

import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;

/**
 *
 * @date 2024/10/2 16:55
 */
//同步发送消息注解
@Component

//单向发送
// 默认selectorExpression="*" 表示消费所有消息
@RocketMQMessageListener(topic = "oneway-topic-boot", consumerGroup = "group-oneway")

public class ConsumerListenerRocketMq implements RocketMQListener<MessageExt> {

    //MessageExt类型---是消息的所有内容,没有报错就签收,如果报错就拒收。
    @Override
    public void onMessage(MessageExt messageExt) {
        System.out.println("接收到了消息:" + new String(messageExt.getBody()));
    }

}

结果
在这里插入图片描述


延迟发送消息

生产者

import com.alibaba.fastjson.JSON;
import com.example.demo.domain.OrderEntity;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.apache.rocketmq.spring.support.RocketMQHeaders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Arrays;
import java.util.Date;
import java.util.List;

/**
 *
 * @date 2024/10/2 16:42
 */
@RestController
@RequestMapping("/rocketmq")
public class ProducerRocketMq {
    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    /**
     * 发送延迟消息
     * 设置延迟等级  messageDelayLevel = "1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
     */
    @GetMapping("/sendDelayMessage")
    public void sendDelayMessage() {
        //创建消息

        System.out.println("发送时间--begin"+new Date());
        OrderEntity order = new OrderEntity(1, 111, 51D, new Date(), "延迟消息");
        String jsonString = JSON.toJSONString(order);
        Message<String> message = MessageBuilder.withPayload(jsonString).build();
        rocketMQTemplate.syncSend("delay-topic-boot", message, 3000, 3);
    }


}

消费者

import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;

/**
 *
 * @date 2024/10/2 16:55
 */
//同步发送消息注解
@Component
//延迟消息
// 默认selectorExpression="*" 表示消费所有消息
@RocketMQMessageListener(topic = "delay-topic-boot", consumerGroup = "group-delay")


public class ConsumerListenerRocketMq implements RocketMQListener<MessageExt> {

    //MessageExt类型---是消息的所有内容,没有报错就签收,如果报错就拒收。
    @Override
    public void onMessage(MessageExt messageExt) {
        System.out.println("收到消息了---end"+new Date());
        OrderEntity orderEntity = JSON.parseObject(new String(messageExt.getBody()), OrderEntity.class);
        System.out.println("接收到了消息:" + orderEntity);
    }

}

结果

生产者
在这里插入图片描述

消费者
在这里插入图片描述

顺序发送消息

消息有序指的是可以按照消息的发送顺序来消费(FIFO)。RocketMQ可以严格的保证消息有序,可以分为:分区有序或者全局有序。
可能大家会有疑问,mq不就是FIFO吗?
rocketMq的broker的机制,导致了rocketMq会有这个问题
因为一个broker中对应了四个queue

示例:

将A,B,C消息放到一个队列中,使用单线程的模式,即可完成局部有序。

在这里插入图片描述

生产者

import com.alibaba.fastjson.JSON;
import com.example.demo.domain.OrderEntity;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.apache.rocketmq.spring.support.RocketMQHeaders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Arrays;
import java.util.Date;
import java.util.List;

/**
 *
 * @date 2024/10/2 16:42
 */
@RestController
@RequestMapping("/rocketmq")
public class ProducerRocketMq {
    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    /**
     * 顺序发送消息
     */
    @GetMapping("/sendOrderMessage")
    public void sendOrderMessage() {
        //将订单信息封装成消息体
        List<OrderEntity> msgs = Arrays.asList(
                new OrderEntity(1, 111, 51D, new Date(), "下订单"),
                new OrderEntity(2, 111, 51D, new Date(), "物流"),
                new OrderEntity(3, 111, 51D, new Date(), "签收"),
                new OrderEntity(4, 112, 52D, new Date(), "下订单"),
                new OrderEntity(5, 112, 52D, new Date(), "物流"),
                new OrderEntity(6, 112, 52D, new Date(), "签收")
        );
        msgs.forEach(msg -> {
            //第二个参数将消息封装成json格式,进行传输,根据第三个参数将消息划分到同一主题队列中
            rocketMQTemplate.syncSendOrderly("order-topic-boot", JSON.toJSONString(msg), msg.getOrderNumber().toString());
        });

    }

}

消费者

@Component
/**
 * consumeMode = ConsumeMode.C 表示消费模式为并发消费
 * consumeMode = ConsumeMode.O 表示消费模式为顺序消费
 * 默认selectorExpression="*" 表示消费所有消息
 * maxReconsumeTimes 表示消息重试次数,默认是16次。如果每次消费失败,则会重试16次,如果第17次还失败,则会将该消息放到死信队列中。
 */
@RocketMQMessageListener(topic = "order-topic-boot", consumerGroup = "group-orderly",
consumeMode = ConsumeMode.ORDERLY,
maxReconsumeTimes = 3)
public class ConsumerOrderListenerRocketMq implements RocketMQListener<MessageExt> {
    @Override
    public void onMessage(MessageExt messageExt) {
        OrderEntity  orderEntity = JSON.parseObject(new String(messageExt.getBody()), OrderEntity.class);
        System.out.println("接收到了消息:" + orderEntity);
    }
}

结果

在这里插入图片描述

6、 主题上打标签发送消息

生产者

import com.alibaba.fastjson.JSON;
import com.example.demo.domain.OrderEntity;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.apache.rocketmq.spring.support.RocketMQHeaders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Arrays;
import java.util.Date;
import java.util.List;

/**
 *
 * @date 2024/10/2 16:42
 */
@RestController
@RequestMapping("/rocketmq")
public class ProducerRocketMq {
    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    /**
     * 主题上打标签发送消息
     */
    @GetMapping("/sendTagAMessage")
    public void sendTagMessageA() {

        OrderEntity order = new OrderEntity(1, 111, 51D, new Date(), "这是一个携带标签的消息");
        String string = JSON.toJSONString(order);
        Message<String> message = MessageBuilder.withPayload(string).build();
        // 主题上打标签发送消息tagA
        rocketMQTemplate.syncSend("tag-topic-boot:tagA", message);
    }



    /**
     * 主题上打标签发送消息
     */
    @GetMapping("/sendTagBMessage")
    public void sendTagMessageB() {

        OrderEntity order = new OrderEntity(1, 111, 51D, new Date(), "这是一个携带标签B的消息");
        String string = JSON.toJSONString(order);
        Message<String> message = MessageBuilder.withPayload(string).build();
        // 主题上打标签发送消息tagA
        rocketMQTemplate.syncSend("tag-topic-boot:tagB", message);
    }

}

消费者

package com.example.demo;

import com.alibaba.fastjson.JSON;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
 * 
 * @date 2024/10/2 16:55
 */
//同步发送消息注解
@Component


//主题上个打标签发送消息,"tagA || tagB"  能接受tagA || tagB  的消息
@RocketMQMessageListener(topic = "tag-topic-boot", consumerGroup = "group-tag",
selectorType = SelectorType.TAG,
        selectorExpression = "tagA || tagB")

//key值过滤
//@RocketMQMessageListener(topic ="key-topic-boot", consumerGroup = "group-key")
public class ConsumerListenerRocketMq implements RocketMQListener<MessageExt> {

    //MessageExt类型---是消息的所有内容,没有报错就签收,如果报错就拒收。
    @Override
    public void onMessage(MessageExt messageExt) {
        byte[] body = messageExt.getBody();
        System.out.println("收到消息了---start"+ body);
        System.out.println("收到消息了---end"+new Date());
        try {
            OrderEntity orderEntity = JSON.parseObject(new String(messageExt.getBody()), OrderEntity.class);
   System.out.println("接收到了标签A || 标签B  消息:" + orderEntity);
        }catch (Exception e){
            e.printStackTrace();

        }

        //如果存在key值则打印key值
        if (messageExt.getKeys() != null){
            System.out.println("key值为:"+messageExt.getKeys());
        }


    }

}

结果

在这里插入图片描述

key值发送消息

在rocketmq中的消息,默认会有一个messageId当做消息的唯一标识,我们也可以给消息携带一个key,用作唯一标识或者业务标识,包括在控制面板查询的时候也可以使用messageId或者key来进行查询

生产者

import com.alibaba.fastjson.JSON;
import com.example.demo.domain.OrderEntity;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.apache.rocketmq.spring.support.RocketMQHeaders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Arrays;
import java.util.Date;
import java.util.List;

/**
 *
 * @date 2024/10/2 16:42
 */
@RestController
@RequestMapping("/rocketmq")
public class ProducerRocketMq {
    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    /**
     * key值发送消息
     */
    @GetMapping("/sendKeyMessage")
    public void sendKeyMessage() {
        OrderEntity order = new OrderEntity(1, 111, 51D, new Date(), "这是一个携带标签B的消息");
        String string = JSON.toJSONString(order);
        //主题上打key值
        Message<String> message = MessageBuilder.withPayload(string)
                .setHeader(RocketMQHeaders.KEYS, "keyValue").build();
        // 主题上打标签发送消息keyA
        rocketMQTemplate.syncSend("key-topic-boot", message);
    }

}

消费者

import com.alibaba.fastjson.JSON;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
 * @date 2024/10/2 16:55
 */
//同步发送消息注解
@Component

//key值过滤
@RocketMQMessageListener(topic ="key-topic-boot", consumerGroup = "group-key")
public class ConsumerListenerRocketMq implements RocketMQListener<MessageExt> {

    //MessageExt类型---是消息的所有内容,没有报错就签收,如果报错就拒收。
    @Override
    public void onMessage(MessageExt messageExt) {
        System.out.println("收到消息了---end"+new Date());
        System.out.println("标签类型---->"+JSON.toJSONString(messageExt.getTags()));
        try {
            OrderEntity orderEntity = JSON.parseObject(new String(messageExt.getBody()), OrderEntity.class);
            System.out.println("接收到了标签A || 标签B  消息:" + orderEntity);
        }catch (Exception e){
            e.printStackTrace();

        }

        //如果存在key值则打印key值
        if (messageExt.getKeys() != null){
            System.out.println("key值为:"+messageExt.getKeys());
        }


    }

}

结果

在这里插入图片描述


消费重试

  • 消费重试前提条件
    • 只有在消息模式为MessageModel.CLUSTERING集群模式时,Broker才会自动进行重试,广播消息是不会重试的
    • 消费失败/消费超时(默认15分钟)或者直接响应ConsumeConcurrentlyStatus.RECONSUME_LATER
  • 重试机制和限制
    • 如果消费者在消费时抛出异常或者返回ConsumeConcurrentlyStatus.RECONSUME_LATER,会将这个消息放入一个重试队列%RETRY%topic(消费者组名),重试间隔时间会使用RocketMQ Broker中的messageDelayLevel=1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h参数,默认重试16次如果没有成功会将这个消息放入死信队列%DLQ%topic(消费者组名)中。

第一种:不使用死信队列
生产者

messageModel:默认值也是MessageModel.CLUSTERING可以不用额外配置
maxReconsumeTimes:重试次数默认-1,在MessageModel.CLUSTERING模式中,-1表示16,消费失败后会重试16次,在MessageModel.BROADCASTING模式中,-1表示整数Integer.MAX_VALUE

import com.alibaba.fastjson.JSON;
import com.example.demo.domain.OrderEntity;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.apache.rocketmq.spring.support.RocketMQHeaders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Arrays;
import java.util.Date;
import java.util.List;

/**
 *
 * @date 2024/10/2 16:42
 */
@RestController
@RequestMapping("/rocketmq")
public class ProducerRocketMq {
    @Autowired
    private RocketMQTemplate rocketMQTemplate;

     /**
     * 重试消费
     */

    @GetMapping("/retryMessage")
    public void retryMessage() {
        OrderEntity order = new OrderEntity(1, 111, 51D, new Date(), "重试消息");
        String string = JSON.toJSONString(order);
        Message<String> message = MessageBuilder.withPayload(string).build();
        rocketMQTemplate.syncSend("retry-topic-boot", message);
    }

}

消费者

package com.example.demo;

import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;

/**
 *
 * @date 2024/10/2 16:55
 */
//同步发送消息注解
@Component


//重试机制
@RocketMQMessageListener(topic ="retry-topic-boot", consumerGroup = "group-retry")
public class ConsumerRetryListenerRocketMq implements RocketMQListener<MessageExt> {

    //MessageExt类型---是消息的所有内容,没有报错就签收,如果报错就拒收。
    @Override
    public void onMessage(MessageExt messageExt) {
       try {
           //处理数据
           handleDb();
       }catch (Exception e){
           int times = messageExt.getReconsumeTimes();
           System.out.println("重试次数:"+times);
           if (times >= 3){
               //将消息记录到特别位置中,比如mysql,文件,人工进行处理
               System.out.println("处理数据失败,需要人工干预处理");
           }else {
               throw new RuntimeException("处理数据失败");
           }
       }
    }

    private void handleDb() {
        int a  = 1/0;
    }

}

结果

在这里插入图片描述

第二种:重试失败进入死信队列

生产者和第一种相同

消费者:

package com.example.demo;

import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.spring.annotation.ConsumeMode;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;

/**
 * 
 * @date 2024/10/2 16:55
 */
//同步发送消息注解
@Component


//重试机制,三次重试
@RocketMQMessageListener(topic ="retry-topic-boot", consumerGroup = "group-retry",
        consumeMode = ConsumeMode.CONCURRENTLY,
        maxReconsumeTimes = 3)
public class ConsumerRetryListenerRocketMq implements RocketMQListener<MessageExt> {

    //MessageExt类型---是消息的所有内容,没有报错就签收,如果报错就拒收。
    @Override
    public void onMessage(MessageExt messageExt) {
        try {
            //处理数据
            handleDb();
        } catch (Exception e) {
            throw new RuntimeException("处理数据失败");
        }
    }

    private void handleDb() {
        int a  = 1/0;
    }
    

}

死信队列监听:

  • 死信特性
  • 死信消息具有以下特性:
    • 不会再被消费者正常消费。
    • 有效期与正常消息相同,均为 3 天,3 天后会被自动删除。因此,请在死信消息产生后的 3 天内及时处理。
    • 一个死信队列对应一个 Group ID, 而不是对应单个消费者实例。
    • 如果一个 Group ID 未产生死信消息,消息队列 RocketMQ 不会为其创建相应的死信队列,一个死信队列包含了对应 Group ID 产生的所有死信消息,不论该消息属于哪个 Topic。
package com.example.demo;

import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.spring.annotation.ConsumeMode;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;

/**
 * @date 2024/10/2 16:55
 */
//同步发送消息注解
@Component


//重试机制
@RocketMQMessageListener(topic = "%DLQ%group-retry", consumerGroup = "group-retry-dead")
public class ConsumerDeadListenerRocketMq implements RocketMQListener<MessageExt> {

    //MessageExt类型---是消息的所有内容,没有报错就签收,如果报错就拒收。
    @Override
    public void onMessage(MessageExt messageExt) {
        System.out.println("收到死信队列中的消息:" + new String(messageExt.getBody()));
            //将消息记录到特别位置中,比如mysql,文件,人工进行处理
            System.out.println("处理数据失败,需要人工干预处理");
    }
}

结果:
在这里插入图片描述

在这里插入图片描述

发送定时消息可指定时间

要使用RocketMQ5.x的自定义时间延时消息必须要使用2.2.3及以上的版本
现在pom 使用的是2.2.2版本的

延时消息存在着一些不足:

  • 1.延时级别只有 18 个,并不能满足所有场景;
  • 2.如果通过修改 messageDelayLevel 配置来自定义延时级别,并不灵活,比如一个在大规模的平台上,延时级别成百上千,而且随时可能增加新的延时时间;
  • 3.延时时间不准确,后台的定时线程可能会因为处理消息量大导致延时误差大。
    为了弥补延时消息的不足,RocketMQ 5.0 引入了定时消息,可指定消息消费时间。

生产者:

    /**
     * 定时发送消息
     */

    @GetMapping("/deliverTimeMessage")
    public void deliverTimeMessage() {
        OrderEntity order = new OrderEntity(1, 111, 51D, new Date(), "定时发送消息");
        String string = JSON.toJSONString(order);
        Message<String> message = MessageBuilder.withPayload(string).build();
        // 延迟15s发送消息
        SendResult sendResult = rocketMQTemplate.syncSendDeliverTimeMills("deliver-topic-boot", message, System.currentTimeMillis() + 1000 * 15);
        System.out.println(sendResult.getSendStatus());
    }

消费者:

package com.example.demo;

import com.alibaba.fastjson.JSON;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
 *
 * @date 2024/10/2 16:55
 */
//同步发送消息注解
@Component

//key值过滤
@RocketMQMessageListener(topic ="deliver-topic-boot", consumerGroup = "group-deliver-boot")
public class ConsumerListenerRocketMq implements RocketMQListener<MessageExt> {

    //MessageExt类型---是消息的所有内容,没有报错就签收,如果报错就拒收。
    @Override
    public void onMessage(MessageExt messageExt) {
        System.out.println("收到消息了---end"+new Date());
        System.out.println("标签类型---->"+JSON.toJSONString(messageExt.getTags()));
        try {
            OrderEntity orderEntity = JSON.parseObject(new String(messageExt.getBody()), OrderEntity.class);
            System.out.println("接收到了 消息:" + orderEntity);
        }catch (Exception e){
            e.printStackTrace();

        }

        //如果存在key值则打印key值
        if (messageExt.getKeys() != null){
            System.out.println("key值为:"+messageExt.getKeys());
        }


    }

}


总结:

生产者配置文件

rocketmq:
#    // 配置 RocketMQ 的 NameServer 地址, 如果是集群的话,使用分号;分隔开
    name-server: 192.168.101.128:9876
    producer:
        group: producer-group
#        发送消息超时时间: 3s
        send-message-timeout: 3000
        #失败重试次数同步次数
        retry-times-when-send-failed: 2
        # 失败重试次数同步次数异步
        retry-times-when-send-async: 2
#        4194304  4M,发送最大消息字节
        max-message-size: 4194304
    #在内部发送失败时是否重试其他代理,这个参数在有多个broker时才生效
        retry-next-server: true
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RocketMQMessageListener {
	// nameServer服务地址,可以直接在注解中指定也可以读取配置文件
    String NAME_SERVER_PLACEHOLDER = "${rocketmq.name-server:}";
    // ACL验证key,服务端开启了ACL时使用,可以直接在注解中指定也可以读取配置文件
    String ACCESS_KEY_PLACEHOLDER = "${rocketmq.consumer.access-key:}";
    // ACL验证密钥,服务端开启了ACL时使用,可以直接在注解中指定也可以读取配置文件
    String SECRET_KEY_PLACEHOLDER = "${rocketmq.consumer.secret-key:}";
    // 自定义的消息轨迹主题
    String TRACE_TOPIC_PLACEHOLDER = "${rocketmq.consumer.customized-trace-topic:}";
    String ACCESS_CHANNEL_PLACEHOLDER = "${rocketmq.access-channel:}";

    // 消费者分组,不同消费者分组名称不能重复
    String consumerGroup();

    // topic名称
    String topic();

    // selectorType 消息选择器类型
    // 默认值 SelectorType.TAG 根据TAG选择
	// 仅支持表达式格式如:“tag1 || tag2 || tag3”,如果表达式为null或者“*”标识订阅所有消息
	// SelectorType.SQL92 根据SQL92表达式选择
    SelectorType selectorType() default SelectorType.TAG;

    /**
     * Control which message can be select. Grammar please see {@link SelectorType#TAG} and {@link SelectorType#SQL92}
     */
    String selectorExpression() default "*";

    // 消费模式,可以选择并发或有序接收消息,默认并发消费 ConsumeMode.CONCURRENTLY
    ConsumeMode consumeMode() default ConsumeMode.CONCURRENTLY;

    // 控制消息模式,可以选择集群和广播,默认集群 MessageModel.CLUSTERING
    // 集群: 消息只会被一个消费者消费 广播:消息被所有消费者都消费一次
    MessageModel messageModel() default MessageModel.CLUSTERING;

    // 消费者最大线程数,在5.x版本该参数已经不推荐使用,因为该实现方式底层线程使用LinkedBlockingQueue作为阻塞队列,队列长度使用Integer.MAX_VALUE。
    @Deprecated
    int consumeThreadMax() default 64;

    // 消费线程数,属于rocketmq-spring-boot-starter 2.2.3新参数,推荐使用该版本
    int consumeThreadNumber() default 20;

    // 消费失败重试次数,在MessageModel.CLUSTERING模式中,-1表示16,消费失败后会重试16次
    int maxReconsumeTimes() default -1;

    // 最大消费时间 默认15分钟
    long consumeTimeout() default 15L;

    // 发送回复消息超时 默认3000毫秒
    int replyTimeout() default 3000;

    // ACL验证key,服务端开启了ACL时使用,可以直接在注解中指定也可以读取配置文件
    String accessKey() default ACCESS_KEY_PLACEHOLDER;

    // ACL验证密钥,服务端开启了ACL时使用,可以直接在注解中指定也可以读取配置文件
    String secretKey() default SECRET_KEY_PLACEHOLDER;

    // 切换消息跟踪的标志实例
    boolean enableMsgTrace() default false;

    // 自定义跟踪主题
    String customizedTraceTopic() default TRACE_TOPIC_PLACEHOLDER;

    // nameServer服务地址,可以直接在注解中指定也可以读取配置文件
    String nameServer() default NAME_SERVER_PLACEHOLDER;

    // The property of "access-channel".
    String accessChannel() default ACCESS_CHANNEL_PLACEHOLDER;
 
    // The property of "tlsEnable" default false.
    String tlsEnable() default "false";

    // 使用者的命名空间
    String namespace() default "";

    // 并发模式下的消息消耗重试策略,下次消费时的延迟级别
    int delayLevelWhenNextConsume() default 0;

    // 以有序模式暂停拉入的间隔,以毫秒为单位。
	// 最小值为10,最大值为30000 默认1000毫秒
    int suspendCurrentQueueTimeMillis() default 1000;

    // 关闭使用者时等待消息消耗的最长时间,以毫秒为单位。
	// 最小值为0 默认1000毫秒
    int awaitTerminationMillisWhenShutdown() default 1000;

    // 实例名称
    String instanceName() default "DEFAULT";
}


10、消息两种模式

集群模式-负载均衡

生产者:

    /**
     * 负载均衡发送消息
     */
    @GetMapping("/balanceMessage")
    public void balanceMessage() {
        for (int i = 1; i <=10; i++) {
            OrderEntity order = new OrderEntity(i, 111, 51D, new Date(), "负载均衡发送消息"+i+"个");
            String string = JSON.toJSONString(order);
            Message<String> message = MessageBuilder.withPayload(string).build();
            rocketMQTemplate.syncSend("balance-topic-boot", message);
        }
    }

消费者:

package com.example.demo;

import com.alibaba.fastjson.JSON;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.spring.annotation.MessageModel;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;

/**
 * @date 2024/10/2 16:55
 */
//同步发送消息注解
@Component


//集群模式
@RocketMQMessageListener(topic ="balance-topic-boot", consumerGroup = "group-balance-boot",
messageModel = MessageModel.CLUSTERING)
public class ConsumerBanlanceAListenerRocketMq implements RocketMQListener<MessageExt> {

    //MessageExt类型---是消息的所有内容,没有报错就签收,如果报错就拒收。
    @Override
    public void onMessage(MessageExt messageExt) {
//        System.out.println("收到消息了---end"+new Date());
//        System.out.println("标签类型---->"+JSON.toJSONString(messageExt.getTags()));
        try {
            OrderEntity orderEntity = JSON.parseObject(new String(messageExt.getBody()), OrderEntity.class);
            System.out.println("消费者1-接收到了 消息:" + orderEntity);
        }catch (Exception e){
            e.printStackTrace();

        }
    }

}

结果:
在这里插入图片描述
在这里插入图片描述

广播模式

生产者:


    /**
     * 广播模式发送消息
     */
    @GetMapping("/broadcastMessage")
    public void broadcastMessage() {
        for (int i = 1; i <= 10; i++) {
            OrderEntity order = new OrderEntity(i, 111, 51D, new Date(), "广播模式发送消息" + i + "个");
            String string = JSON.toJSONString(order);
            Message<String> message = MessageBuilder.withPayload(string).build();
            rocketMQTemplate.syncSend("broadcast-topic-boot", message);
        }
    }

消费者:

package com.example.demo;

import com.alibaba.fastjson.JSON;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.spring.annotation.MessageModel;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;

/**
 * 
 * @date 2024/10/2 16:55
 */
//同步发送消息注解
@Component


//广播模式
@RocketMQMessageListener(topic ="broadcast-topic-boot", consumerGroup = "group-broadcast-boot",
messageModel = MessageModel.BROADCASTING)
public class ConsumerBorad1ListenerRocketMq implements RocketMQListener<MessageExt> {

    //MessageExt类型---是消息的所有内容,没有报错就签收,如果报错就拒收。
    @Override
    public void onMessage(MessageExt messageExt) {
        try {
            OrderEntity orderEntity = JSON.parseObject(new String(messageExt.getBody()), OrderEntity.class);
            System.out.println("广播模式1-接收到了消息:" + orderEntity);
        }catch (Exception e){
            e.printStackTrace();

        }
    }

}

package com.example.demo;

import com.alibaba.fastjson.JSON;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.spring.annotation.MessageModel;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;

/**
 * 
 * @date 2024/10/2 16:55
 */
//同步发送消息注解
@Component


//广播模式
@RocketMQMessageListener(topic ="broadcast-topic-boot", consumerGroup = "group-broadcast-boot",
messageModel = MessageModel.BROADCASTING)
public class ConsumerBorad2ListenerRocketMq implements RocketMQListener<MessageExt> {

    //MessageExt类型---是消息的所有内容,没有报错就签收,如果报错就拒收。
    @Override
    public void onMessage(MessageExt messageExt) {
        try {
            OrderEntity orderEntity = JSON.parseObject(new String(messageExt.getBody()), OrderEntity.class);
            System.out.println("广播模式2-接收到了消息:" + orderEntity);
        }catch (Exception e){
            e.printStackTrace();

        }
    }

}

package com.example.demo;

import com.alibaba.fastjson.JSON;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.spring.annotation.MessageModel;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;

/**
 * 
 * @date 2024/10/2 16:55
 */
//同步发送消息注解
@Component


//广播模式
@RocketMQMessageListener(topic ="broadcast-topic-boot", consumerGroup = "group-broadcast-boot",
messageModel = MessageModel.BROADCASTING)
public class ConsumerBorad3ListenerRocketMq implements RocketMQListener<MessageExt> {

    //MessageExt类型---是消息的所有内容,没有报错就签收,如果报错就拒收。
    @Override
    public void onMessage(MessageExt messageExt) {
        try {
            OrderEntity orderEntity = JSON.parseObject(new String(messageExt.getBody()), OrderEntity.class);
            System.out.println("广播模式3-接收到了消息:" + orderEntity);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

}

package com.example.demo;

import com.alibaba.fastjson.JSON;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.spring.annotation.MessageModel;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;

/**
 * 
 * @date 2024/10/2 16:55
 */
//同步发送消息注解
@Component
//广播模式
@RocketMQMessageListener(topic ="broadcast-topic-boot", consumerGroup = "group-broadcast-boot",
messageModel = MessageModel.BROADCASTING)
public class ConsumerBorad4ListenerRocketMq implements RocketMQListener<MessageExt> {

    //MessageExt类型---是消息的所有内容,没有报错就签收,如果报错就拒收。
    @Override
    public void onMessage(MessageExt messageExt) {
        try {
            OrderEntity orderEntity = JSON.parseObject(new String(messageExt.getBody()), OrderEntity.class);
            System.out.println("广播模式4-接收到了消息:" + orderEntity);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

}

结果:
在这里插入图片描述
在这里插入图片描述
控制面板中,广播模式只负责把消息广播出去,不在管是否已经被消费。

11、如何解决消息堆积

信息没有来得及消费,
一般认为单条队列消息差值>=10w时 算堆积问题

11.1、 什么情况下会出现堆积:

1.生产太快了

  • 生产方可以做业务限流
  • 增加消费者数量,但是消费者数量<=队列数量,适当的设置最大的消费线程数量(根据IO(2n ,n为电脑最大处理器的数量)/CPU(n+1)) 。线程池可以看JUC有讲解。
  • 动态扩容队列数量,从而增加消费者数量(可能会导致重复消费的问题)

在这里插入图片描述

2.消费者消费出现问题

消费者不能消费消息,出现的问题,导致消息积压
排查消费者程序的问题

跳过堆积:
将未被消费的消息直接跳过去,不再进行消费。
在这里插入图片描述
重申消费者点位:

根据重置时间,根据补偿,会再次 消费队列中的消息。
在这里插入图片描述

12、如何确保消息不丢失

在这里插入图片描述
同步落盘:将消息,发送到mq和 磁盘中之后,再返回消息状态。 性能不高,风险低。

异步刷盘:将消息,发送到mq和 buffer(缓冲区-内存)之后,返回消息状态。当缓冲区达到一定大小往磁盘中放数据。性能高,风险大

持久化:log记录在文件或mysql记录到数据库。

在这里插入图片描述

1.生产者使用同步发送模式 ,收到mq的返回确认以后 顺便往自己的数据库里面写
key,createTime ,status=1
2.消费者消费以后 ,通过key,修改数据这条消息的状态 = 2,handleTime(处理时间)
3.写一个定时任务 去查询数据 如果有status = 1 and createTime >1天或者2天,查询出来进行补发,结合幂等操作控制重复问题。

一般是不开启消息轨迹
1.在broker.conf中开启消息追踪
traceTopicEnable=true
在这里插入图片描述

2.重启broker即可

3.生产者配置文件开启消息轨迹
enable-msg-trace: true
在这里插入图片描述

4.消费者开启消息轨迹功能,可以给单独的某一个消费者开启
enableMsgTrace = true

在这里插入图片描述
在rocketmq的面板中可以查看消息轨迹
默认会将消息轨迹的数据存在 RMQ_SYS_TRACE_TOPIC 主题里面

13、安全

1.开启acl的控制 在broker.conf中开启aclEnable=true
在这里插入图片描述
在这里插入图片描述

2.配置账号密码 修改plain_acl.yml

在这里插入图片描述
3.修改控制面板的配置文件 放开52/53行 把49行改为true 上传到服务器的jar包平级目录下即可

在这里插入图片描述
登录rocketmq控制面板,账户密码都是admin,
在这里插入图片描述

在这里插入图片描述

生产者:
在这里插入图片描述
消费者:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值