rabbitmq 延时插件整合spring boot

385 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 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。这个错误就会消失。