延迟任务通过消息的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,接下来我们设置任意时间的延迟队列
我们需要一种更通用的方案才能满足需求,那么就只能将TTL设置在消息属性里了,只有如此我们才能更加灵活的实现延迟队列的具体业务开发,方法也很简单,我们只需要增加一个延时队列,用于接收设置为任意延时时长的消息,同时增加一个相应的死信队列和routingkey,但是该方法有个极大的弊端就是如果使用在消息属性上设置TTL的方式,消息可能并不会按时“死亡“,因为RabbitMQ只会检查第一个消息是否过期,如果过期则丢到死信队列,所以如果第一个消息的延时时长很长,而第二个消息的延时时长很短,则第二个消息并不会优先得到执行,此处则不再进行编写代码,但是为了解决这个问题,我们将利用rabbitMq插件实现延迟队列。
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前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合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开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!