RabbitMQ基础学习笔记

一、简介:

RabbitMQ是一个实现了AMQP(Advanced Message Queuing Protocol)高级消息队列协议的消息队列服务,用Erlang语言,是面向消息的中间件:相当于一个快递站点,其只负责接收,存储和转发消息数据。

 

二、界面介绍:

RabbitMQ管理界面:

#安装启动
1、下载镜像:
docker pull rabbitmq:3.7.7-management
2、启动镜像(设置账号密码均为guest):
docker run -dit --restart=always --name rabbitmq3.7.7 -e RABBITMQ_DEFAULT_USER=guest -e RABBITMQ_DEFAULT_PASS=guest  -v /home/rabbitmq/data:/var/lib/rabbitmq   -p 15672:15672 -p 5672:5672 rabbitmq:3.7.7-management
3、相关管理界面:
http://服务器地址:15672
4、开启日志:
rabbitmq-plugins enable rabbitmq_tracing

#用户管理:
1、创建账号
rabbitmqctl add_user admin 123
2、设置用户角色
rabitmqctl set_user_tags admin adminstrator
3、设置用户权限
rabbitmqctl set_permissions -p "/" admin ".*" ".*" ".*"
4、查看用户列表
rabbitmqctl list_users

RabbitMQ管理界面操作详情

 

三、执行流程:

生产者(Producer)与消费者(Consumer)和 RabbitMQ 服务(Broker)建立连接, 然后生产者发布消息(Message)同时需要携带交换机(Exchange) 名称以及路由规则(Routing Key),这样消息会到达指定的交换机,然后交换机根据路由规则匹配对应的 Binding,最终将消息发送到匹配的消息队列(Quene),最后 RabbitMQ 服务将队列中的消息投递给订阅了该队列的消费者(消费者也可以主动拉取消息)。

 

四、基础:

1、相关配置信息详解参考:

# base
spring.rabbitmq.host: 服务Host
spring.rabbitmq.port: 服务端口
spring.rabbitmq.username: 登陆用户名
spring.rabbitmq.password: 登陆密码
spring.rabbitmq.virtual-host: 连接到rabbitMQ的vhost
spring.rabbitmq.addresses: 指定client连接到的server的地址,多个以逗号分隔(优先取addresses,然后再取host)
spring.rabbitmq.requested-heartbeat: 指定心跳超时,单位秒,0为不指定;默认60s
spring.rabbitmq.publisher-confirms: 是否启用【发布确认】
spring.rabbitmq.publisher-returns: 是否启用【发布返回】
spring.rabbitmq.connection-timeout: 连接超时,单位毫秒,0表示无穷大,不超时
spring.rabbitmq.parsed-addresses:


# ssl
spring.rabbitmq.ssl.enabled: 是否支持ssl
spring.rabbitmq.ssl.key-store: 指定持有SSL certificate的key store的路径
spring.rabbitmq.ssl.key-store-password: 指定访问key store的密码
spring.rabbitmq.ssl.trust-store: 指定持有SSL certificates的Trust store
spring.rabbitmq.ssl.trust-store-password: 指定访问trust store的密码
spring.rabbitmq.ssl.algorithm: ssl使用的算法,例如,TLSv1.1


# cache
spring.rabbitmq.cache.channel.size: 缓存中保持的channel数量
spring.rabbitmq.cache.channel.checkout-timeout: 当缓存数量被设置时,从缓存中获取一个channel的超时时间,单位毫秒;如果为0,则总是创建一个新channel
spring.rabbitmq.cache.connection.size: 缓存的连接数,只有是CONNECTION模式时生效
spring.rabbitmq.cache.connection.mode: 连接工厂缓存模式:CHANNEL 和 CONNECTION


# listener
spring.rabbitmq.listener.simple.auto-startup: 是否启动时自动启动容器
spring.rabbitmq.listener.simple.acknowledge-mode: 表示消息确认方式,其有三种配置方式,分别是none、manual和auto;默认auto
spring.rabbitmq.listener.simple.concurrency: 最小的消费者数量
spring.rabbitmq.listener.simple.max-concurrency: 最大的消费者数量
spring.rabbitmq.listener.simple.prefetch: 指定一个请求能处理多少个消息,如果有事务的话,必须大于等于transaction数量.
spring.rabbitmq.listener.simple.transaction-size: 指定一个事务处理的消息数量,最好是小于等于prefetch的数量.
spring.rabbitmq.listener.simple.default-requeue-rejected: 决定被拒绝的消息是否重新入队;默认是true(与参数acknowledge-mode有关系)
spring.rabbitmq.listener.simple.idle-event-interval: 多少长时间发布空闲容器时间,单位毫秒

spring.rabbitmq.listener.simple.retry.enabled: 监听重试是否可用
spring.rabbitmq.listener.simple.retry.max-attempts: 最大重试次数
spring.rabbitmq.listener.simple.retry.initial-interval: 第一次和第二次尝试发布或传递消息之间的间隔
spring.rabbitmq.listener.simple.retry.multiplier: 应用于上一重试间隔的乘数
spring.rabbitmq.listener.simple.retry.max-interval: 最大重试时间间隔
spring.rabbitmq.listener.simple.retry.stateless: 重试是有状态or无状态


# template
spring.rabbitmq.template.mandatory: 启用强制信息;默认false
spring.rabbitmq.template.receive-timeout: receive() 操作的超时时间
spring.rabbitmq.template.reply-timeout: sendAndReceive() 操作的超时时间
spring.rabbitmq.template.retry.enabled: 发送重试是否可用
spring.rabbitmq.template.retry.max-attempts: 最大重试次数
spring.rabbitmq.template.retry.initial-interval: 第一次和第二次尝试发布或传递消息之间的间隔
spring.rabbitmq.template.retry.multiplier: 应用于上一重试间隔的乘数
spring.rabbitmq.template.retry.max-interval: 最大重试时间间隔
View Code

 

2、项目搭建:

建工程——改POM——写YML——业务类

(1)、POM:

        <!-- Rabbitmq(amqp)中间件-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <!--MQ消息转换器-->
        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
            <version>2.9.10</version>
        </dependency>    

(2)、YML:

spring:
  rabbitmq:
    #服务Host
    host: xxx.xxx.xxx.xxx
    #服务端口
    port: 5672
    #登陆用户名
    username: guest
    #登陆密码
    password: guest
    #连接到rabbitMQ的vhost
    virtual-host: /

 

3、基于注解方式生产消费:

(1)、生产者:

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void sendMsg2() {
        /**
         * @param exchange 交换机
         * @param routingKey 交换机与队列绑定标识
         * @param msg 消息
         * @param CorrelationData 唯一标识
         * */
        //生成唯一标识
        rabbitTemplate.convertAndSend("TOPIC.EX", "user.save", "topic路由消息,use.save",new CorrelationData(UUID.randomUUID().toString()));
        log.info("主题(topic)交换机模式采用注解方式+配置消息转换器实现,消息发送成功");
    }

(2)、消费者:

@Slf4j
@Component
public class ConsumerListener {

    @RabbitListener(bindings = {@QueueBinding(
            //声明交换机,交换机类型:直接(direct)、主题(topic)、标题(headers)、扇出(fanout)
            //1、扇出(fanout)交换机:Routingkey相同,即交换机与队列绑定相同,一发多收模式
            //2、直接(direct)交换机:Routingkey不同,即交换机与队列绑定不相同,交换机指定某一队列,同一个队列可以与交换机有多种绑定关系
            //3、主题(topic)交换机:Routingkey采用替换符方式声明,相隔用点分开,可实现fanout与direct业务场景
            exchange = @Exchange(name = "TOPIC.EX",type = ExchangeTypes.TOPIC),
            //声明队列
            //指定队列value = @Queue(value ="TEST",durable = "true" ),默认是false,表示消息支持持久化(存储在磁盘上),当消息代理重启时可以接收缓存到队列里的消息
            //不指定队列value = @Queue,表示无法接收缓存到队列里的消息,当消息代理重启时无法接收缓存到队列里的消息,采用此方法可实现多个消费者消费同一个消息
            //value = @Queue
            value = @Queue(value ="TEST",durable = "true" ),
            //声明交换机与队列的绑定关系routingkey
            //主题(topic)routingkey声明必须是一个单词列表,以点号分隔开
            // * 可以代替一个单词
            // # 可以代替零个或多个单词
            key = {"user.save","user.*"})})
    public void recevicel(String message){
        System.out.println("message = " + message);
        log.info("注解方式实现,消息消费成功");
    }
}

(3)、备注(交换机):

交换机详解

 

4、基于配置文件方式生产消费:

(1)、生产者:

    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Test
    public void sendMsg1() {
        rabbitTemplate.convertAndSend("configway.exchange", "user.demo.configway",
                "topic路由消息,user.demo.configway",new CorrelationData(UUID.randomUUID().toString()));
        log.info("主题(topic)交换机模式采用配置文件方式+配置消息转换器实现,消息发送成功");
    }

(2)、配置声明:

import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

//配置文件声明
@Configuration
public class TopicsExchangeConfig {
//    @Bean("configWayExchange")
    @Bean
    public TopicExchange configWayExchange(){
        /**
         * @Param name:交换机名称
         * @Param durable:是否持久化
         * @Param autoDelete:是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。
         * */
        // durable:是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:当前连接有效
        // autoDelete:是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。
        return new TopicExchange("configway.exchange",true,false);
    }

//    @Bean("configWayQueue")
    @Bean
    public Queue configWayQueue(){
        /**
         * @Param name:队列名称
         * @Param durable:是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:当前连接有效
         * @Param exclusive:默认也是false,只能被当前创建的连接使用,而且当连接关闭后队列即被删除。此参考优先级高于durable
         * @Param autoDelete:是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。
         * */
        return new Queue("configWay",true,false,false);
    }

    //声明交换机与队列的绑定关系routingkey
//    @Bean
//    Binding bindingExchangeMessage(@Qualifier("configWayQueue") Queue configWayQueue, @Qualifier("configWayExchange") DirectExchange configWayExchange) {
//        return BindingBuilder.bind(configWayQueue).to(configWayExchange).with("user.demo.configway");
//    }

    @Bean
    public Binding bindingExchangeMessage(Queue configWayQueue, TopicExchange configWayExchange, RabbitAdmin rabbitAdmin) {
        Binding binding = BindingBuilder.bind(configWayQueue).to(configWayExchange).with("user.demo.configway");
        binding.setAdminsThatShouldDeclare(rabbitAdmin);
        return binding;
    }
 
}

(3)、消费者:

@Slf4j
@Component
public class TopicLister {
    @RabbitListener(queues = "configWay")
    public void recevicel(String message){
        log.info("message = " + message);
        log.info("采用配置文件方式+配置消息转换器实现,消息消费成功");
    }
}

 

五、进阶:

1、相关配置:

 (1)、RabbitAdmin配置:

用于对交换机和队列进行管理,用于创建、绑定、删除队列与交换机,发送消息的组件

import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * RabbitAdmin配置
 * 用于对交换机和队列进行管理,用于创建、绑定、删除队列与交换机,发送消息的组件
 */
@Configuration
public class RabbitAdminConfig {

    @Value("${spring.rabbitmq.host}")
    private String host;
    @Value("${spring.rabbitmq.username}")
    private String username;
    @Value("${spring.rabbitmq.password}")
    private String password;
    @Value("${spring.rabbitmq.virtualhost}")
    private String virtualhost;

    @Bean
    public ConnectionFactory connectionFactory() {
        CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
        connectionFactory.setAddresses(host);
        connectionFactory.setUsername(username);
        connectionFactory.setPassword(password);
        connectionFactory.setVirtualHost(virtualhost);
        return connectionFactory;
    }

    @Bean
    public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
        RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
        rabbitAdmin.setAutoStartup(true);
        return rabbitAdmin;
    }


}

(2)、生产者配置:

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.batch.BatchingStrategy;
import org.springframework.amqp.rabbit.batch.SimpleBatchingStrategy;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.BatchingRabbitTemplate;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ConcurrentTaskScheduler;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;


//生产者配置
@Slf4j
@Configuration
public class RabbitmqProducersConfig {

    @Resource
    private CachingConnectionFactory connectionFactory;//RabbitMQ连接池

    /**
     * 配置发布确认机制,避免消息丢失
     * 方案一:RabbitMQ 事务机制:吞吐量低,消耗性能
     * 流程:
     * 生产者发送数据之前开启RabbitMQ 事务 channel.txSelect() ,然后发送消息,
     * 如果消息没有成功被 RabbitMQ 接收到,那么生产者会收到异常报错,此时就可以回滚事务 channel.txRollback() ,然后重试发送消息;
     * 如果收到了消息,那么可以提交事务 channel.txCommit()
     *
     * 方案二:confirm机制(常用):
     * 1、单个confirm 确认模式:每发送一条消息后,调用channel.waitForConfirms() 方法,不足:吞吐量低
     * 2、批量confirm 确认模式:每发送一批消息后,调用channel.waitForConfirms() 方法,不足:当发生故障导致发布出现问题时,无法确认哪个消息出现问题
     * 3、异步confirm 确认模式:提供消息进入到Exchange触发回调setConfirmCallback()与消息未送达队列触发回调方法setReturnCallback()
     *
     * */
    @Bean
    public RabbitTemplate rabbitTemplate(){
        // 回调机制配置
        //是否启用【发布确认】
        connectionFactory.setPublisherConfirms(true);
        connectionFactory.setPublisherConfirmType(CachingConnectionFactory.ConfirmType.CORRELATED);
        //是否启用【发布返回】
        connectionFactory.setPublisherReturns(true);
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        //触发setReturnCallback回调必须设置mandatory=true, 否则Exchange没有找到Queue就会丢弃掉消息, 而不会触发回调
        rabbitTemplate.setMandatory(true);
        //消息进入到Exchange触发回调
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                log.info("消息发送成功:correlationData({}),ack({}),cause({})",correlationData,ack,cause);
                //发送确认结果
                if (correlationData!=null && !ObjectUtils.isEmpty(correlationData.getId())){
                    if (ack) {
                        //消息投递成功,记录correlationData.getId()投递成功日志
                        //记录数据的相关业务逻辑
                        log.info("生产者发送消息投递成功结果");
                    } else {
                        //消息投递失败,记录correlationData.getId()投递失败日志
                        //记录数据的相关业务逻辑
                        log.info("生产者发送消息投递失败结果");
                    }
                }
            }
        });
        //消息未送达队列触发回调
        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
            @Override
            public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
                log.warn("消息丢失:exchange({}),route({}),replyCode({}),replyText({}),message:{}",exchange,routingKey,replyCode,replyText,message);
                // 回退的信息,可做补偿机制
            }
        });
        return rabbitTemplate;
    }

    /**
     * 配置批量发布机制
     * Spring-AMQP 通过 BatchingRabbitTemplate 提供批量发送消息的功能。如下是三个条件,满足任一即会批量发送:
     * <p>
     * 【数量】batchSize :超过收集的消息数量的最大条数。
     * 【空间】bufferLimit :超过收集的消息占用的最大内存。
     * 【时间】timeout :超过收集的时间的最大等待时长,单位:毫秒。
     * 不过要注意,这里的超时开始计时的时间,是以最后一次发送时间为起点。也就说,每调用一次发送消息,都以当前时刻开始计时,
     * 重新到达 timeout 毫秒才算超时。
     *
     * @return BatchingRabbitTemplate
     */
    @Bean
    public BatchingRabbitTemplate batchRabbitTemplate() {
        // 创建 BatchingStrategy 对象,代表批量策略
        // 超过收集的消息数量的最大条数。
        int batchSize = 10; // 例:每次发送10条,记为a
        // 每次批量发送消息的最大内存 b
        int bufferLimit = 1024 * 1024;
        // 超过收集的时间的最大等待时长,单位:毫秒
        int timeout = 10 * 1000; // 例:不足10条时,等待10秒继续发送
        BatchingStrategy batchingStrategy = new SimpleBatchingStrategy(batchSize, bufferLimit, timeout);
        // 创建 TaskScheduler 对象,用于实现超时发送的定时器
        TaskScheduler taskScheduler = new ConcurrentTaskScheduler();
        // 创建 BatchingRabbitTemplate 对象
        BatchingRabbitTemplate batchTemplate = new BatchingRabbitTemplate(batchingStrategy, taskScheduler);
        batchTemplate.setConnectionFactory(connectionFactory);
        return batchTemplate;
    }

}

(3)、消费者配置:

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.AcknowledgeMode;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.amqp.SimpleRabbitListenerContainerFactoryConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.Resource;

//消费者配置
@Slf4j
@Configuration
public class RabbitmqConsumersConfig {
    @Resource
    private CachingConnectionFactory connectionFactory;

    @Autowired
    private SimpleRabbitListenerContainerFactoryConfigurer factoryConfigurer;

    //单一消费者
    @Bean(name = "singleListenerContainer")
    public SimpleRabbitListenerContainerFactory listenerContainer(){
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        //消息序列化类型
        factory.setMessageConverter(new Jackson2JsonMessageConverter());//避免rabbitmq界面上消息乱码
        //消费者并发消费线程
        factory.setConcurrentConsumers(1);//并发线程数
        factory.setMaxConcurrentConsumers(1);//最大并发线程数
        //设置预取值,0则是轮询公平分配模式(默认),1则是能者多劳配置模式,n(n>1)则是消费者允许的最大未确认的消息数量(可堆积量)
        factory.setPrefetchCount(1);
        //设置消费端手动ack确认,用于配置消费者消息确认机制
        //AcknowledgeMode.NONE:不发送确认
        //AcknowledgeMode.MANUAL:配置需要通过手动调用来确认所有消息Channel.basicAck()
        //AcknowledgeMode.AUTO:配置由容器自动确认消息,除非MessageListener抛出异常。在原配置的基础上需要添加重试配置
        factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);//三种配置方式,分别是none、manual和auto;默认auto
        factory.setDefaultRequeueRejected(true);//消息拒绝接收,重新进入消费队列中

        return factory;
    }

    //并发消费
    @Bean("multiListenerContainer")
    public SimpleRabbitListenerContainerFactory multiQueueRabbitListenerContainerFactory() {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factoryConfigurer.configure(factory,connectionFactory);
        factory.setMessageConverter(new Jackson2JsonMessageConverter());
        //消息确认机制,手动确认ack
        factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
        //开启消费者批量消费消息的监听器
        factory.setBatchListener(true);
        //开启批量处理配置
        //true:启用分批处理;false:禁止分批处理
        //if consumerBatchEnabled is true , deBatchingEnabled will be true
        //factory.setDeBatchingEnabled(true);
        factory.setConsumerBatchEnabled(true);
        //消费者并发消费线程
        factory.setConcurrentConsumers(5);//并发线程数,可作为一次并发批量消费的量
        factory.setMaxConcurrentConsumers(10);//最大并发线程数
        //设置预取值,0则是轮询公平分配模式(默认),1则是能者多劳配置模式,n(n>1)则是设定消费者允许的最大未确认的消息数量(可堆积量)
        factory.setPrefetchCount(10);
        return factory;
    }

    //批量消费
    @Bean("batchListenerContainer")
    public SimpleRabbitListenerContainerFactory batchQueueRabbitListenerContainerFactory() {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factoryConfigurer.configure(factory,connectionFactory);
        factory.setMessageConverter(new Jackson2JsonMessageConverter());
        //消息确认机制,手动确认ack
//        factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
        //开启消费者批量消费消息的监听器
        factory.setBatchListener(true);
        //开启批量处理配置
        //true:启用分批处理;false:禁止分批处理
        //if consumerBatchEnabled is true , deBatchingEnabled will be true
        //factory.setDeBatchingEnabled(true);
        factory.setConsumerBatchEnabled(true);
        //一次批量监听消费大小
        //结合BatchingRabbitTemplate配置批量发送条数,将其配置的数量压缩成1条消息,记为a,同时结合一次批量监听消费配置的大小,记为b,
        //解析后,每次返回的List的大小为a*b
        factory.setBatchSize(20);//记为b
        // 等待时间 毫秒 ,指单个消息的等待时间
        // 也就是说极端情况下,你将会等待 BatchSize * ReceiveTimeout 的时间才会收到消息
        factory.setReceiveTimeout(10 * 1000L);
        return factory;
    }


}

 

2、消息发布确认机制:

(1)、生产者:

配置发布确认机制,避免消息丢失

方案一:RabbitMQ 事务机制:吞吐量低,消耗性能

流程: 生产者发送数据之前开启RabbitMQ 事务 channel.txSelect() ,然后发送消息, 如果消息没有成功被 RabbitMQ 接收到,那么生产者会收到异常报错,此时就可以回滚事务 channel.txRollback() ,然后重试发送消息; 如果收到了消息,那么可以提交事务 channel.txCommit()

方案二:confirm机制(常用):

1、单个confirm 确认模式:每发送一条消息后,调用channel.waitForConfirms() 方法,不足:吞吐量低

2、批量confirm 确认模式:每发送一批消息后,调用channel.waitForConfirms() 方法,不足:当发生故障导致发布出现问题时,无法确认哪个消息出现问题

3、异步confirm 确认模式:提供消息进入到Exchange触发回调setConfirmCallback()与消息未送达队列触发回调方法setReturnCallback()

    @Autowired
    private RabbitTemplate rabbitTemplate;//已开启confirm机制
    
    @Test
    public void sendMsg3() {
        //单个消息的发布确认模式
        rabbitTemplate.convertAndSend("TOPIC.ONLY","user.only","生产者单个发送");
        log.info("主题(topic)交换机模式采用单个发送————消息确认机制(发布确认+消费确认)实现,消息发送成功");
    }

(2)、消费者:

为确保消息消费成功,需设置消费者消息确认机制,采用手动ack确认,如果消费失败或异常了,可做补偿机制。

import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.io.IOException;

//发布确认模式
@Slf4j
@Component
public class AdvancedOnlyConsumerListener {
    //重试次数设置
    int i = 0;
    @RabbitListener(bindings = {@QueueBinding(
            exchange = @Exchange(name = "TOPIC.ONLY",type = ExchangeTypes.TOPIC),
            value = @Queue(value ="ONLYTEST",durable = "true" ),
            key = {"user.only"})},
            //消费者消息确认机制,需要设置消费端为手动ack确认(AcknowledgeMode)
            containerFactory ="singleListenerContainer",
            ackMode = "MANUAL") //手动模式
    public void recevicelOnly(String msg, Channel channel, Message message) throws IOException {
        // 打印线程名
        String name = Thread.currentThread().getName();
        try {
            // 消费消息
            log.info("线程{}消息消费{}成功",name,msg);
            // 判断消息是否正常后应答确认
            if(true) {
                /**
                 * deliveryTag:表示消息投递序号,每次消费消息或者消息重新投递后,deliveryTag都会增加
                 * multiple:是否批量确认,值为 true 则会一次性 ack所有小于当前消息 deliveryTag 的消息。
                 */
                channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
                log.info("发布确认方式,单个消息消费成功");
            }
//            throw new RuntimeException("来个异常");//测试是否异常时重回队列
        } catch (Exception e) {
            e.printStackTrace();
            i++;
            if(i<3) {
                log.info("消息消费异常,重回队列");
                /**
                 * deliveryTag:表示消息投递序号。
                 * multiple:是否批量确认。
                 * requeue:值为 true 消息将重新入队列。
                 */
                channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
            }else {
                log.info("重试次数过多,避免消息堆积,丢弃消息");
                /**
                 * 消息拒绝
                 * deliveryTag:表示消息投递序号。
                 * requeue:值为 true 消息将重新入队列。
                 */
                channel.basicReject(message.getMessageProperties().getDeliveryTag(),false);
                i = 0;
            }
        }
    }

}

 

3、批量发送:

1)、生产者批量发送:

1)、生产者:

    @Resource
    private BatchingRabbitTemplate batchingRabbitTemplate;

    @Test
    public void sendMsg4() {
        //生产者批量发送
        for (int i = 0; i < 30; i++) {
            batchingRabbitTemplate.convertAndSend("TOPIC.PRODUCER","user.batch.producer","生产者批量发送"+i);
        }
        log.info("主题(topic)交换机模式采用生产者批量发送模式进行消费实现,消息发送成功");
    }

2)、消费者:

    //生产者批量发送模式
    @RabbitListener(bindings = {@QueueBinding(
            exchange = @Exchange(name = "TOPIC.PRODUCER", type = ExchangeTypes.TOPIC),
            value = @Queue(value = "PATCHPRODUCERTEST", durable = "true"),
            key = {"user.batch.producer"})}
    )
    public void produceRecevicelBatch(Message message) throws IOException{
        log.info("生产者批量发布消息,当前线程{}消息:{}",Thread.currentThread().getName(),
                new String(message.getBody(), "UTF-8"));
    }

 

2)、消费者并发处理:

1)、生产者:

    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    @Test
    public void sendMsg5() {
        //消费者并发消费
        for (int i = 0; i < 30; i++) {
            rabbitTemplate.convertAndSend("TOPIC.CONSUMER","user.batch.consumer","生产者批量发送"+i);
        }
        log.info("主题(topic)交换机模式采用消费者批量消费模式————消息确认机制(发布确认+消费确认)并发进行并发消费实现,消息发送成功");
    }

2)、消费者:

    //消费者并发消费模式
    int i = 0;
    @RabbitListener(bindings = {@QueueBinding(
            exchange = @Exchange(name = "TOPIC.CONSUMER", type = ExchangeTypes.TOPIC),
            value = @Queue(value = "PATCHCONSUMERTEST", durable = "true"),
            key = {"user.batch.consumer"})},
            //消费者消息确认机制,需要设置消费端为手动ack确认(AcknowledgeMode)
            containerFactory ="multiListenerContainer",
            ackMode = "MANUAL")
    public void consumerRecevicelBatch(List<Message> messages, Channel channel)throws IOException{
        for (Message msg: messages) {
            try {
                //毫秒
                TimeUnit.MILLISECONDS.sleep(1000);
                log.info("消费者批量消费消息,当前时间{}消息:{}",new Date(),new String(msg.getBody(), "UTF-8"));
//                String a =new String(msg.getBody(), "UTF-8");
                if(true){
//                    if("生产者批量发送2".equals(a)){
//                        throw new IOException("来个异常");//测试是否异常时重回队列
//                    }
                    channel.basicAck(msg.getMessageProperties().getDeliveryTag(), true);
                    log.info("消息消费成功");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
                //可以采用外部初始化一个List集合记录msg.getMessageProperties().getDeliveryTag()消息标识,
                //再进行批量重新入队channel.basicNack(msg.getMessageProperties().getDeliveryTag(), true, true);
                i++;
                if(i<3) {
                    log.info("消费者消费异常,重回队列");
                    /**
                     * deliveryTag:表示消息投递序号。
                     * multiple:是否批量确认。
                     * requeue:值为 true 消息将重新入队列。
                     */
                    channel.basicNack(msg.getMessageProperties().getDeliveryTag(), false, true);
                }else {
                    log.info("重试次数过多,避免消息堆积,丢弃消息");
                    /**
                     * 消息拒绝
                     * deliveryTag:表示消息投递序号。
                     * requeue:值为 true 消息将重新入队列。
                     */
                    channel.basicReject(msg.getMessageProperties().getDeliveryTag(),false);
                    i = 0;
                }
            }
        }
    }

 

3)、消费者批量消费:

1)、生产者:

    @Resource
    private BatchingRabbitTemplate batchingRabbitTemplate;
    
    @Test
    public void sendMsg6() {
        //批量处理(生产者批量发布,消费者批量消费)
        for (int i = 0; i < 1000; i++) {
            batchingRabbitTemplate.convertAndSend("TOPIC.PATCH","user.batch","生产者批量发送"+i);
        }
        log.info("主题(topic)交换机模式采用生产者批量发布+消费者批量消费模式————消息确认机制(发布确认+消费确认)并发进行批量消费实现,消息发送成功");
    }

2)、消费者:

    //生产者批量发布+消费者批量消费模式
    @RabbitListener(bindings = {@QueueBinding(
            exchange = @Exchange(name = "TOPIC.PATCH", type = ExchangeTypes.TOPIC),
            value = @Queue(value = "PATCHTEST", durable = "true"),
            key = {"user.batch"})},
            //消费者消息确认机制,需要设置消费端为手动ack确认(AcknowledgeMode)
            containerFactory ="batchListenerContainer"
            //ackMode = "MANUAL"
    )
    public void recevicelBatch(Channel channel, List<Message> messages) {
        log.info("batch.queue.consumer 收到{}条message", messages.size());
        if(messages.size()>0){
            log.info("第一条数据是: {}", new String(messages.get(1).getBody()));
        }
    }

 

4、延时队列:

消息发送后使消费者延迟接收,需要延时队列插件(delayed_message_exchange)实现延迟

相关插件参考 密码:u2gu

1、解压:
unzip rabbitmq_delayed_message_exchange-20171201-3.7.x.zip 
2、拷贝:
docker cp rabbitmq_delayed_message_exchange-20171201-3.7.x.ez rabbitmq3.7.7:/plugins
3、查看启动容器信息:
docker ps
4、开启进入终端:
docker exec -it 镜像ID /bin/bash
5、查看插件列表:
rabbitmq-plugins list
6、启动插件:
rabbitmq-plugins enable rabbitmq_delayed_message_exchange

1)、相关场景:

1)、订单在十分钟之内未支付则自动取消

2)、新创建的店铺,如果在十天内都没有上传过商品,则自动发送消息提醒。

3)、用户注册成功后,如果三天内没有登陆则进行短信提醒。

4)、用户发起退款,如果三天内没有得到处理则通知相关运营人员。

5)、预定会议后,需要在预定的时间点前十分钟通知各个与会人员参加会议等等

 (2)、生产者:

    @Autowired
    private DelayedQueueUtil delayedQueueUtil;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void sendMsg7() {
        /**
         * 发送延迟队列
         * @param queueName 队列名称
         * @param params 消息内容
         * @param expiration 延迟时间 毫秒
         */
        //方式一
        delayedQueueUtil.sendDelayedQueue("delayTest","延时队列——生产者单个发送",5000);
        log.info("主题(topic)交换机模式采用延时队列——生产者单个发送——消息确认机制(发布确认+消费确认)实现,消息发送成功");

        //方式二
        rabbitTemplate.convertAndSend("DELAYED.EA", "delayed.ra", "延时队列——采用配置文件方式单个发送", msg -> {
            // 发送消息的时候 延迟时长
            msg.getMessageProperties().setDelay(10000);
            return msg;
        });
    }

(3)、消费者:

方式一:

1)、延时队列工具类:

import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.CustomExchange;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;


/**
 * 延迟队列工具类
 * 消息发送后使消费者延迟接收
 * 1、订单在十分钟之内未支付则自动取消
 * 2、新创建的店铺,如果在十天内都没有上传过商品,则自动发送消息提醒。
 * 3、用户注册成功后,如果三天内没有登陆则进行短信提醒。
 * 4、用户发起退款,如果三天内没有得到处理则通知相关运营人员。
 * 5、预定会议后,需要在预定的时间点前十分钟通知各个与会人员参加会议
 * 等等
 * */
@Component
public class DelayedQueueUtil {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Resource
    private RabbitAdmin rabbitAdmin;

    // routingKey
    private static final String DELAYED_ROUTING_KEY = "delayed.routingkey";
    // 延迟队列交换机
    private static final String DELAYED_EXCHANGE = "delayed.exchange";


    /**
     * 发送延迟队列
     * @param queueName 队列名称
     * @param params 消息内容
     * @param expiration 延迟时间 毫秒
     */
    public void sendDelayedQueue(String queueName, Object params, Integer expiration) {
        // 创建延迟队列交换机
        /**
         * ---------------------------------创建延迟队列交换机---------------------------------------------
         */
        CustomExchange customExchange = createCustomExchange();
        rabbitAdmin.declareExchange(customExchange);

        /**
         * ---------------------------------创建延迟队列--------------------------------------------
         */
        Queue queue = new Queue(queueName);
        rabbitAdmin.declareQueue(queue);

        /**
         * ---------------------------------队列绑定延迟队列交换机--------------------------------------------
         */
        Binding binding = BindingBuilder.bind(queue).to(customExchange).with(DELAYED_ROUTING_KEY).noargs();
        rabbitAdmin.declareBinding(binding);

        /**
         * ---------------------------------发送延迟消息---------------------------------------------
         */
        rabbitTemplate.convertAndSend(DELAYED_EXCHANGE, DELAYED_ROUTING_KEY, params, msg -> {
            // 发送消息的时候 延迟时长
            msg.getMessageProperties().setDelay(expiration);
            return msg;
        });
    }

    public CustomExchange createCustomExchange() {

        Map<String, Object> arguments = new HashMap<>();
        /**
         * 参数说明:
         * 1.交换机的名称
         * 2.交换机的类型
         * 3.是否需要持久化
         * 4.是否自动删除
         * 5.其它参数
         */
        arguments.put("x-delayed-type", "direct");
        return new CustomExchange(DELAYED_EXCHANGE,"x-delayed-message", true, false, arguments);
    }

}

2)、消费:

    @RabbitListener(queuesToDeclare = @Queue(value = "delayTest",durable = "true"),
            //消费者消息确认机制,需要设置消费端为手动ack确认(AcknowledgeMode)
            containerFactory ="singleListenerContainer",
            ackMode = "MANUAL")
    public void recevicelDelayed(String msg, Channel channel, Message message) throws IOException {
        // 打印线程名
        String name = Thread.currentThread().getName();
        try {
            // 消费消息
            log.info("线程{}延时队列消息消费{}成功",name,msg);
            if(true) {
                /**
                 * deliveryTag:表示消息投递序号,每次消费消息或者消息重新投递后,deliveryTag都会增加
                 * multiple:是否批量确认,值为 true 则会一次性 ack所有小于当前消息 deliveryTag 的消息。
                 */
                channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
                log.info("延时队列消费成功");
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.info("消息消费异常,重回队列,可以结合SQL记录并设置重试次数,避免消息堆积");
            /**
             * deliveryTag:表示消息投递序号。
             * multiple:是否批量确认。
             * requeue:值为 true 消息将重新入队列。
             */
            channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,true);
        }
    }

方式二:

1)、配置类声明:

import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;


/**
 * 延迟队列配置类
 * 消息发送后使消费者延迟接收
 * 1、订单在十分钟之内未支付则自动取消
 * 2、新创建的店铺,如果在十天内都没有上传过商品,则自动发送消息提醒。
 * 3、用户注册成功后,如果三天内没有登陆则进行短信提醒。
 * 4、用户发起退款,如果三天内没有得到处理则通知相关运营人员。
 * 5、预定会议后,需要在预定的时间点前十分钟通知各个与会人员参加会议
 * 等等
 * */
@Component
public class DelayedQueueConfig {
    private static final String DELAYED_EXCHANGE = "DELAYED.EA";
    private static final String DELAYED_QUEUE = "DELAYEDQA";
    private static final String DELAYED_ROUTING_KEY = "delayed.ra";

    /**
     * ---------------------------------创建交换机---------------------------------------------
     */
    @Bean
    public CustomExchange directExchangeDELAYED(){
        Map<String, Object> arguments = new HashMap<>();
        /**
         * 参数说明:
         * 1.交换机的名称
         * 2.交换机的类型
         * 3.是否需要持久化
         * 4.是否自动删除
         * 5.其它参数
         */
        arguments.put("x-delayed-type", "direct");
        return new CustomExchange(DELAYED_EXCHANGE,"x-delayed-message", true, false, arguments);

    }

    /**
     * ---------------------------------创建优先级队列---------------------------------------------
     */
    @Bean
    public Queue queueDELAYED(){
        return QueueBuilder.durable(DELAYED_QUEUE).build();
    }

    /**
     * ---------------------------------绑定优先级队列关系routingkey---------------------------------------------
     */
    @Bean
    Binding bindingDirectDELAYED(Queue queueDELAYED, CustomExchange directExchangeDELAYED){
        return BindingBuilder.bind(queueDELAYED).to(directExchangeDELAYED).with(DELAYED_ROUTING_KEY).noargs();
    }

}

2)、消费:

    @RabbitListener(queues = "DELAYEDQA")
    public void recevicelDelayedQA(String msg){
            log.info("延时队列消息消费{}成功",msg);
    }

 

5、TTL队列:

发送消息指定其过期时间,从消息入队列开始计算,只要超过队列配置的超时时间,消息没被接收,就会自动清除(或者进入死信)。当消息未过期中可以使用 `channel.basicNack()`方法重新将消息放回到队列中,但此时消息的TTL不会重新计算,而是继续之前的计时器。

(1)、生产者:

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void sendMsg8() {
        rabbitTemplate.convertAndSend("TTL.DIRECT.EXA", "ttl.qa", "TTL队列——生产者QA单个发送");
    }

(2)、配置类声明:

/**
 * TTL队列配置类
 * 指定消息的过期时间,从消息入队列开始计算,只要超过队列的超时时间配置,消息没被接收,消息就会自动清除
 */
@Configuration
public class TtlQueueConfig {

    /**
     * ---------------------------------创建ttl交换机---------------------------------------------
     */
    @Bean
    public DirectExchange directExchangeTTL(){
        // durable:是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:当前连接有效
        // exclusive:默认也是false,只能被当前创建的连接使用,而且当连接关闭后队列即被删除。此参考优先级高于durable
        // autoDelete:是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。
        return new DirectExchange("TTL.DIRECT.EXA",true,false);
    }

    /**
     * ---------------------------------创建ttl队列---------------------------------------------
     */
    @Bean
    public Queue queueTTL(){
        //x-message-ttl:TTL队列过期时间参数
        return QueueBuilder.durable("TTLQA").ttl(5000).build();
    }

    /**
     * ---------------------------------创建交换机与队列的绑定关系routingkey---------------------------------------------
     */
    @Bean
    Binding bindingDirectTTL(Queue queueTTL,DirectExchange directExchangeTTL){
        return BindingBuilder.bind(queueTTL).to(directExchangeTTL).with("ttl.qa");
    }

}

(3)、消费者:

    @RabbitListener(queues = "TTLQA",
            //消费者消息确认机制,需要设置消费端为手动ack确认(AcknowledgeMode)
            containerFactory ="singleListenerContainer",
            ackMode = "MANUAL")
    public void recevicelTtl(String msg, Channel channel, Message message) throws IOException {
        try {
            // 消费消息
            //超过队列的超时时间配置,消息没被接收,消息就会自动清除
            log.info("TTL队列消息QA消费{}成功",msg);
//            throw new RuntimeException("抛出一个异常");
            if(true) {
                /**
                 * deliveryTag:表示消息投递序号,每次消费消息或者消息重新投递后,deliveryTag都会增加
                 * multiple:是否批量确认,值为 true 则会一次性 ack所有小于当前消息 deliveryTag 的消息。
                 */
                channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
                log.info("TTL队列QA消费成功");
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.info("QA消息消费异常,重回队列,可以结合SQL记录并设置重试次数,避免消息堆积");
            //重新将消息放回到队列中,但此时消息的TTL不会重新计算,而是继续之前的计时器。
            /**
             * deliveryTag:表示消息投递序号。
             * multiple:是否批量确认。
             * requeue:值为 true 消息将重新入队列。
             */
            channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,true);
        }
    }

 

6、TTL与死信队列:

队列消息变成死信(deadmessage)后,能够被重新被发送到另一个交换器中,即DLX(Dead-Letter-Exchange),其绑定DLX的队列就称为死信队列

(1)、消息变成死信的三种情况:

1)、消息被拒绝(channel.basicReject()/ channel.basicNack())并且requeue=false,即不允许重新入队

2)、消息TTL过期

3)、队列达到最大长度

(2)、生产者:

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void sendMsg9() {
        rabbitTemplate.convertAndSend("DEAD.TTL.EA", "dead.ttl.ra", "TTL+死信队列单个发送");

    }

(3)、配置类声明:

import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;


/**
 * 死信队列配置类
 * 队列消息变成死信(deadmessage)之后,它能被重新被发送到另一个交换器中,这个交换器就是DLX(Dead-Letter-Exchange),绑定DLX的队列就称之为死信队列
 * 消息变成死信的几种情况:
 * 1. 消息被拒绝(channel.basicReject()/ channel.basicNack())并且requeue=false,即不允许重新入队
 * 2. 消息TTL过期
 * 3. 队列达到最大长度
 *
 * */
@Component
public class DLXQueueConfig {

    //死信队列
    private static final String DEAD_EXCHANGE = "DEAD.EA";
    private static final String DEAD_QUEUE = "DEADQA";
    private static final String DEAD_ROUTING_KEY = "dead.ra";
    //TTL队列
    private static final String TTL_EXCHANGE = "DEAD.TTL.EA";
    private static final String TTL_QUEUE = "DEADTTLQA";
    private static final String TTL_ROUTING_KEY = "dead.ttl.ra";


    /**
     * ---------------------------------创建死信交换机---------------------------------------------
     */
    @Bean
    public DirectExchange directExchangeDEAD(){
        return new DirectExchange(DEAD_EXCHANGE,true,false);
    }

    /**
     * ---------------------------------创建死信队列---------------------------------------------
     */
    @Bean
    public Queue queueDEAD(){
        return QueueBuilder.durable(DEAD_QUEUE).build();
    }

    /**
     * ---------------------------------绑定死信队列关系routingkey---------------------------------------------
     */
    @Bean
    Binding bindingDirectDEAD(Queue queueDEAD, DirectExchange directExchangeDEAD){
        return BindingBuilder.bind(queueDEAD).to(directExchangeDEAD).with(DEAD_ROUTING_KEY);
    }

//================================================================================================================================

    /**
     * ---------------------------------创建TTL交换机---------------------------------------------
     */
    @Bean
    public DirectExchange directExchangeDeadTTL(){
        return new DirectExchange(TTL_EXCHANGE,true,false);
    }

    /**
     * ---------------------------------创建死信队列---------------------------------------------
     */
    @Bean
    public Queue queueDeadTTL(){
        return QueueBuilder.durable(TTL_QUEUE)
                //x-message-ttl:TTL过期时间设置
                .ttl(5000)
                //x-dead-letter-exchange:设置死信交换机
                .deadLetterExchange(DEAD_EXCHANGE)
                //x-dead-letter-routing-key:设置死信交换器路由键
                .deadLetterRoutingKey(DEAD_ROUTING_KEY)
                .build();
    }

    /**
     * ---------------------------------绑定死信队列关系routingkey---------------------------------------------
     */
    @Bean
    Binding bindingDirectDeadTTL(Queue queueDeadTTL, DirectExchange directExchangeDeadTTL){
        return BindingBuilder.bind(queueDeadTTL).to(directExchangeDeadTTL).with(TTL_ROUTING_KEY);
    }

}

(4)、消费者:

    //TTL队列
    @RabbitListener(queues = "DEADTTLQA",
            //消费者消息确认机制,需要设置消费端为手动ack确认(AcknowledgeMode)
            containerFactory ="singleListenerContainer",
            ackMode = "MANUAL")
    public void recevicelTtl(String msg, Channel channel, Message message) throws IOException {
        try {
            // 消费消息
            //超过队列的超时时间配置,消息没被接收,消息就会自动清除
            log.info("TTL队列消息QA消费:{}成功",msg);
//            throw new RuntimeException("抛出一个异常");
            if(true) {
                /**
                 * deliveryTag:表示消息投递序号,每次消费消息或者消息重新投递后,deliveryTag都会增加
                 * multiple:是否批量确认,值为 true 则会一次性 ack所有小于当前消息 deliveryTag 的消息。
                 */
                channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
                log.info("ttl队列QA消费成功");
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.info("QA消息消费异常,重回队列,可以结合SQL记录并设置重试次数,避免消息堆积");
            //重新将消息放回到队列中,但此时消息的TTL不会重新计算,而是继续之前的计时器。
            /**
             * deliveryTag:表示消息投递序号。
             * multiple:是否批量确认。
             * requeue:值为 true 消息将重新入队列。
             */
            channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,true);
        }
    }

    //死信队列
    @RabbitListener(queues = "DEADQA")
    public void recevicelTTLDead(Message message) throws IOException {
        log.info("获取TTL过期的死信消息:"+new String(message.getBody(), "UTF-8"));
    }

 

7、优先级队列:

通过配置优先级给不同业务场景下的消息提供提前消费的特权。

RabbitMQ优先级数值大小范围为【0-255】,优先级数值越高就优先处理,为考虑服务器的硬件性能问题,一般设置的数值会在0-10】之间

(1)、生产者:

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void sendMsg10() {
        for (int i = 0; i < 9; i++) {
            //程序启动时,会创建了一个交换机和队列,由于此时队列是空的,第一条消息,即优先级最小的那条消息进入队列后,还没有来得及排序,就立马就被消费者消费了
            rabbitTemplate.convertAndSend("PRIORITY.EA","priority.ra","生产者发送优先级" + i, messagePostProcessor(i));
        }
        log.info("优先级队列消息发送成功");
    }

    private MessagePostProcessor messagePostProcessor(Integer priority) {
        return message -> {
            // 设置消息的优先级
            // 设置优先级, 不得高于x-max-priority 设置的值(默认10),数字越大,会优先被消费
            message.getMessageProperties().setPriority(priority);
            return message;
        };
    }

(2)、配置类声明:

import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;

/**
 * 优先级队列配置类
 * 优先级高的消息先被优先处理。
 * RabbitMQ的优先级大小最小至最大的数值是0~255也就是说,数字越大,会优先被消费。不过一般设置的数值会在0~10之间
 * 【因为如果设置0-255,会考验服务器的硬件性能问题】
 * */
@Component
public class PriorityQueueConfig {
    private static final String PRIORITY_EXCHANGE = "PRIORITY.EA";
    private static final String PRIORITY_QUEUE = "PRIORITYQA";
    private static final String PRIORITY_ROUTING_KEY = "priority.ra";

    /**
     * ---------------------------------创建交换机---------------------------------------------
     */
    @Bean
    public DirectExchange directExchangePRIORITY(){
        return new DirectExchange(PRIORITY_EXCHANGE,true,false);
    }

    /**
     * ---------------------------------创建优先级队列---------------------------------------------
     */
    @Bean
    public Queue queuePRIORITY(){
        return QueueBuilder.durable(PRIORITY_QUEUE)
                //x-max-priority:设置最大的优先级数量,优先级大小最小至最大的数值是0~255,数字越大,会优先被消费。
                //一般设置的数值会在0~10之间,避免性能损失
                .maxPriority(10)
                .build();
    }

    /**
     * ---------------------------------绑定优先级队列关系routingkey---------------------------------------------
     */
    @Bean
    Binding bindingDirectPRIORITY(Queue queuePRIORITY, DirectExchange directExchangePRIORITY){
        return BindingBuilder.bind(queuePRIORITY).to(directExchangePRIORITY).with(PRIORITY_ROUTING_KEY);
    }


}

(3)、消费者:

    @RabbitListener(queues = "PRIORITYQA",
            //消费者消息确认机制,需要设置消费端为手动ack确认(AcknowledgeMode)
            containerFactory = "singleListenerContainer",
            ackMode = "MANUAL")
    public void recevicelPriority(String msg, Channel channel, Message message) throws IOException {
        log.info("接受到优先级队列消息为:" + msg);
        try {
            // 消费消息
            log.info("消息消费{}成功", msg);
            // 判断消息是否正常后应答确认
            if (true) {
                /**
                 * deliveryTag:表示消息投递序号,每次消费消息或者消息重新投递后,deliveryTag都会增加
                 * multiple:是否批量确认,值为 true 则会一次性 ack所有小于当前消息 deliveryTag 的消息。
                 */
                channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
                log.info("发布确认方式,单个消息消费成功");
            }
//            throw new RuntimeException("来个异常");//测试是否异常时重回队列
        } catch (Exception e) {
            e.printStackTrace();
            channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
        }
    }

 

六、MQ高可用:

1、单机模式Demo):

2、普通集群模式(无高可用):

3、镜像集群模式(高可用性):

 

七、相关问题解决:

1、如何保证消息不被重复消费(幂等性问题):

业务场景:用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。比如支付扣款功能

(1)、利用数据库主键唯一性保证数据唯一

(2)、利用Redis的天然幂等性,过滤数据

(3)、利用全局ID或者唯一标识,进行标识消息(CorrelationData)

 

2、如何保证消息的可靠性传输(消息丢失问题):

(1)、生产者方面:

采用RabbitMQ自带的消息机制,同时开启队列持久化,MQ宕机恢复之后会自动读取之前队列存储的数据。

消息机制:

1)、方案一:采用RabbitMQ事务机制,但是吞吐量低,消耗性能。

2)、方案二:采用confirm机制:即发布确认,通过ACK机制返回消息接受状态,判断消息是否发送成功。

(2)、MQ方面:

采用主从机制,集群部署,实现MQ高可用

(3)、消费者方面:

关闭RabbitMQ的自动ACK(应答)机制,进行手动应答channel.basicAck()、消费异常手动重新入队channel.basicNack()、重试次数过多手动丢弃channel.basicReject(),避免消息堆积

(4)、极端情况:

MQ节点全部宕机,消息发送失败后先存入本地,例如放到缓存中,另外启动一个线程扫描缓存的消息去重试发送。

 

3、如何保证消息的顺序性:

业务场景:一个队列,有多个消费者,比如,生产者向RabbitMQ里发送了三条数据,顺序依次是 data1/data2/data3,有三个消费者分别从MQ中消费这三条数据中的一条,结果消费者二先执行完操作,把 data2 存入数据库,然后是 data1/data3。对于需要依次保存执行的数据操作,这样消费顺序明显会出问题。

方案:可以拆分多个queue,每个queue 一个consumer,同时消费者内部采用多线程的方式进行消费,避免吞吐量降低。

 

4、如何避免消息堆积:

(1)、提高消费并行度,即并发消费

(2)、批量方式消费

(3)、发生消息堆积,适当跳过或者丢弃非重要消息

(4)、优化每条消息消费过程

 

5、如何写一个消息队列:

(1)、MQ吞吐量和容量设计问题

(2)、数据持久化与可靠性设计问题

(3)、MQ高可用设计问题

 

八、相关参考:

参考一

参考二

参考三

 

posted on 2023-07-18 00:08  爱文(Iven)  阅读(64)  评论(0编辑  收藏  举报

导航