RabbitMQ三、springboot整合rabbitmq(消息可靠性、高级特性)
一、springboot整合RabbitMQ(jdk17)(创建两个项目,一个生产者项目,一个消费者项目)
- 上面使用原生JAVA操作RabbitMQ较为繁琐,很多的代码都是重复书写的,使用springboot可以简化代码的编写。
生产者项目
第一步:创建springboot工程,然后引入rabbitmq的依赖
<!-- RabbitMQ起步依赖 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
第二步:编写配置文件
spring:rabbitmq:host: 192.168.70.130 # 虚拟机的地址port: 5672username: adminpassword: adminvirtual-host: /#日志格式
logging:pattern:console: '%d{HH:mm:ss.SSS} %clr(%-5level) --- [%-15thread] %cyan(%-50logger{50}):%msg%n'
第三步:编写RabbitMQ的配置类
@Configuration
public class RabbitmqConfig1 {private final String EXCHANGE_NAME = "boot_exchange";private final String QUEUE_NAME = "boot_queue";private final String ROUTE_NAME = "boot_route";//创建交换机@Bean(EXCHANGE_NAME)public Exchange getExchange(){return ExchangeBuilder.topicExchange(EXCHANGE_NAME).durable(true).build();}//创建队列@Bean(QUEUE_NAME)public Queue getQueue(){return new Queue(QUEUE_NAME);}//交换机和队列绑定@Beanpublic Binding exchangeBindQueue(@Qualifier(QUEUE_NAME) Queue queue, @Qualifier(EXCHANGE_NAME) Exchange exchange){return BindingBuilder.bind(queue).to(exchange).with(ROUTE_NAME).noargs();}
}
第四步:编写发送消息测试类
//编写发送消息测试类
@SpringBootTest
public class RabbitmqTest {// 注入RabbitTemplate工具类@Autowiredprivate RabbitTemplate rabbitTemplate;@Testpublic void testSendMessage(){/*** 发送消息* 参数1:交换机* 参数2:路由key* 参数3:要发送的消息*/rabbitTemplate.convertAndSend("boot_exchange","boot_route","你好我有一个毛衫");System.out.println("发送消息成功");}
}
消费者项目
第一步:创建springboot工程,然后引入rabbitmq的依赖
<!-- RabbitMQ起步依赖 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
第二步:编写配置文件
spring:rabbitmq:host: 192.168.70.130 # 虚拟机的地址port: 5672username: adminpassword: adminvirtual-host: /#日志格式
logging:pattern:console: '%d{HH:mm:ss.SSS} %clr(%-5level) --- [%-15thread] %cyan(%-50logger{50}):%msg%n'
第三步:编写消费者,监听队列
@Component
public class Consumer1 {/*** 监听队列* @param message* queues表示监听的队列的名称*/@RabbitListener(queues = "boot_queue")public void listener(String message){System.out.println("接受到消息 = " + message);}
}
4、rabbitmq的消息可靠性
-
RabbitMQ消息投递的路径为:
生产者--->交换机--->队列--->消费者
-
在RabbitMQ工作的过程中,每个环节消息都可能传递失败,那么RabbitMQ是如何监听消息是否成功投递的呢?
-
- 确认模式(confirm):可以监听消息是否从生产者成功传递到
交换机
。
- 确认模式(confirm):可以监听消息是否从生产者成功传递到
-
- 退回模式(return):可以监听消息是否
从交换机成功传递到队列
。
- 退回模式(return):可以监听消息是否
-
- 消费者消息确认(Consumer Ack):可以监听
消费者
是否成功处理消息。
- 消费者消息确认(Consumer Ack):可以监听
-
【一】rabbitmq的消息可靠性——确认模式
- 确认模式(confirm):可以监听消息是否从生产者成功传递到
交换机
。 - 创建一个新的生产者项目,导入mq(上面的第一步操作)依赖进行开发:(也可以在原来的基础上修改信息)
- 代码组成和上面的生产者项目是一样的,也是三步内容。
第一步:修改配置文件
只是添加了一句代码
spring:rabbitmq:host: 192.168.70.130port: 5672username: adminpassword: adminvirtual-host: / # 表示使用默认的virtual-host#开启确认模式publisher-confirm-type: correlated#????
logging:pattern:console: '%d{HH:mm:ss.SSS} %clr(%-5level) --- [%-15thread] %cyan(%-50logger{50}):%msg%n'
第二步:在生产者的配置类创建交换机和队列(RabbitMQ的配置类)
@Configuration
public class RabbitmqConfig2Confirm {public final String EXCHANGE_NAME = "confirm_exchange";public final String QUEUE_NAME = "confirm_queue";public final String ROUTING_NAME = "confirm_routing";// 创建交换机@Bean(EXCHANGE_NAME)public Exchange exchange(){return ExchangeBuilder.topicExchange(EXCHANGE_NAME).durable(true).build();}// 创建队列@Bean(QUEUE_NAME)public Queue queue(){return QueueBuilder.durable(QUEUE_NAME).build();}
// 创建交换机和队列绑定@Beanpublic Binding exchangeBindQueue(@Qualifier(QUEUE_NAME) Queue queue, @Qualifier(EXCHANGE_NAME) Exchange exchange){return BindingBuilder.bind(queue).to(exchange).with(ROUTING_NAME).noargs();}
}
第三步:编写测试类发生消息:生产者定义确认模式的回调方法(springboot的测试类,能够加载到第二步的配置类)
@Testvoid testConfirm() {//回调确认rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {/**** @param correlationData 配置信息* @param b 是否成功,true 是 ,false 否* @param s 失败原因*/@Overridepublic void confirm(CorrelationData correlationData, boolean b, String s) {if (b){System.out.println("发送成功");}else{System.out.println("发送失败,原因:"+s);}}});//发送消息/*** 发送消息* 参数1:交换机* 参数2:路由key* 参数3:要发送的消息*/rabbitTemplate.convertAndSend("confirm_exchange","confirm_routing","send message...confirm");}
由于rabbitmq的confirm确认模式是确认消息是否从生产者成功传递到交换机的,所以就没必要写消费者进行信息的消费了
- 当我们执行测试类的时候,先执行rabbitTemplate.convertAndSend(“confirm_exchange”,“confirm_routing”,“send message…confirm”);,无论消息是否成功发送都会调用 rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback()方法,如果发送成功则执行if语句的代码,如果发送失败则调用else语句的代码。
- 根据执行的是if或者else的语句,就能判断消息是否成功传递到交换机了。
【二】rabbitmq的消息可靠性——退回模式
- 退回模式(return):可以监听消息是否
从交换机成功传递到队列
。 - 创建一个新的生产者项目,导入mq(上面的第一步操作)依赖进行开发:(也可以在原来的基础上修改信息)
- 代码组成和上面的生产者项目是一样的,也是三步内容。
第一步:修改配置文件
只是添加了一句
# rabbitmq???
spring:rabbitmq:host: 192.168.70.130port: 5672username: adminpassword: adminvirtual-host: /#开启确认模式publisher-confirm-type: correlated#开始回退模式publisher-returns: true#????
logging:pattern:console: '%d{HH:mm:ss.SSS} %clr(%-5level) --- [%-15thread] %cyan(%-50logger{50}):%msg%n'
第二步:编写配置类(RabbitMQ的配置类)
@Configuration
public class RabbitmqConfig3Return {public final String EXCHANGE_NAME = "return_exchange";public final String QUEUE_NAME = "return_queue";public final String ROUTING_NAME = "return_routing";
// 创建交换机@Bean(EXCHANGE_NAME)public Exchange exchange(){return ExchangeBuilder.topicExchange(EXCHANGE_NAME).durable(true).build();}// 创建队列@Bean(QUEUE_NAME)public Queue queue(){return QueueBuilder.durable(QUEUE_NAME).build();}// 创建交换机和队列绑定@Beanpublic Binding exchangeBindQueue(@Qualifier(QUEUE_NAME) Queue queue, @Qualifier(EXCHANGE_NAME) Exchange exchange){return BindingBuilder.bind(queue).to(exchange).with(ROUTING_NAME).noargs();}
}
第三步:编写测试类发生消息:生产者定义退回模式的回调方法(springboot的测试类,能够加载到第二步的配置类)
@Testvoid testReturnSendMessage(){
// 调用回退模式的回调方法,只有失败才会回调,成功不会回调哦
// 失败后将失败信息封装到参数中rabbitTemplate.setReturnsCallback(returned ->{Message message = returned.getMessage();System.out.println("消息对象:"+message);System.out.println("错误码:"+returned.getReplyCode());System.out.println("错误信息:"+returned.getReplyText());System.out.println("交换机:"+returned.getExchange());System.out.println("路由键:"+returned.getRoutingKey());});// 发送消息/*** 发送消息* 参数1:交换机* 参数2:路由key* 参数3:要发送的消息*/rabbitTemplate.convertAndSend("return_exchange","return_routing","send message...return");}
由于rabbitmq的return回退模式是确认消息是否从交换机成功传递到队列的,还没有传递到消费者,所以就没必要写消费者进行信息的消费了
- 当我们执行测试类的时候,先执行rabbitTemplate.convertAndSend(“return_exchange”,“return_routing”,“send message…return”);,如果消息
成功发送到队列
上则不会
调用 rabbitTemplate.setReturnsCallback方法,如果发送步成功则调用回调方法rabbitTemplate.setReturnsCallback,- 根据运行结果就可以知道在传递消息到队列上的时候哪里发生错误了
【三】rabbitmq的消息可靠性——消费者消息确认(Consumer Ack)
- 在RabbitMQ中,消费者接收到消息后会
向队列发送确认签收的消息
,只有确认签收的消息才会被移除队列
。这种机制称为消费者消息确认(Consumer Acknowledge,简称Ack)。- 类似快递员派送快递也需要我们签收,否则一直存在于快递公司的系统中。
- 消费者消息确认(Consumer Acknowledge,简称Ack)分为
自动确认
和手动确认
。- 自动确认指消息只要被消费者接收到,无论是否成功处理消息,则
自动签收,并将消息从队列中移除
。 - 但是在实际开发中,收到消息后可能业务处理
出现异常
,那么消息就会丢失
。此时需要设置手动签收,即在业务处理成功后再通知签收消息,如果出现异常,则拒签消息
,让消息依然保留
在队列当中。
- 自动确认指消息只要被消费者接收到,无论是否成功处理消息,则
● 自动确认:spring.rabbitmq.listener.simple.acknowledge=“none”
● 手动确认:spring.rabbitmq.listener.simple.acknowledge=“manual”
- 创建一个新的生产者项目和新的消费者项目,导入mq(上面的第一步操作)依赖进行开发:(也可以在原来的基础上修改信息)
- 代码组成和上面的生产者项目是一样的,也是三步内容。
生产者项目:第一步:修改配置文件
不用修改
# rabbitmq???
spring:rabbitmq:host: 192.168.70.130port: 5672username: adminpassword: adminvirtual-host: /#开启确认模式publisher-confirm-type: correlated#开始回退模式publisher-returns: true#????
logging:pattern:console: '%d{HH:mm:ss.SSS} %clr(%-5level) --- [%-15thread] %cyan(%-50logger{50}):%msg%n'
生产者项目:第二步:编写配置类(RabbitMQ的配置类)
@Configuration
public class RabbitmqConfig4ACK {public final String EXCHANGE_NAME = "ack_exchange";public final String QUEUE_NAME = "ack_queue";public final String ROUTING_NAME = "ack_routing";
// 创建交换机@Bean(EXCHANGE_NAME)public Exchange exchange(){return ExchangeBuilder.topicExchange(EXCHANGE_NAME).durable(true).build();}// 创建队列@Bean(QUEUE_NAME)public Queue queue(){return QueueBuilder.durable(QUEUE_NAME).build();}// 创建交换机和队列绑定@Beanpublic Binding exchangeBindQueue(@Qualifier(QUEUE_NAME) Queue queue, @Qualifier(EXCHANGE_NAME) Exchange exchange){return BindingBuilder.bind(queue).to(exchange).with(ROUTING_NAME).noargs();}
}
生产者项目:第三步:编写测试类发生消息:(springboot的测试类,能够加载到第二步的配置类)
@Testvoid testAck(){// 发送消息rabbitTemplate.convertAndSend("ack_exchange","ack_routing","send message...ack");}
消费者项目(自动确认):第一步:修改配置文件
- 消费者消息确认——自动确认的配置文件
spring:rabbitmq:host: 192.168.70.130port: 5672username: adminpassword: adminvirtual-host: /#开动手动签收listener:simple:acknowledge-mode: none # 默认就是自动确认
#????
logging:pattern:console: '%d{HH:mm:ss.SSS} %clr(%-5level) --- [%-15thread] %cyan(%-50logger{50}):%msg%n'
- 自动签收模式就是:消息只要被消费者接收到,无论是否成功处理消息,则
自动签收,并将消息从队列中移除
。当我们拿到消息的时候,业务出现异常了,所以无法正确处理消息,导致消息丢失了。
消费者项目(自动确认):第二步:编写消费者类,监听队列
- 自动确认的消费者类
@Component
public class AckConsumer {
// 自动签收@RabbitListener(queues = "ack_queue")public void listenMessage(Message message, Channel channel) throws IOException, InterruptedException, IOException {
// 获取消息String s = new String(message.getBody(), StandardCharsets.UTF_8);System.out.println(s);
// TODO,处理事务
// 故意出错int i= 1/0;}}
消费者项目(手动确认):第一步:修改配置文件
- 消费者消息确认——手动确认的配置文件
spring:rabbitmq:host: 192.168.70.130port: 5672username: adminpassword: adminvirtual-host: /#开动手动签收listener:simple:acknowledge-mode: manual
#????
logging:pattern:console: '%d{HH:mm:ss.SSS} %clr(%-5level) --- [%-15thread] %cyan(%-50logger{50}):%msg%n'
消费者项目(手动确认):第二步:编写消费者类,监听队列
- 手动确认
@Component
public class AckConsumer {// 手动签收@RabbitListener(queues = "ack_queue")public void listenMessage(Message message, Channel channel) throws IOException, InterruptedException, IOException {
// 消息投递序号,消息每次投递该值都会+1long deliveryTag = message.getMessageProperties().getDeliveryTag();try {
// int i = 1/0; //模拟处理消息出现bugSystem.out.println("成功接受到消息:"+message);// 签收消息/*** 参数1:消息投递序号* 参数2:是否一次可以签收多条消息*/channel.basicAck(deliveryTag,true);}catch (Exception e){System.out.println("消息消费失败!");Thread.sleep(2000);// 拒签消息/*** 参数1:消息投递序号* 参数2:是否一次可以拒签多条消息* 参数3:拒签后消息是否重回队列*/channel.basicNack(deliveryTag,true,true);}}
}
- 手动签收模式就是:如果出现异常,则
拒签消息
,让消息依然保留
在队列当中。方便下次请求能够请求到这次因为异常而没有接收到的消息。
【四】RabbitMQ高级特性——消费端限流
- 前面说过MQ可以对请求进行“削峰填谷”,即通过消费端限流的方式限制消息的拉取速度,达到保护消费端的目的。
- 使用【三】rabbitmq的消息可靠性——消费者消息确认(Consumer Ack)的项目,消费者使用手动确认模式的代码即可(但是要修改配置文件)
第一步:先在生产者项目中,发送多个消息
@Testpublic void testLimitSendBatch() {// 发送十条消息for (int i = 0; i < 10; i++) {rabbitTemplate.convertAndSend("ack_exchange", "ack_routing", "这是第"+i+"条消息");}}
第二步:修改消费者项目的配置文件
最主要就是配置文件的修改:
spring:rabbitmq:host: 192.168.70.130port: 5672username: adminpassword: adminvirtual-host: /#开动手动签收listener:simple:acknowledge-mode: manual #none是默认的prefetch: 5 # 每次消费者从队列拉取的消息数量(限制)#????
logging:pattern:console: '%d{HH:mm:ss.SSS} %clr(%-5level) --- [%-15thread] %cyan(%-50logger{50}):%msg%n'
第三步:重新编写消费者类
@Component
public class ConsumerLimit {
// 手动签收@RabbitListener(queues = "limit_queue")public void listenMessage(Message message, Channel channel) throws IOException, InterruptedException, IOException {
// 获取消息String s = new String(message.getBody(), StandardCharsets.UTF_8);System.out.println(s);// 模拟业务处理Thread.sleep(3000);long deliveryTag = message.getMessageProperties().getDeliveryTag();
// 手动签收channel.basicAck(deliveryTag,true);}}
- 其实就是修改了消费者项目的配置文件,添加一条配置信息,限制消费者消息的拉取速度。
【五】RabbitMQ高级特性——利用限流实现不公平分发
- 在RabbitMQ中,多个消费者监听同一条队列,则队列默认采用的轮询分发。但是在某种场景下这种策略并不是很好,例如消费者1处理任务的速度非常快,而其他消费者处理速度却很慢。此时如果采用公平分发,则消费者1有很大一部分时间处于空闲状态。此时可以采用不公平分发,即谁处理的快,谁处理的消息多。
- 在【四】RabbitMQ高级特性——消费端限流的基础上,修改一消费者项目的配置文件,然后在消费者类中多写几个监听消息的方法(或者多写几个消费者类)。
第一步:修改消费者项目的配置文件
最主要就是配置文件的修改:
spring:rabbitmq:host: 192.168.70.130port: 5672username: adminpassword: adminvirtual-host: /#开动手动签收listener:simple:acknowledge-mode: manual #none是默认的prefetch: 1 # 消费端最多拉取1条消息消费,这样谁处理的快谁拉取下一条消息,实现了不公平分发#????
logging:pattern:console: '%d{HH:mm:ss.SSS} %clr(%-5level) --- [%-15thread] %cyan(%-50logger{50}):%msg%n'
第二步:修改消费者类,编写多个监听方法
@Component
public class ConsumerUnfair {
// 消费者1@RabbitListener(queues = "ack_queue")public void listenMessage1(Message message, Channel channel) throws IOException, InterruptedException, IOException {
// 获取消息String s = new String(message.getBody(), StandardCharsets.UTF_8);System.out.println("消费者1"+s);Thread.sleep(3000);long deliveryTag = message.getMessageProperties().getDeliveryTag();
// 手动签收channel.basicAck(deliveryTag,true);}// 消费者2@RabbitListener(queues = "ack_queue")public void listenMessage2(Message message, Channel channel) throws IOException, InterruptedException, IOException {
// 获取消息String s = new String(message.getBody(), StandardCharsets.UTF_8);System.out.println("消费者2"+s);Thread.sleep(1000);long deliveryTag = message.getMessageProperties().getDeliveryTag();
// 手动签收channel.basicAck(deliveryTag,true);}// .......监听方法
}
- 最主要的就是消费者项目的配置文件的修改:
配置消费端最多拉取1条消息消费
,这样谁处理的快谁拉取下一条消息,实现了不公平分发。
【六】RabbitMQ高级特性——消息存活时间
- RabbitMQ可以设置消息的存活时间(Time To Live,简称TTL),当
消息到达存活时间后还没有被消费
,会被移出队列。RabbitMQ可以对队列的所有消息设置存活时间,也可以对某条消息设置存活时间。
- 使用【三】rabbitmq的消息可靠性——消费者消息确认(Consumer Ack)的项目,消费者使用手动确认模式的代码
第一步:修改生产者项目的配置类
@Configuration
public class RabbitmqConfig7ttl {public final String EXCHANGE_NAME = "ack_exchange";public final String QUEUE_NAME = "ack_queue";public final String ROUTING_NAME = "ack_routing";
// 创建交换机@Bean(EXCHANGE_NAME)public Exchange exchange(){return ExchangeBuilder.topicExchange(EXCHANGE_NAME).durable(true).build();}// 创建队列@Bean(QUEUE_NAME)public Queue queue(){return QueueBuilder.durable(QUEUE_NAME)
// 设置队列的超时的时间,单位是毫秒.ttl(10000).build();}// 创建交换机和队列绑定@Beanpublic Binding exchangeBindQueue(@Qualifier(QUEUE_NAME) Queue queue, @Qualifier(EXCHANGE_NAME) Exchange exchange){return BindingBuilder.bind(queue).to(exchange).with(ROUTING_NAME).noargs();}
}
第二步:修改生产者项目的测试类
设置单条消息存活时间
@Testpublic void testTtlSendBatch() {// 发送十条消息for (int i = 0; i < 100; i++) {if (i%5 == 0) {//设置消息属性MessageProperties messageProperties = new MessageProperties();//设置存活时间messageProperties.setExpiration("10000");// 创建消息对象(可以配置消息的一些配置)Message message = new Message(("这是第" + i + "条消息").getBytes(StandardCharsets.UTF_8), messageProperties);// 发送消息rabbitTemplate.convertAndSend("ack_exchange", "ack_routing", message);}else {rabbitTemplate.convertAndSend("ack_exchange", "ack_routing", "这是第" + i + "条消息");}}}
-
- 如果
设置了单条消息的存活时间,也设置了队列的存活时间
,以时间短
的为准。
- 如果
-
- 消息过期后,并不会马上移除消息,只有消息消费到队列顶端时,才会移除该消息
【七】RabbitMQ高级特性——优先级队列
- 假设在电商系统中有一个订单催付的场景,即客户在一段时间内未付款会给用户推送一条短信提醒,但是系统中分为
大型商家和小型商家
。比如像苹果,小米这样大商家一年能给我们创造很大的利润,所以在订单量大时,他们的订单必须得到优先处理,此时就需要为不同的消息设置不同的优先级,此时我们要使用优先级队列。
- 使用【三】rabbitmq的消息可靠性——消费者消息确认(Consumer Ack)的项目,消费者使用手动确认模式的代码
第一步:修改生产者项目的配置类
@Configuration
public class RabbitmqConfig8Priority {public final String EXCHANGE_NAME = "priority_exchange";public final String QUEUE_NAME = "priority_queue";public final String ROUTING_NAME = "priority_routing";
// 创建交换机@Bean(EXCHANGE_NAME)public Exchange exchange(){return ExchangeBuilder.topicExchange(EXCHANGE_NAME).durable(true).build();}// 创建队列@Bean(QUEUE_NAME)public Queue queue(){return QueueBuilder.durable(QUEUE_NAME)
// 设置队列的优先级,值越大优先级越高,一般不超过10.maxPriority(10).build();}// 创建交换机和队列绑定@Beanpublic Binding exchangeBindQueue(@Qualifier(QUEUE_NAME) Queue queue, @Qualifier(EXCHANGE_NAME) Exchange exchange){return BindingBuilder.bind(queue).to(exchange).with(ROUTING_NAME).noargs();}
}
第二步:修改生产者项目的测试
@Testpublic void testPrioritySendBatch() {// 发送十条消息for (int i = 0; i < 100; i++) {if (i%5 == 0) {//设置消息属性MessageProperties messageProperties = new MessageProperties();
// 设置优先级messageProperties.setPriority(9);// 创建消息对象(可以配置消息的一些配置)Message message = new Message(("这是第" + i + "条消息").getBytes(StandardCharsets.UTF_8), messageProperties);// 发送消息rabbitTemplate.convertAndSend("priority_exchange", "priority_routing", message);}else {rabbitTemplate.convertAndSend("priority_exchange", "priority_routing", "这是第" + i + "条消息");}}}
- 设置了消息的优先级,那么消费者项目在消费消息的时候就会优先消费等级高的消息。
【八】RabbitMQ高级特性——死信队列
- 在MQ中,当消息成为死信(Dead message)后,消息中间件可以将其
从当前队列发送到另一个队列中
,当前队列就是死信队列。而在RabbitMQ中,由于有交换机的概念,实际是将死信发送给了死信交换机(Dead Letter Exchange,简称DLX)。死信交换机和死信队列和普通的没有区别。
- 消息成为死信的情况:
-
- 队列消息长度到达限制。
-
- 消费者拒接消费消息,basicNack/basicReject,并且不把消息重新放入原目标队列,requeue=false;
-
- 消息到达存活时间未被消费。
-
生产者项目:第一步:修改配置文件
# rabbitmq???
spring:rabbitmq:host: 192.168.70.130port: 5672username: adminpassword: adminvirtual-host: /#开启确认模式publisher-confirm-type: correlated#开始回退模式publisher-returns: true#????
logging:pattern:console: '%d{HH:mm:ss.SSS} %clr(%-5level) --- [%-15thread] %cyan(%-50logger{50}):%msg%n'
生产者项目:第二步:编写配置类(RabbitMQ的配置类)
@Configuration
public class RabbitmqConfig9Dead {// 死信private final String DEAD_EXCHANGE = "dead_exchange";private final String DEAD_QUEUE = "dead_queue";private final String DEAD_ROUTING = "dead_routing";// 死信交换机@Bean(DEAD_EXCHANGE)public Exchange deadExchange(){return ExchangeBuilder.topicExchange(DEAD_EXCHANGE).durable(true).build();}// 死信队列@Bean(DEAD_QUEUE)public Queue deadQueue(){return QueueBuilder.durable(DEAD_QUEUE).build();}// 死信交换机绑定死信队列@Beanpublic Binding bindDeadQueue(@Qualifier(DEAD_EXCHANGE) Exchange exchange,@Qualifier(DEAD_QUEUE)Queue queue){return BindingBuilder.bind(queue).to(exchange).with(DEAD_ROUTING).noargs();}// 普通private final String NORMAL_EXCHANGE = "normal_exchange";private final String NORMAL_QUEUE = "normal_queue";private final String NORMAL_ROUTING = "normal_routing";// 普通交换机@Bean(NORMAL_EXCHANGE)public Exchange normalExchange(){return ExchangeBuilder.topicExchange(NORMAL_EXCHANGE).durable(true).build();}// 普通队列@Bean(NORMAL_QUEUE)public Queue normalQueue(){return QueueBuilder.durable(NORMAL_QUEUE).deadLetterExchange(DEAD_EXCHANGE) // 绑定死信交换机.deadLetterRoutingKey(DEAD_ROUTING) // 死信队列路由关键字.ttl(10000) // 消息存活10s.maxLength(10) // 队列最大长度为10.build();}// 普通交换机绑定普通队列@Beanpublic Binding bindNormalQueue(@Qualifier(NORMAL_EXCHANGE) Exchange exchange,@Qualifier(NORMAL_QUEUE)Queue queue){return BindingBuilder.bind(queue).to(exchange).with(NORMAL_ROUTING).noargs();}
}
生产者项目:第三步:编写测试类发生消息:(springboot的测试类,能够加载到第二步的配置类)
@Test
public void testDlx(){// 存活时间过期后变成死信// rabbitTemplate.convertAndSend("normal_exchange","normal_routing","测试死信");// 超过队列长度后变成死信// for (int i = 0; i < 20; i++) {// rabbitTemplate.convertAndSend("normal_exchange","normal_routing","测试死信");// }// 消息拒签但不返回原队列后变成死信rabbitTemplate.convertAndSend("normal_exchange","normal_routing","测试死信");
}
消费者项目(手动确认):第一步:修改配置文件
- 消费者消息确认——手动确认的配置文件
spring:rabbitmq:host: 192.168.70.130port: 5672username: adminpassword: adminvirtual-host: /#开动手动签收listener:simple:acknowledge-mode: manual
#????
logging:pattern:console: '%d{HH:mm:ss.SSS} %clr(%-5level) --- [%-15thread] %cyan(%-50logger{50}):%msg%n'
消费者项目(手动确认):第二步:编写消费者类,监听队列
- 手动确认
@Component
public class ConsumerDead {@RabbitListener(queues = "normal_queue")public void listenMessage1(Message message, Channel channel) throws IOException, InterruptedException, IOException {
// 获取消息String s = new String(message.getBody(), StandardCharsets.UTF_8);System.out.println("消费者1"+s);Thread.sleep(500);long deliveryTag = message.getMessageProperties().getDeliveryTag();
// 拒绝签收channel.basicNack(deliveryTag,true,false);}
- 死信队列小结
-
- 死信交换机和死信队列和普通的没有区别
-
- 当消息成为死信后,如果该队列绑定了死信交换机,则消息会被死信交换机重新路由到死信队列
-
- 消息成为死信的三种情况:
-
- 队列消息长度到达限制;
-
- 消费者拒接消费消息,并且不重回队列;
-
- 原队列存在消息过期设置,消息到达超时时间未被消费;
-
【九】RabbitMQ高级特性——延迟队列
- 延迟队列,即消息进入队列后不会立即被消费,只有到达指定时间后,才会被消费。
- 例如:
-
- 下单后,30分钟未支付,取消订单,回滚库存。
-
- 新用户注册成功7天后,发送短信问候。
- 实现方式:
-
- 定时器
-
- 延迟队列
- 延迟队列
-
-
- 例如:
- RabbitMQ中并未提供延迟队列功能,我们可以使用死信队列实现延迟队列的效果。
-
- 延迟队列 指消息进入队列后,可以被延迟一定时间,再进行消费。
-
- RabbitMQ没有提供延迟队列功能,但是可以使用 : TTL + DLX 来实现延迟队列效果。
- RabbitMQ没有提供延迟队列功能,但是可以使用 : TTL + DLX 来实现延迟队列效果。
第一步:创建springboot项目并添加依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId>
</dependency>
第二步:编写配置文件
spring:rabbitmq:host: 192.168.70.130port: 5672username: adminpassword: adminvirtual-host: /#开动手动签收listener:simple:acknowledge-mode: manual
# ????
logging:pattern:console: '%d{HH:mm:ss.SSS} %clr(%-5level) --- [%-15thread] %cyan(%-50logger{50}):%msg%n'
第三步:编写配置类
@Configuration
public class RabbitMQConfig {private final String DEAD_EXCHANGE = "order_expire_exchange";private final String DEAD_QUEUE = "order_expire_queue";private final String DEAD_ROUTING = "order_expire_routing";private final String ORDER_EXCHANGE = "order_exchange";private final String ORDER_QUEUE = "order_queue";private final String ORDER_ROUTING = "order_routing";// 死信交换机@Bean(DEAD_EXCHANGE)public Exchange deadExchange(){return ExchangeBuilder.topicExchange(DEAD_EXCHANGE).durable(true).build();}// 死信队列@Bean(DEAD_QUEUE)public Queue deadQueue(){return QueueBuilder.durable(DEAD_QUEUE).build();}// 死信交换机绑定死信队列@Beanpublic Binding bindDeadQueue(@Qualifier(DEAD_EXCHANGE) Exchange exchange, @Qualifier(DEAD_QUEUE)Queue queue){return BindingBuilder.bind(queue).to(exchange).with(DEAD_ROUTING).noargs();}// 普通交换机@Bean(ORDER_EXCHANGE)public Exchange normalExchange(){return ExchangeBuilder.topicExchange(ORDER_EXCHANGE).durable(true).build();}// 普通队列@Bean(ORDER_QUEUE)public Queue normalQueue(){return QueueBuilder.durable(ORDER_QUEUE).deadLetterExchange(DEAD_EXCHANGE) // 绑定死信交换机.deadLetterRoutingKey(DEAD_ROUTING) // 死信队列路由关键字.ttl(10000) // 消息存活10s(模拟30min超时).build();}// 普通交换机绑定普通队列@Beanpublic Binding bindNormalQueue(@Qualifier(ORDER_EXCHANGE) Exchange exchange,@Qualifier(ORDER_QUEUE)Queue queue){return BindingBuilder.bind(queue).to(exchange).with(ORDER_ROUTING).noargs();}
}
第四步:创建控制器,完成下单功能
@RestController
public class OrderController {//注入MQ@Autowiredprivate RabbitTemplate rabbitTemplate;@RequestMapping("/addOrder")public String addOrder(){//生成订单号String orderNumber = "2030061812251234";//在service层完成订单逻辑//将订单号发送到订单mq,30分钟过期进入死信队列,死信队列消费查询订单支付状态,做对应处理rabbitTemplate.convertAndSend("order_exchange","order_routing",orderNumber);return "下单成功! 您的订单号为 :"+orderNumber;}
}
第五步:创建消费者,监听消息
@Component
public class ListenerOrder {//监听订单过期队列@RabbitListener(queues = "order_expire_queue")public void orderListener(String orderId){System.out.println("orderId = " + orderId);//根据订单id查询订单状态是否支付/*** 监听死信队列的类,回去30min超时订单号,根据订单号查询订单的支付状态* 支付:走下一步流程* 未支付:关闭订单,库存回滚*/}
}
手动签收模式的结果
- 在手动签收模式的时候,当我们启动项目,访问订单功能时,立刻生成了一个队列消息
- 然后因为是手动签收模式,所以在消息的存活时间过去了之后,成为了死信消息,所以被消息被拒收了,但是还存在队列中。
自动签收模式的结果
spring:rabbitmq:host: 192.168.70.130port: 5672username: adminpassword: adminvirtual-host: /#开动自动签收listener:simple:acknowledge-mode: none # 默认的
# ????
logging:pattern:console: '%d{HH:mm:ss.SSS} %clr(%-5level) --- [%-15thread] %cyan(%-50logger{50}):%msg%n'
- 在自动签收模式的时候,当我们启动项目,访问订单功能时,立刻生成了一个队列消息
- 因为是自动签收的,所以消息过了存活时间之后就没了(自动确认指消息只要被消费者接收到,无论是否成功处理消息,则自动签收,并将消息从队列中移除)
RabbitMQ一、RabbitMQ的介绍与安装(docker)
RabbitMQ二、RabbitMQ的六种模式
相关文章:

RabbitMQ三、springboot整合rabbitmq(消息可靠性、高级特性)
一、springboot整合RabbitMQ(jdk17)(创建两个项目,一个生产者项目,一个消费者项目) 上面使用原生JAVA操作RabbitMQ较为繁琐,很多的代码都是重复书写的,使用springboot可以简化代码的…...

第八十九周周报
学习目标: 论文 学习时间: 2024.05.25-2024.05.31 学习产出: 一、论文 SAN: INDUCING METRIZABILITY OF GAN WITH DISCRIMINATIVE NORMALIZED LINEAR LAYER 将GAN与切片最优输运联系起来,提出满足方向最优性、可分离性和单射…...

Centos升级Openssh版本至openssh-9.3p2
一、启动Telnet服务 为防止升级Openssh失败导致无法连接ssh,所以先安装Telnet服务备用 1.安装telnet-server及telnet服务 yum install -y telnet-server* telnet 2.安装xinetd服务 yum install -y xinetd 3.启动xinetd及telnet并做开机自启动 systemctl enable…...

茉莉香飘,奶茶丝滑——周末悠闲时光的绝佳伴侣
周末的时光总是格外珍贵,忙碌了一周的我们,终于迎来了难得的闲暇。这时,打开喜欢的综艺,窝在舒适的沙发里,再冲泡一杯香飘飘茉莉味奶茶,一边沉浸在剧情的海洋中,一边品味着香浓丝滑的奶茶&#…...

揭秘:Java字符串对象的内存分布原理
先来看看下面寄到关于String的真实面试题,看看你废不废? String str1 "Hello"; String str2 "Hello"; String str3 new String("Hello"); String str4 new String("Hello");System.out.println(str1 str2)…...

Vue.js - 生命周期与工程化开发【0基础向 Vue 基础学习】
文章目录 Vue 的生命周期Vue 生命周期的四个阶段Vue 生命周期函数(钩子函数 工程化开发 & 脚手架 Vue CLI**开发 Vue 的两种方式:**脚手架目录文件介绍项目运行流程组件化开发 & 根组件App.vue 文件(单文件组件)的三个组成…...

Element-UI 快速入门指南
Element-UI 快速入门指南 Element-UI 是一套基于 Vue.js 的桌面端组件库,由饿了么前端团队开发和维护。它提供了丰富的 UI 组件,帮助开发者快速构建美观、响应式的用户界面。本篇文章将详细介绍 Element-UI 的安装、配置和常用组件的使用方法,帮助你快速上手并应用于实际项…...

2024华为OD机试真题-整型数组按个位值排序-C++(C卷D卷)
题目描述 给定一个非空数组(列表),其元素数据类型为整型,请按照数组元素十进制最低位从小到大进行排序, 十进制最低位相同的元素,相对位置保持不变。 当数组元素为负值时,十进制最低位等同于去除符号位后对应十进制值最低位。 输入描述 给定一个非空数组,其元素数据类型…...

善听提醒遵循易经原则。世界大同只此一路。
如果说前路是一个大深坑,那必然是你之前做的事情做的不太好,当坏的时候,坏的结果来的时候,是因为你之前的行为,你也就不会再纠结了,会如何走出这个困境,是好的来了,不骄不躁…...

CrossOver有些软件安装不了 用CrossOver安装软件后如何运行
CrossOver为用户提供了三种下载软件的方式分别是:搜索、查找分类、导入。如果【搜索】和【查找分类】提供的安装资源不能成功安装软件,那么我们可以通过多种渠道下载安装包,并将安装包以导入的方式进行安装。这里我们以QQ游戏为例,…...

在vue中如何使用leaflet图层展示地图
在vue中如何使用leaflet <template><div id"map" class"map"></div> </template><script> export default {data () {return {};},mounted(){this.initMaps()},methods: {initMaps () {const map L.map(map, {zoomControl…...

mybatisplus 字段存的是json 在查询的时候怎么映射成对象
数据库交互对象 TableName(value "表名", autoResultMap true)TableField(typeHandler JacksonTypeHandler.class, value "user_info")private User user;autoResultMap 是一个 MyBatis-Plus 中的注解属性,用于控制是否自动生成结果映射。…...

Python 学习笔记【1】
此笔记仅适用于有任一编程语言基础,且对面向对象有一定了解者观看 文章目录 数据类型字面量数字类型数据容器字符串列表元组 type()方法数据类型强转 注释单行注释多行注释 输出基本输出连续输出,中间用“,”分隔更复杂的输出格式 变量定义del方法 标识符…...

Git系列:rev-parse 使用技巧
💝💝💝欢迎莅临我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…...

【Java数据结构】详解LinkedList与链表(一)
🔒文章目录: 1.❤️❤️前言~🥳🎉🎉🎉 2.ArrayList的缺陷 3.链表的概念及结构 4.无头单向非循环链表的实现 4.1成员属性 4.2成员方法 createList display——打印链表 addFirst——头插 addLast…...

PDF高效编辑器革新:一键智能转换PDF至HTML,轻松开启文件处理全新时代!
信息爆炸的时代,PDF文件因其跨平台、不易修改的特性,成为了商务、教育、出版等领域不可或缺的文件格式。然而,PDF文件的固定性也带来了诸多不便,特别是在需要对其内容进行编辑或格式转换时。这时,一款高效、易用的PDF编…...

JDBC知识
JDBC是什么? 这工作中我们针对数据库的操作,实际上很少会用到SQL语句,通过命令行/图形化来操作数据库,更多的是通过主流的编程语言来对数据库进行操作,即使通过代码来操作数据,我们还是会使用到SQL语句,所以掌握SQL语句也是很重要的. 如何通过代码操作数据库? 通过代码操作…...

C++操纵符用法
C中的操纵符(Manipulators)是用于格式化输入输出的特殊工具。它们可以在输出流中控制各种格式,如设置字段宽度、精度、填充字符等。以下是一些常用的操纵符及其用法: setw(int width): 设置字段宽度为width个字符。 cout <<…...

【一步一步了解Java系列】:子类继承以及代码块的初始化
看到这句话的时候证明:此刻你我都在努力 加油陌生人 个人主页:Gu Gu Study专栏:一步一步了解Java 喜欢的一句话: 常常会回顾努力的自己,所以要为自己的努力留下足迹 喜欢的话可以点个赞谢谢了。 作者:小闭 …...

探索Expect Python用法:深入解析与实战挑战
探索Expect Python用法:深入解析与实战挑战 在自动化和脚本编写领域,Expect Python已经成为了一种强大的工具组合。它结合了Expect的交互式会话处理能力和Python的编程灵活性,为开发者提供了一种全新的方式来处理复杂的自动化任务。然而&…...

【PostgreSQL17新特性之-explain命令新增选项】
EXPLAIN是一个用于显示语句执行计划的命令,可用于显示以下语句类型之一的执行计划: - SELECT - INSERT - UPDATE - DELETE - VALUES - EXECUTE - DECLARE - CREATE TABLE AS - CREATE MATERIALIZED VIEWPostgreSQL17-beta1版本近日发布了,新…...

JAVA实现人工智能,采用框架SpringAI
文章目录 JAVA实现人工智能,采用框架SpringAISpring AI介绍使用介绍项目前提项目结构第一种方式采用openai1. pom文件: 2. application.yml 配置3.controller 实现层 项目测试 JAVA实现人工智能,采用框架SpringAI Spring AI介绍 Spring AI是AI工程师的一个应用框架…...

基础—SQL—DQL(数据查询语言)分组查询
一、引言 分组查询的关键字是:GROUP BY。 二、DQL—分组查询 1、语法 SELECT 字段列表 FROM 表名 [ WHERE 条件 ] GROUP BY 分组字段名 [ HAVING 分组后过滤条件 ]; 注意: 1、[ ] 里的内容可以有可以没有。 2、这条SQL语句有两块指定条件的地方&#…...

从CSV到数据库(简易)
需求:客户上传CSV文档,要求CSV文档内容查重/插入/更新相关数据。 框架:jdbcTemplate、commons-io、 DB:oracle 相关依赖: 这里本来打算用的2.11.0,无奈正式项目那边用老版本1.3.1,新版本对类型…...

K210视觉识别模块学习笔记3:内存卡写入拍摄图片_LED三色灯的操作_按键操作_定时器的配置使用
今日开始学习K210视觉识别模块: LED三色灯的操作_按键操作_定时器的配置使用_内存卡写入拍摄图片 亚博智能的K210视觉识别模块...... 固件库版本: canmv_yahboom_v2.1.1.bin 本文最终目的是编写一个按键拍照的例程序: 为以后的专用场景的模型训练做准备…...

如何定义“智慧校园”这个概念
在信息爆炸的时代,教育面临着前所未有的挑战:如何让每个学生在海量知识中找到属于自己的路径?如何让教师的智慧与科技的力量相得益彰?如何让校园成为培养创新思维的摇篮?智慧校园,这一概念的提出࿰…...

OpenSSL自签名证书
文章目录 生成1. 生成根证书的私钥(root_private_key.pem)2. 创建根证书的CSR和自签名证书(root_csr.pem)3. 生成服务器证书的私钥(server_private_key.pem)4. 创建服务器证书的CSR(server_priv…...

QtCreator调试运行工程报错,无法找到相关库的的解决方案
最新在使用国产化平台做qt应用开发时,总是遇到qtcreator内调试运行 找不到动态库的问题,为什么会出现这种问题呢?明明编译的时候能够正常通过,运行或者调试的时候找不到相关的库呢?先说结论,排除库本身的问…...

【Python系列】Python 元组(Tuple)详解
💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...

特征融合篇 | YOLOv8 引入动态上采样模块 | 超过了其他上采样器
1. 介绍 本篇介绍了一种将动态上采样模块引入 YOLOv8 目标检测算法的新方法,该方法在 COCO 数据集上获得了 55.7% 的 mAP,超越了其他上采样器。该方法将动态上采样模块引入到 YOLOv8 的特征融合阶段,能够根据输入图像的特征分辨率动态调整上…...