一. 什么是延迟队列?
从字面意思上来看,延迟其实就是不会立即执行的会拖拖拉拉的感觉。就是在队列中的消息不会立刻被消费,而是需要等待一定的时间才会被消费。
二. 延迟队列的两种实现方式?
2.1 通过死信队列的方式
这种方式通过给消息或者队列设置TTL过期时间,如果说在设置的时间内该消息还没有被消费的话,那么就变成了"死消息",就进入我们预设的死信队列。
架构流程图:
上面图大概意思就是消费者发送消息并且设置过期时间到交换机,交换机通过routingkey转发到Q1,因为没有消费者,所以会等待消息过期后进入exchange2,死信交换机将这条消息转发到队列,被消费者消费,至此完成了简单的延迟队列的流程。
2.2 利用交换机插件
使用插件的方式就简单很多了,我们直接到官网上下载对应的插件。
插件名:`rabbitmq-delayed-message-exchange`
下载地址:https://github.com/rabbitmq/rabbitmq-delayed-message-exchange/releases
2.2.1 将下载的插件安装到rabbitmq中
这儿图方便直接使用docker,下载插件以后,放到服务器上通过如下命令进行插件安装:
1. 前提是docker已经安装,并且运行了rabbitmq容器。
docker cp /home/rabbitmq_delayed_message_exchange-3.11.1.ez rabbitmq:/plugins
2. 执行完成以上命令以后,进入容器安装插件
docker exec -it 容器名 /bin/bash
3. 进入rabbitmq容器的plugins目录
4. 执行如下命令安装插件
rabbitmq-plugins enable rabbitmq_delayed_message_exchange
如果一切顺利的话,插件安装成功,重启rabbitmq容器
docker xxxx restart
5. 访问rabbit管理页面
在exchange一栏去看交换机类型,如果有红色矩形的内容存在,那么插件安装成功。
三 小Demo练习
这儿主要使用的是插件的方式,因为使用死队列的方式具有局限性且不太易于理解。这一个小Demo主要是模拟活动预约以后,在活动开始之前给预约活动的用户发一封邮件提醒参加预约的活动,算是一个很小很小的Demo了吧。
通过插件实现的延迟队列代码架构图:
3.1 搭建项目工程
就使用springboot工程吧!
1. 导入项目依赖
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.2</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.47</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <!--RabbitMQ 测试依赖--> <dependency> <groupId>org.springframework.amqp</groupId> <artifactId>spring-rabbit-test</artifactId> <scope>test</scope> </dependency> </dependencies>
2. 构建结构
3.2 构建队列和交换机
构建交换机、队列、绑定关系
@Configuration public class DelayQueueConfig { private static final String DELAY_QUEUE = "delay.queue"; private static final String DELAY_EXCHANGE = "delay.exchange"; private static final String DELAY_ROUTING_KEY = "delay"; // 声明队列 @Bean public Queue delayQueue(){ return new Queue(DELAY_QUEUE); } // 延迟交换机配置 @Bean public CustomExchange customExchange(){ Map<String, Object> args = new HashMap<>(); // 声明交换机类型 args.put("x-delayed-type", "direct"); return new CustomExchange(DELAY_EXCHANGE, "x-delayed-message", true, false ,args); } // 绑定关系 @Bean public Binding delayQueueBindingCustomExchange( @Qualifier("customExchange") CustomExchange customExchange, @Qualifier("delayQueue") Queue delayQueue ){ return BindingBuilder.bind(delayQueue).to(customExchange).with(DELAY_ROUTING_KEY).noargs(); } }
生产者构建
@RestController @RequestMapping("/delay") public class RemindController { private static final String DELAY_EXCHANGE = "delay.exchange"; private static final String DELAY_ROUTING_KEY = "delay"; @Autowired private RabbitTemplate rabbitTemplate; @GetMapping("/{activityName}") public String activityRemind(@PathVariable String activityName){ // 延迟消息发送,简单模拟,10s rabbitTemplate.convertAndSend(DELAY_EXCHANGE, DELAY_ROUTING_KEY, activityName, correlationData ->{ correlationData.getMessageProperties().setDelay(10000); return correlationData; } ); return "ok"; } }
消费者构建
@Component @Slf4j public class DelayConsumer { // 当然也可以直接使用直接的方式绑定交换机 @RabbitListener(queues = "delay.queue") public void delayListener(Message message){ byte[] body = message.getBody(); log.info("您预约参与的活动:{}, 还剩10分钟就开始了,记得参加哦!", new String(body)); } }
最后测试一下搭建成功没有:
浏览器访问:localhost:8080/delay/篮球活动
10秒钟以后延迟队列发来消息,说明demo搭建成功。
3.3 集成邮件发送功能
最后在集成一个邮件功能用于消息的发送!
首先呢,我是用的是网易邮箱,需要设置开启SMTP服务。
以上步骤完成以后,我们直接引入starter依赖,很方便的让我门操作邮箱:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> </dependency>
yml配置文件
spring: rabbitmq: host: xxx username: xxx password: xxxx application: name: delay mail: #smtp服务主机 host: smtp.163.com #服务协议 protocol: smtp # 编码集 default-encoding: UTF-8 #发送邮件的账户 username: xxxx #授权码,开启stmp服务后会弹出授权码 password: xxxxx test-connection: true properties: mail: smtp: auth: true starttls: enable: true required: true
创建一个实体类:
@Data @AllArgsConstructor @NoArgsConstructor public class Email { // 他人的邮箱 private String[] user; // 邮件的主题 private String subject; // 邮件的内容 private String content; }
发送邮件的工具类:
@Component public class EmailSendUtil { @Autowired private JavaMailSender mailSender; @Value("${spring.mail.username}") private String senderName; public void sendMail(Email email){ SimpleMailMessage mailMessage = new SimpleMailMessage(); mailMessage.setFrom(senderName); // 邮件接收人 mailMessage.setTo(email.getUser()); // 邮件主题 mailMessage.setSubject(email.getSubject()); // 邮件内容 mailMessage.setText(email.getContent()); mailSender.send(mailMessage); } }
最后修改我们RabbitMq的消费者端:
@Component @Slf4j public class DelayConsumer { @Autowired private EmailSendUtil emailSendUtil; // 当然也可以直接使用直接的方式绑定交换机 @RabbitListener(queues = "delay.queue") public void delayListener(Message message){ byte[] body = message.getBody(); log.info("您预约参与的活动:{}, 还剩10分钟就开始了,记得参加哦!", new String(body)); String msg = String.format("您预约参与的活动:%s, 只有十分钟就要开始啦!", new String(body)); Email email = new Email(new String[]{"test@qq.com", "test@qq.com"}, "活动开始提醒", new String(msg)); emailSendUtil.sendMail(email); } }