rabbitMq实现延迟队列

延迟任务通过消息的TTL和Dead Letter Exchange来实现。我们需要建立2个队列,一个用于发送消息,一个用于消息过期后的转发目标队列,大致原理如下图所示。

在这里插入图片描述

生产者输出消息到Queue1,并且这个消息是设置有有效时间的,比如60s。消息会在Queue1中等待60s,如果没有消费者收掉的话,它就是被转发到Queue2,Queue2有消费者,收到,处理延迟任务。

接下来正式进入代码阶段

代码

  • 声明交换机、队列以及他们的绑定关系:

import org.springframework.amqp.core.*;

import org.springframework.beans.factory.annotation.Qualifier;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import java.util.HashMap;

import java.util.Map;

@Configuration

public class RabbitMQConfig {

public static final String DELAY_EXCHANGE_NAME = “delay.queue.demo.business.exchange”;//普通的交换机

public static final String DELAY_QUEUEA_NAME = “delay.queue.demo.business.queuea”;//声明两个队列 A B

public static final String DELAY_QUEUEB_NAME = “delay.queue.demo.business.queueb”;

public static final String DELAY_QUEUEA_ROUTING_KEY = “delay.queue.demo.business.queuea.routingkey”;

public static final String DELAY_QUEUEB_ROUTING_KEY = “delay.queue.demo.business.queueb.routingkey”;

public static final String DEAD_LETTER_EXCHANGE = “delay.queue.demo.deadletter.exchange”;//Dead Letter Exchanges

public static final String DEAD_LETTER_QUEUEA_ROUTING_KEY = “delay.queue.demo.deadletter.delay_10s.routingkey”;//死信交换机

public static final String DEAD_LETTER_QUEUEB_ROUTING_KEY = “delay.queue.demo.deadletter.delay_60s.routingkey”;

public static final String DEAD_LETTER_QUEUEA_NAME = “delay.queue.demo.deadletter.queuea”;

public static final String DEAD_LETTER_QUEUEB_NAME = “delay.queue.demo.deadletter.queueb”;

// 声明延时Exchange

@Bean(“delayExchange”)

public DirectExchange delayExchange() {

return new DirectExchange(DELAY_EXCHANGE_NAME);

}

// 声明死信Exchange

@Bean(“deadLetterExchange”)

public DirectExchange deadLetterExchange() {

return new DirectExchange(DEAD_LETTER_EXCHANGE);

}

// 声明延时队列A 延时10s

// 并绑定到对应的死信交换机

@Bean(“delayQueueA”)

public Queue delayQueueA() {

Map<String, Object> args = new HashMap<>(2);

// x-dead-letter-exchange 这里声明当前队列绑定的死信交换机

args.put(“x-dead-letter-exchange”, DEAD_LETTER_EXCHANGE);

// x-dead-letter-routing-key 这里声明当前队列的死信路由key

args.put(“x-dead-letter-routing-key”, DEAD_LETTER_QUEUEA_ROUTING_KEY);

// x-message-ttl 声明队列的TTL

args.put(“x-message-ttl”, 1000 * 10);

return QueueBuilder.durable(DELAY_QUEUEA_NAME).withArguments(args).build();

}

// 声明延时队列B 延时 60s

// 并绑定到对应的死信交换机

@Bean(“delayQueueB”)

public Queue delayQueueB() {

Map<String, Object> args = new HashMap<>(2);

// x-dead-letter-exchange 这里声明当前队列绑定的死信交换机

args.put(“x-dead-letter-exchange”, DEAD_LETTER_EXCHANGE);

// x-dead-letter-routing-key 这里声明当前队列的死信路由key

args.put(“x-dead-letter-routing-key”, DEAD_LETTER_QUEUEB_ROUTING_KEY);

// x-message-ttl 声明队列的TTL

args.put(“x-message-ttl”, 60000);

return QueueBuilder.durable(DELAY_QUEUEB_NAME).withArguments(args).build();

}

// 声明死信队列A 用于接收延时10s处理的消息

@Bean(“deadLetterQueueA”)

public Queue deadLetterQueueA() {

return new Queue(DEAD_LETTER_QUEUEA_NAME);

}

// 声明死信队列B 用于接收延时60s处理的消息

@Bean(“deadLetterQueueB”)

public Queue deadLetterQueueB() {

return new Queue(DEAD_LETTER_QUEUEB_NAME);

}

// 声明延时队列A绑定关系

@Bean

public Binding delayBindingA(@Qualifier(“delayQueueA”) Queue queue,

@Qualifier(“delayExchange”) DirectExchange exchange) {

return BindingBuilder.bind(queue).to(exchange).with(DELAY_QUEUEA_ROUTING_KEY);

}

// 声明业务队列B绑定关系

@Bean

public Binding delayBindingB(@Qualifier(“delayQueueB”) Queue queue,

@Qualifier(“delayExchange”) DirectExchange exchange) {

return BindingBuilder.bind(queue).to(exchange).with(DELAY_QUEUEB_ROUTING_KEY);

}

// 声明死信队列A绑定关系

@Bean

public Binding deadLetterBindingA(@Qualifier(“deadLetterQueueA”) Queue queue,

@Qualifier(“deadLetterExchange”) DirectExchange exchange) {

return BindingBuilder.bind(queue).to(exchange).with(DEAD_LETTER_QUEUEA_ROUTING_KEY);

}

// 声明死信队列B绑定关系

@Bean

public Binding deadLetterBindingB(@Qualifier(“deadLetterQueueB”) Queue queue,

@Qualifier(“deadLetterExchange”) DirectExchange exchange) {

return BindingBuilder.bind(queue).to(exchange).with(DEAD_LETTER_QUEUEB_ROUTING_KEY);

}

}

  • 消息的生产者

import org.springframework.amqp.rabbit.core.RabbitTemplate;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Component;

import org.springframework.stereotype.Service;

import static com.talent.infocenter.rabbitmq.RabbitMQConfig.*;

@Service

public class DelayMessageSender {

@Autowired

private RabbitTemplate rabbitTemplate;

public enum DelayTypeEnum {

DELAY_10s, DELAY_60s;

}

public static DelayTypeEnum getByIntValue(int value) {

switch (value) {

case 10:

return DelayTypeEnum.DELAY_10s;

case 60:

return DelayTypeEnum.DELAY_60s;

default:

return null;

}

}

public void sendMsg(String msg, DelayTypeEnum type) {

switch (type) {

case DELAY_10s:

rabbitTemplate.convertAndSend(DELAY_EXCHANGE_NAME, DELAY_QUEUEA_ROUTING_KEY, msg);

break;

case DELAY_60s:

rabbitTemplate.convertAndSend(DELAY_EXCHANGE_NAME, DELAY_QUEUEB_ROUTING_KEY, msg);

break;

}

}

}

  • 消费者

我们创建两个消费者,分别消费10s和60s的订单

import com.rabbitmq.client.Channel;

import lombok.extern.slf4j.Slf4j;

import org.springframework.amqp.core.Message;

import org.springframework.amqp.rabbit.annotation.RabbitListener;

import org.springframework.stereotype.Component;

import java.io.IOException;

import java.util.Date;

import static com.talent.infocenter.rabbitmq.RabbitMQConfig.DEAD_LETTER_QUEUEA_NAME;

import static com.talent.infocenter.rabbitmq.RabbitMQConfig.DEAD_LETTER_QUEUEB_NAME;

@Slf4j

@Component

public class DeadLetterQueueConsumer {

@RabbitListener(queues = DEAD_LETTER_QUEUEA_NAME)

public void receiveA(Message message, Channel channel) throws IOException {

String msg = new String(message.getBody());

log.info(“当前时间:{},死信队列A收到消息:{}”, new Date().toString(), msg);

channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);

}

@RabbitListener(queues = DEAD_LETTER_QUEUEB_NAME)

public void receiveB(Message message, Channel channel) throws IOException {

String msg = new String(message.getBody());

log.info(“当前时间:{},死信队列B收到消息:{}”, new Date().toString(), msg);

channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);

}

}

  • 创建一个接口进行测试

import com.talent.infocenter.rabbitmq.DelayMessageSender;

import lombok.extern.slf4j.Slf4j;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestMethod;

import org.springframework.web.bind.annotation.RequestParam;

import org.springframework.web.bind.annotation.RestController;

import java.util.Date;

import java.util.Objects;

@Slf4j

@RestController

public class RabbitMQMsgController {

@Autowired

private DelayMessageSender sender;

@RequestMapping(value = “sendmsg”, method = RequestMethod.GET)

public void sendMsg(@RequestParam(value = “msg”) String msg, @RequestParam(value = “delayType”) Integer delayType) {

log.info(“当前时间:{},收到请求,msg:{},delayType:{}”, new Date(), msg, delayType);

sender.sendMsg(msg, Objects.requireNonNull(DelayMessageSender.getByIntValue(delayType)));

}

}

接下来开始测试,我用的是swagger,大家可以用postman等其他方法自行测试

在这里插入图片描述

在这里插入图片描述

打开我们的rabbitmq后台就可以看到我们交换机和队列信息

在这里插入图片描述

同样的方法,我们创建一个60s之后才能消费的订单

在这里插入图片描述

在这里插入图片描述

上面的实现仅能设置两个指定的时间10s和60s,接下来我们设置任意时间的延迟队列

4.3 RabbitMq的优化


我们需要一种更通用的方案才能满足需求,那么就只能将TTL设置在消息属性里了,只有如此我们才能更加灵活的实现延迟队列的具体业务开发,方法也很简单,我们只需要增加一个延时队列,用于接收设置为任意延时时长的消息,同时增加一个相应的死信队列和routingkey,但是该方法有个极大的弊端就是如果使用在消息属性上设置TTL的方式,消息可能并不会按时“死亡“,因为RabbitMQ只会检查第一个消息是否过期,如果过期则丢到死信队列,所以如果第一个消息的延时时长很长,而第二个消息的延时时长很短,则第二个消息并不会优先得到执行,此处则不再进行编写代码,但是为了解决这个问题,我们将利用rabbitMq插件实现延迟队列。

4.4 利用插件实现延迟队列


4.4.1 下载插件

点击此处下载

在这里插入图片描述

下载完成之后进行解压,此处推荐bandzip进行解压,并且将解压之后的文件夹放到rabbitmq的安装目录下的plugins目录下

在这里插入图片描述

进入到sbin目录下使用cmd执行以下指令来启用插件

rabbitmq-plugins enable rabbitmq_delayed_message_exchange

在这里插入图片描述

执行以上步骤之后开始重启我们的rabbitmq服务

  • 1.进入到服务

在这里插入图片描述

  • 2.进入到sbin目录,双击rabbitmq-server.bat

在这里插入图片描述

验证是否重启成功访问http://localhost:15672

如果能够访问成功说明重启成功

4.4.2 编写代码

重新创建一个配置类

import org.springframework.amqp.core.Binding;

import org.springframework.amqp.core.BindingBuilder;

import org.springframework.amqp.core.CustomExchange;

import org.springframework.amqp.core.Queue;

import org.springframework.beans.factory.annotation.Qualifier;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import java.util.HashMap;

import java.util.Map;

@Configuration

public class DelayedRabbitMQConfig {

public static final String DELAYED_QUEUE_NAME = “delay.queue.demo.delay.queue”;

public static final String DELAYED_EXCHANGE_NAME = “delay.queue.demo.delay.exchange”;

public static final String DELAYED_ROUTING_KEY = “delay.queue.demo.delay.routingkey”;

@Bean

public Queue immediateQueue() {

return new Queue(DELAYED_QUEUE_NAME);

}

@Bean

public CustomExchange customExchange() {

Map<String, Object> args = new HashMap<>();

args.put(“x-delayed-type”, “direct”);

return new CustomExchange(DELAYED_EXCHANGE_NAME, “x-delayed-message”, true, false, args);

}

@Bean

public Binding bindingNotify(@Qualifier(“immediateQueue”) Queue queue,

@Qualifier(“customExchange”) CustomExchange customExchange) {

return BindingBuilder.bind(queue).to(customExchange).with(DELAYED_ROUTING_KEY).noargs();

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
ED_ROUTING_KEY).noargs();

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-9OXCYhXI-1715583194939)]

[外链图片转存中…(img-dzKhOBwB-1715583194940)]

[外链图片转存中…(img-ldUyZpjF-1715583194940)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

  • 30
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值