首页 > 应用开发 > Java > SpringBoot:RabbitMQ 延迟队列
* 1、params.put("x-message-ttl", 5 * 1000); * TODO 第一种方式是直接设置 Queue 延迟时间 但如果直接给队列设置过期时间,这种做法不是很灵活,(当然二者是兼容的,默认是时间小的优先) * 2、rabbitTemplate.convertAndSend(book, message -> { * message.getMessageProperties().setExpiration(2 * 1000 + ""); * return message; * }); * TODO 第二种就是每次发送消息动态设置延迟时间,这样我们可以灵活控制 **/ @Bean public Queue delayProcessQueue() { Map params = new HashMap<>(); // x-dead-letter-exchange 声明了队列里的死信转发到的DLX名称, params.put("x-dead-letter-exchange", REGISTER_EXCHANGE_NAME); // x-dead-letter-routing-key 声明了这些死信在转发时携带的 routing-key 名称。 params.put("x-dead-letter-routing-key", ROUTING_KEY); return new Queue(REGISTER_DELAY_QUEUE, true, false, false, params); } /** * 需要将一个队列绑定到交换机上,要求该消息与一个特定的路由键完全匹配。 * 这是一个完整的匹配。如果一个队列绑定到该交换机上要求路由键 “dog”,则只有被标记为“dog”的消息才被转发,不会转发dog.puppy,也不会转发dog.guard,只会转发dog。 * TODO 它不像 TopicExchange 那样可以使用通配符适配多个 * * @return DirectExchange */ @Bean public DirectExchange delayExchange() { return new DirectExchange(REGISTER_DELAY_EXCHANGE); } @Bean public Binding dlxBinding() { return BindingBuilder.bind(delayProcessQueue()).to(delayExchange()).with(DELAY_ROUTING_KEY); } @Bean public Queue registerBookQueue() { return new Queue(REGISTER_QUEUE_NAME, true); } /** * 将路由键和某模式进行匹配。此时队列需要绑定要一个模式上。 * 符号“#”匹配一个或多个词,符号“*”匹配不多不少一个词。因此“audit.#”能够匹配到“audit.irs.corporate”,但是“audit.*” 只会匹配到“audit.irs”。 **/ @Bean public TopicExchange registerBookTopicExchange() { return new TopicExchange(REGISTER_EXCHANGE_NAME); } @Bean public Binding registerBookBinding() { // TODO 如果要让延迟队列之间有关联,这里的 routingKey 和 绑定的交换机很关键 return BindingBuilder.bind(registerBookQueue()).to(registerBookTopicExchange()).with(ROUTING_KEY); } } ``` ## 实体类 创建一个`Book`类 ``` public class Book implements java.io.Serializable { private static final long serialVersionUID = -2164058270260403154L; private String id; private String name; // 省略get set ... } ``` ## 控制器 编写一个`Controller`类,用于消息发送工作,同时为了看到测试效果,添加日志输出,将发送消息的时间记录下来.. ``` package com.battcn.controller; import com.battcn.config.RabbitConfig; import com.battcn.entity.Book; import com.battcn.handler.BookHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.amqp.support.converter.AbstractJavaTypeMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.time.LocalDateTime; /** * @author Levin * @since 2018/4/2 0002 */ @RestController @RequestMapping(value = "/books") public class BookController { private static final Logger log = LoggerFactory.getLogger(BookController.class); private final RabbitTemplate rabbitTemplate; @Autowired public BookController(RabbitTemplate rabbitTemplate) { this.rabbitTemplate = rabbitTemplate; } /** * this.rabbitTemplate.convertAndSend(RabbitConfig.REGISTER_DELAY_EXCHANGE, RabbitConfig.DELAY_ROUTING_KEY, book); 对应 {@link BookHandler#listenerDelayQueue} */ @GetMapping public void defaultMessage() { Book book = new Book(); book.setId("1"); book.setName("一起来学Spring Boot"); // 添加延时队列 this.rabbitTemplate.convertAndSend(RabbitConfig.REGISTER_DELAY_EXCHANGE, RabbitConfig.DELAY_ROUTING_KEY, book, message -> { // TODO 第一句是可要可不要,根据自己需要自行处理 message.getMessageProperties().setHeader(AbstractJavaTypeMapper.DEFAULT_CONTENT_CLASSID_FIELD_NAME, Book.class.getName()); // TODO 如果配置了 params.put("x-message-ttl", 5 * 1000); 那么这一句也可以省略,具体根据业务需要是声明 Queue 的时候就指定好延迟时间还是在发送自己控制时间 message.getMessageProperties().setExpiration(5 * 1000 + ""); return message; }); log.info("[发送时间] - [{}]", LocalDateTime.now()); } } ``` ## 消息消费者 默认情况下 `spring-boot-data-amqp` 是自动`ACK`机制,就意味着 MQ 会在消息消费完毕后自动帮我们去ACK,这样依赖就存在这样一个问题:**如果报错了,消息不会丢失,会无限循环消费,很容易就吧磁盘空间耗完,虽然可以配置消费的次数但这种做法也有失优雅。目前比较推荐的就是我们手动ACK然后将消费错误的消息转移到其它的消息队列中,做补偿处理。** 由于我们需要手动控制`ACK`,因此下面监听完消息后需要调用`basicAck`通知`rabbitmq`消息已被正确消费,可以将远程队列中的消息删除 ``` package com.battcn.handler; import com.battcn.config.RabbitConfig; import com.battcn.entity.Book; import com.rabbitmq.client.Channel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.amqp.core.Message; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component; import java.io.IOException; import java.time.LocalDateTime; /** * BOOK_QUEUE 消费者 * * @author Levin * @since 2018/4/11 0011 */ @Component public class BookHandler { private static final Logger log = LoggerFactory.getLogger(BookHandler.class); @RabbitListener(queues = {RabbitConfig.REGISTER_QUEUE_NAME}) public void listenerDelayQueue(Book book, Message message, Channel channel) { log.info("[listenerDelayQueue 监听的消息] - [消费时间] - [{}] - [{}]", LocalDateTime.now(), book.toString()); try { // TODO 通知 MQ 消息已被成功消费,可以ACK了 channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); } catch (IOException e) { // TODO 如果报错了,那么我们可以进行容错处理,比如转移当前消息进入其它队列 } } } ``` ## 主函数 ``` package com.battcn; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * @author Levin */ @SpringBootApplication public class Chapter12Application { public static void main(String[] args) { SpringApplication.run(Chapter12Application.class, args); } } ``` ## 测试 完成准备事项后,启动`Chapter12Application` 访问 [http://localhost:8080/books](http://localhost:8080/books) 将会看到如下内容,就代表一切正常…. ``` 2018-05-23 19:56:36.248 INFO 29048 --- [nio-8080-exec-1] com.battcn.controller.BookController : [发送时间] - [2018-05-23T19:56:36.248] 2018-05-23 19:56:41.256 INFO 29048 --- [cTaskExecutor-1] com.battcn.handler.BookHandler : [listenerDelayQueue 监听的消息] - [消费时间] - [2018-05-23T19:56:41.256] - [Book{id='1', name='一起来学Spring Boot'}] ``` # 总结 目前很多大佬都写过关于 **SpringBoot** 的教程了,如有雷同,请多多包涵,本教程基于最新的 `spring-boot-starter-parent:2.0.2.RELEASE`编写,包括新版本的特性都会一起介绍… #### 欢迎工作一到五年的Java工程师朋友们加入Java填坑之路:860113481 群内提供免费的Java架构学习资料(里面有高可用、高并发、高性能及分布式、Jvm性能调优、Spring源码,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多个知识点的架构资料)合理利用自己每一分每一秒的时间来学习提升自己,不要再用"没有时间“来掩饰自己思想上的懒惰!趁年轻,使劲拼,给未来的自己一个交代!
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/31557450/viewspace-2656107/ ,如需转载,请注明出处,否则将追究法律责任。
注册时间:2018-10-08