一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第5天
最近在开发一个项目,正好用到了消息队列,由于之前使用过rabbitmq,所以这次沿用了之前的技术栈。在开发一个延时队列的时候,本来想使用死信队列来实现这个功能,刚好同事说rabbitmq只需要安装一个插件就可以实现延时队列的功能了。
程序员就是要时刻抱有求知欲,遇到新奇的事物,就想着探索一番。于是我打开了rabbitmq的官网,顺着它的插件列表找到了一个和延时相关的插件rabbitmq-delayed-message-exchange。然后点击跳转到这个链接指向的GitHub仓库。可以看到相关的教程说明。
首先下载压缩安装包rabbitmq_delayed_message_exchange-3.8.0.ez到我们rabbitmq的安装目录下面会有一个plugins文件夹将安装包放在这里。然后切换到rabbitmq的sbin目录,执行下面的命令
rabbitmq-plugins enable rabbitmq_delayed_message_exchange
可以看到下面的输出信息,表示插件安装成功
Enabling plugins on node rabbit@DESKTOP-KRUG30P:
rabbitmq_delayed_message_exchange
The following plugins have been configured:
rabbitmq_delayed_message_exchange
rabbitmq_management
rabbitmq_management_agent
rabbitmq_web_dispatch
Applying plugin configuration to rabbit@DESKTOP-KRUG30P...
The following plugins have been enabled:
rabbitmq_delayed_message_exchange
set 4 plugins.
Offline change; changes will take effect at broker restart.
激活插件之后开始按照文档的教程进行开发验证,首先我们需要创建队列需要的exchange和queue
@Bean
public CustomExchange customDelayExchange() {
Map<String, Object> args = new HashMap<String, Object>();
args.put("x-delayed-type", "topic");
return new CustomExchange("plugin_delay_exchange", "x-delayed-message", false, true, args);
}
@Bean
public Queue pluginDelayQueue(){
return new Queue("plugin_delay_queue");
}
@Bean
public Binding delayBinding(CustomExchange customDelayExchange,Queue pluginDelayQueue){
return BindingBuilder
.bind(pluginDelayQueue)
.to(customDelayExchange)
.with("plugin.delay").noargs();
}
首先声明一个customerExchange,这个是自定义的交换器,可以自行配置参数。第一个plugin_delay_exchange是交换器的名称,第二个是使用插件延时功能默认的值,就是x-delayed-message,第三是是否持久化,我这里只是验证功能所以选择了false,一般测试和生产环境中需要设置为true,第四个是是否自动删除,和第三个是一样的理由我选择true,最后一个是注入一个参数map。这个map中有个x-delayed-type它可以指定我们exchange的类型,比如我们这里指明了类型是topic,那么我们这exchange就会被创建成拥有topic功能的exchange。 然后是声明一个队列,将队列和交换器用一个路由键绑定起来。 然后创建我们的生产者
@Override
public void delayPlugin(String info) {
LocalDateTime now = LocalDateTime.now();
rabbitTemplate.convertAndSend("plugin_delay_exchange","plugin.delay",info,message -> {
message.getMessageProperties().setHeader("x-delay","5000");
return message;
});
log.info("发送延时时间:[{}]", now.toString());
}
接受一个string类型的消息这里使用spring封装的convertAndSend方法。首先指定消息要投递的exchange,和我们之前声明的同名即可。然后是路由键和我们之前声明的同名即可,然后是我们的消息,最后是消息的处理器,在这里我们需要设置我们的header。和官方文档上一样,延时参数需要配置在消息的header上,并且参数名指定为x-delay 单位是毫秒。 然后我们创建一个消费者,监听这个队列
@RabbitListener(queues = "plugin_delay_queue")
public void pluginDelayListener(String message){
log.info("plugin delay info:[{}]", message);
}
启动我们的工程,推送一条消息,
2022-04-06 21:28:00.502 INFO 11736 --- [nio-9093-exec-1] c.z.s.service.impl.ProducerServiceImpl : 发送延时时间:[2022-04-06T21:28:00.479]
2022-04-06 21:28:00.505 ERROR 11736 --- [eferred-pool-11] o.s.amqp.rabbit.core.RabbitTemplate : 消息:null发送失败,响应码:312,响应信息:NO_ROUTE,交换器:plugin_delay_exchange,路由键:plugin.delay
2022-04-06 21:28:05.532 INFO 11736 --- [tContainer#12-1] c.z.s.service.listener.TopicListener : plugin delay info:[hello]
可以看到的确是延时了5s后被投递到了消费者上。这里遇到了一个问题,就是报了一个警告错误,官方文档中是给出了解释,是因为插件的这个功能是把消息暂存在磁盘上,在投递给消费者时,无法保证有队列可以路由,以及无法保证原连接还能支持回调方法。消息在磁盘的读写有一定时间,无法保证投递前后,队列信息一致。又因为rabbitmq本身有个mandatory参数用于消息无法路由时,消息回退的功能。我们在使用这个延时插件的时候这个功能是无法实现的,所以需要把mandatory=false,来避免触发这个消息回退的机制。
rabbitTemplate.setMandatory(false);
在投递延时消息之前,将这个参数置为false。这个错误就会消失。