前言
■ 需求
一批XX任务按用户设置的时间开启与结束。
■ 解析
执行时间固定的情况用定时任务足矣,而任务执行时间未知,变化情况的下,就得将定时任务设置成每分钟甚至每秒,不断轮询需要执行的任务。显然,轮询会有性能压力、即时性等问题,这就需要延时队列来解决。在此,我选用RabbitMQ来实现延时队列。
实现方案
https://help.aliyun.com/document_detail/148083.html
阿里版RabbitMQ实现了原生延时消息方案,不过是收费的。开源版RabbitMQ通过死信队列间接实现延时消息是有缺点的,所以在此通过rabbitmq-delayed-message-exchange插件来实现延迟队列。
实现过程
■ 软件准备
rabbitmq-delayed-message-exchange插件下载
https://www.rabbitmq.com/community-plugins.html
并升级对应版本的 RabbitMQ 和 Erlang
而我选用的是 RabbitMQ 3.8.9、Erlang 23.1
■ 插件安装
将.ez的压缩文件放到rabbitmq安装目录的plugins文件夹里。
执行命令
rabbitmq-plugins enable rabbitmq_delayed_message_exchange
重启mq
■ 代码
1.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<description>rabbitmq 连接服务配置</description>
<!-- 加载配置属性文件 -->
<context:property-placeholder ignore-unresolvable="true" location="classpath:XXX.properties" />
<!-- 连接配置 -->
<rabbit:connection-factory channel-cache-size="10" id="mqConnectionFactory" host="${mq.host}" username="${mq.username}" password="${mq.password}" port="${mq.port}" virtual-host="${mq.vhost}"/>
<!--通过指定下面的admin信息,当前producer中的exchange和queue会在rabbitmq服务器上自动生成-->
<rabbit:admin connection-factory="mqConnectionFactory"/>
<!-- 声明消息队列 -->
<!-- durable:是否持久化,宕机恢复后会重持久化日志恢复消息队列 -->
<!-- exclusive: 仅创建者可以使用的私有队列,断开后自动删除 -->
<!-- auto_delete: 当所有消费客户端连接断开后,是否自动删除队列 -->
<!-- 若已经声明消息队列,并且设置跟以下设置不一致,会报错,必须先去管理中心删除该消息队列,再重新创建消息队列 -->
<rabbit:queue id="taskStartQueue" name="taskStartQueue" durable="true" auto-delete="false" exclusive="false" />
<rabbit:queue id="taskEndQueue" name="taskEndQueue" durable="true" auto-delete="false" exclusive="false" />
<util:map id="delayExchangeArgs" map-class="java.util.HashMap">
<entry key="x-delayed-type" value="direct"/>
</util:map>
<!-- 自定义交换机 - 延时交换机 : rabbitMq 第三方插件 rabbitmq_delayed_message_exchange 实现延时队列 -->
<bean id="taskExchange" name="delayExchange" class="org.springframework.amqp.core.CustomExchange">
<constructor-arg name="name" value="taskExchange"/>
<constructor-arg name="type" value="x-delayed-message"/>
<constructor-arg name="durable" value="true"/>
<constructor-arg name="autoDelete" value="false"/>
<constructor-arg name="arguments" ref="delayExchangeArgs"/>
</bean>
<util:map id="emptyMap" map-class="java.util.HashMap" />
<bean id="taskStartBind" class="org.springframework.amqp.core.Binding">
<constructor-arg name="destination" value="taskStartQueue"/>
<constructor-arg name="destinationType" value="QUEUE"/>
<constructor-arg name="exchange" value="taskExchange"/>
<constructor-arg name="routingKey" value="delay.task.start"/>
<constructor-arg name="arguments" ref="emptyMap"/>
</bean>
<bean id="taskEndBind" class="org.springframework.amqp.core.Binding">
<constructor-arg name="destination" value="taskEndQueue"/>
<constructor-arg name="destinationType" value="QUEUE"/>
<constructor-arg name="exchange" value="taskExchange"/>
<constructor-arg name="routingKey" value="delay.task.end"/>
<constructor-arg name="arguments" ref="emptyMap"/>
</bean>
</beans>
2.监听消费
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.context.annotation.Lazy;
import org.springframework.amqp.core.Message;
import org.springframework.stereotype.Service;
@Service
@Lazy(false)
public class QueueListener {
// 开始任务延时队列监听
@RabbitListener(queues = "taskStartQueue")
@RabbitHandler
public void taskStartQueueListener(Message msg){
Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter();
Task task = (task)jackson2JsonMessageConverter.fromMessage(msg);
logger.info("==================== 消费延时队列 - 开始任务 ====================\n";
}
// 结束任务延时队列监听
@RabbitListener(queues = "taskEndQueue")
@RabbitHandler
public void taskEndQueueListener(Message msg){
Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter();
Task task = (Task)jackson2JsonMessageConverter.fromMessage(msg);
logger.info("==================== 消费延时队列 - 结束任务 ====================\n";
}
}
■ 执行情况
1:30开始任务 - 18:30结束任务
最后
这样就可通过RabbitMQ实现延时队列功能。