开发过程中通常会碰到这样的需求:
淘宝订单业务:下单后 30min 之内没有付款,就自动取消订单。
饿了吗订餐通知:下单成功后 60s 之后给用户发送短信通知。
关闭空闲连接:服务器中有很多客户端的连接,空闲一段时间之后需要关闭之。
缓存:缓存中的对象,超过了空闲时间,从缓存中移出。
任务超时处理:在网络协议滑动窗口请求应答式交互时,处理超时未响应的请求。
失败重试机制:业务操作失败后,间隔一定的时间进行失败重试。
这类业务的特点就是:需要延迟工作,需要进行失败重试。一种比较笨的方式是使用一个后台线程,遍历所有对象,挨个检查。这种方法简单好用,但是对象数量过多时,可能存在性能问题,检查间隔时间不好设置,间隔时间过大,影响精确度,过小则存在效率问题,而且做不到按超时的时间顺序处理。
再比如常见的场景:
场景一:物联网系统经常会遇到向终端下发命令,如果命令一段时间没有应答,就需要设置成超时。
场景二:订单下单之后30分钟后,如果用户没有付钱,则系统自动取消订单。
实现原理
RabbitMQ给我们提供了TTL(Time-To-Live)和DLX (Dead-Letter-Exchange)这两个特性,使用RabbitMQ实现延迟队列利用的正是这两个特性。TTL用于控制消息的生存时间,如果超时,消息将变成Dead Letter。DLX用于配置死信队列,可以通过配置x-dead-letter-exchange和x-dead-letter-routing-key这两个参数来指定消息变成死信后需要路由到的交换器和队列。关于TTL(Time-To-Live)和DLX (Dead-Letter-Exchange)的详细介绍可以参考文末的参考链接。
代码示例
简单介绍一下,项目首先引入Spring与RabbitMQ相关的依赖,然后在spring-rabbitmq.xml中配置一个常规队列(delay_queue)和一个死信队列(task_queue),在常规队列上配置x-message-ttl和x-dead-letter-exchange等参数指向死信队列。最后配置任务处理器(delayTask),并将任务处理器与死信队列绑定并配置到监听器上。然后就可以开开心心地实现业务逻辑了~
项目中有两个Service,ProducerService和ConsumerService,导入项目后可以直接运行这两个类的main方法查看延迟队列的效果。也可以部署到本地服务器后运行web项目来测试,但是测试中只使用到了ProducerService。
spring-rabbitmq.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="connectionFactory" class="org.springframework.amqp.rabbit.connection.CachingConnectionFactory">
<property name="host" value="localhost"/>
<property name="port" value="5672"/>
<property name="username" value="guest"/>
<property name="password" value="guest"/>
</bean>
<rabbit:admin connection-factory="connectionFactory"/>
<rabbit:queue name="delay_queue" auto-declare="true">
<rabbit:queue-arguments>
<entry key="x-message-ttl" value="5000" value-type="java.lang.Long" />
<entry key="x-dead-letter-exchange" value="exchange_delay" />
<entry key="x-dead-letter-routing-key" value="task_queue" />
</rabbit:queue-arguments>
</rabbit:queue>
<rabbit:queue name="task_queue" auto-declare="true"/>
<rabbit:direct-exchange name="exchange_delay" durable="false" auto-delete="false" id="exchange_delay">
<rabbit:bindings>
<rabbit:binding queue="task_queue" />
</rabbit:bindings>
</rabbit:direct-exchange>
<rabbit:template id="amqpTemplate" connection-factory="connectionFactory" queue="delay_queue" routing-key="delay_queue"/>
<!-- 配置线程池 -->
<bean id ="taskExecutor" class ="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor" >
<!-- 线程池维护线程的最少数量 -->
<property name ="corePoolSize" value ="5" />
<!-- 线程池维护线程所允许的空闲时间 -->
<property name ="keepAliveSeconds" value ="30000" />
<!-- 线程池维护线程的最大数量 -->
<property name ="maxPoolSize" value ="1000" />
<!-- 线程池所使用的缓冲队列 -->
<property name ="queueCapacity" value ="200" />
</bean>
<bean id="delayTask" class="com.shenofusc.task.DelayTask" />
<!-- Queue Listener 当有消息到达时会通知监听在对应的队列上的监听对象-->
<rabbit:listener-container connection-factory="connectionFactory" acknowledge="auto" task-executor="taskExecutor">
<rabbit:listener queues="task_queue" ref="delayTask"/>
</rabbit:listener-container>
</beans>
DelayTask
public class DelayTask implements MessageListener {
public void onMessage(Message message) {
try {
String receivedMsg = new String(message.getBody(), "UTF-8");
System.out.println("Received : " + receivedMsg);
} catch (Exception e) {
e.printStackTrace();
}
}
}
ProducerService
@Service
public class ProducerService {
@Resource
private AmqpTemplate amqpTemplate;
public void send(String msg) {
amqpTemplate.convertAndSend(msg);
System.out.println("Sent: " + msg);
}
public static void main(String[] args) {
ApplicationContext context = new GenericXmlApplicationContext("classpath:/spring-rabbitmq.xml");
AmqpTemplate amqpTemplate = context.getBean(AmqpTemplate.class);
amqpTemplate.convertAndSend("Hello World");
System.out.println("Sent: Hello World");
}
}
ConsumerService
@Service
public class ConsumerService {
@Resource
private AmqpTemplate amqpTemplate;
public void recive() {
System.out.println("Received: " + amqpTemplate.receiveAndConvert());
}
public static void main(String[] args) {
ApplicationContext context = new GenericXmlApplicationContext("classpath:/spring-rabbitmq.xml");
AmqpTemplate amqpTemplate = context.getBean(AmqpTemplate.class);
System.out.println("Received: " + amqpTemplate.receiveAndConvert());
}
}
参考:https://blog.csdn.net/shy_1023/article/details/78517210
转载于:https://blog.51cto.com/4925054/2095453