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
三、执行流程:
生产者(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: 最大重试时间间隔
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高可用设计问题
八、相关参考:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人