@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();
}
}
新建一个消息的发送者
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import static com.talent.infocenter.rabbitMq.DelayedRabbitMQConfig.DELAYED_EXCHANGE_NAME;
import static com.talent.infocenter.rabbitMq.DelayedRabbitMQConfig.DELAYED_ROUTING_KEY;
@Service
public class Provider {
@Autowired
private RabbitTemplate rabbitTemplate;
public void sendDelayMsg(String msg, Integer delayTime) {
rabbitTemplate.convertAndSend(DELAYED_EXCHANGE_NAME, DELAYED_ROUTING_KEY, msg, a -> {
a.getMessageProperties().setDelay(delayTime*1000);
return a;
});
}
}
新建一个消息的消费者
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.DelayedRabbitMQConfig.DELAYED_QUEUE_NAME;
@Slf4j
@Component
public class Consumer {
@RabbitListener(queues = DELAYED_QUEUE_NAME)
public void receiveD(Message message, Channel channel) throws IOException {
String msg = new String(message.getBody());
log.info(“当前时间:{},延时队列收到消息:{}”, new Date().toString(), msg);
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
}
}
修改我们之前的接口
import com.talent.api.utils.RedisUtils;
import com.talent.infocenter.rabbitMq.Provider;
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 Provider provider;
@RequestMapping(value = “sendmsg”, method = RequestMethod.GET)
public void sendMsg(@RequestParam(value = “msg”) String msg, @RequestParam(value = “delayTime”) Integer delayTime) {
log.info(“当前时间:{},收到请求,msg:{},delayTime:{}”, new Date(), msg, delayTime);
provider.sendDelayMsg(msg, delayTime);
}
}
接下来开始测试
再接着测试一下我们不同顺序的订单是否是按照时间顺序进行消费的
我们将订单0002设置为60s,订单0003设置为15s,看看订单0003能否在0002之前消费
结果显而易见订单0003确实在第15s的时候被消费掉
5.总结
延时队列在需要延时处理的场景下非常有用,而且十分稳定,使用RabbitMQ来实现延时队列可以很好的利用RabbitMQ的特性,如:消息可靠发送、消息可靠投递、死信队列来保障消息至少被消费一次以及未被正确处理的消息不会被丢弃。另外,通过RabbitMQ集群的特性,可以很好的解决单点故障问题,不会因为单个节点挂掉导致延时队列不可用或者消息丢失。
当然,延时队列还有很多其它选择,比如利用Java的DelayQueu,利用Redis的zset,利用Quartz或者利用kafka的时间轮,这些方式各有特点。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)
最后
正值金三银四招聘旺季,很多小伙伴都询问我有没有前端方面的面试题,特地整理出来赠送给大家!
深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-WhXbavC0-1713551756744)]
[外链图片转存中…(img-6af7MrBr-1713551756744)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
[外链图片转存中…(img-i9BZrXHk-1713551756744)]
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)
[外链图片转存中…(img-8QImMAuq-1713551756745)]
最后
正值金三银四招聘旺季,很多小伙伴都询问我有没有前端方面的面试题,特地整理出来赠送给大家!