rubbitMq+spring实现延迟队列(适用订单超时未支付的问题)

什么是延迟队列


通俗一点说,延迟队列和我们生活中常用的定时器有点像,定时器会在指定的时间后响起,延迟队列则会在指定的时间后处理消息。延迟队列主要的应用场景有订单超时取消、超时自动评价等等。

实现原理


RabbitMQ给我们提供了TTL(Time-To-Live)和DLX (Dead-Letter-Exchange)这两个特性,使用RabbitMQ实现延迟队列利用的正是这两个特性。TTL用于控制消息的生存时间,如果超时,消息将变成Dead Letter。DLX用于配置死信队列,可以通过配置x-dead-letter-exchange和x-dead-letter-routing-key这两个参数来指定消息变成死信后需要路由到的交换器和队列。

图例

首先看看死信队列的图

然后就是延迟队列,延迟队列本身就是死信队列的扩展

代码实例

<?xml version="1.0" encoding="UTF-8"?>
<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/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/rabbit
                           http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">

    <!--创建连接工厂-->
    <rabbit:connection-factory id="mqConnectionFactory"
                               host="***.***.**.**"
                               port="****"
                               username="***"
                               password="***"
                               publisher-confirms="true"
                               virtual-host="*****"/>

    <!--通过指定下面的admin信息,当前producer中的exchange和queue会在rabbitmq服务器上自动生成 -->
    <rabbit:admin id="orderConnectAdmin" connection-factory="mqConnectionFactory"/>
   <!-- <rabbit:admin id="delayConnectAdmin" connection-factory="delayConnectionFactory"/>-->

    <!-- 说明:将需要关闭的订单的消息发送到此队列
        durable: 为 true 则设置队列为持久化。持久化的队列会存盘,在服务器重启的时候可以保证不丢失相关信息。
        autoDelete: 设置是否自动删除。为 true 则设置队列为自动删除。自动删除的前提是:至少有一个消费者连接到
        这个队列,之后所有与这个队列连接的消费者都断开时,才会自动删除
     -->
    <rabbit:queue name="shanreal_order_shutdown_queue" durable="false" auto-declare="true">
        <rabbit:queue-arguments>
            <entry key="x-message-ttl" value="10000" value-type="java.lang.Long" />
            <entry key="x-dead-letter-exchange" value="shanreal_exchange_delay" />
            <entry key="x-dead-letter-routing-key" value="shanreal_delay_key" />
        </rabbit:queue-arguments>
    </rabbit:queue>

    <!--正常交换机
         durable:true 持久化。可以将交换器存盘,在服务器重启 的时候不会丢失相关信息。false 反之
         auto-declare:true 自动删除。自动删除的前提是至少有一个队列或者交换器与这个交换器绑定 ,
         之后所有与这个交换器绑定的队列或者交换器都与此解绑
    -->
    <rabbit:fanout-exchange name="shanreal_exchange_normal" durable="false" auto-delete="true" id="shanreal_exchange_normal">
        <rabbit:bindings>
            <rabbit:binding queue="shanreal_order_shutdown_queue" />
        </rabbit:bindings>
    </rabbit:fanout-exchange>

    <!--死亡队列,用于存储超时消息-->
    <rabbit:queue name="shanreal_delay_queue" durable="false" auto-declare="true"/>

    <!--死亡交换机,消息过时后会由此交换机转发到对应的队列-->
    <rabbit:direct-exchange name="shanreal_exchange_delay" durable="false" auto-delete="true" id="shanreal_exchange_delay">
        <rabbit:bindings>
            <rabbit:binding queue="shanreal_delay_queue" key="shanreal_delay_key"/>
        </rabbit:bindings>
    </rabbit:direct-exchange>


    <!--定义rabbit orderAmqpTemplate用于数据的生产 -->
    <rabbit:template id="orderAmqpTemplate" connection-factory="mqConnectionFactory" exchange="shanreal_exchange_normal"/>

    <!--定义rabbit delayAmqpTemplate用于死信队列数据的消费 -->
    <rabbit:template
            id="delayAmqpTemplate"
            connection-factory="mqConnectionFactory"
            exchange="shanreal_exchange_delay"
            queue="shanreal_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.shanreal.controller.gb.rubbitMq.DelayTask" />

    <!-- Queue Listener 当有消息到达时会通知监听在对应的队列上的监听对象-->
    <rabbit:listener-container connection-factory="mqConnectionFactory" acknowledge="auto" task-executor="taskExecutor">
        <rabbit:listener queues="shanreal_delay_queue" ref="delayTask"/>
    </rabbit:listener-container>


</beans>

生产者:

这里可以单独对每个消息设置TTL(消息生存时间)

import com.rabbitmq.client.ConnectionFactory;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class ProducerService {

/*    @Resource(name="mqConnectionFactory")
    private ConnectionFactory mqConnectionFactory;*/

    @Resource(name="orderAmqpTemplate")
    private AmqpTemplate orderAmqpTemplate;

    public void send(String msg) {
        orderAmqpTemplate.convertAndSend((Object) msg, new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                message.getMessageProperties().setDelay(10000);
                return message;
            }
        });
        System.out.println("Sent: " + msg);
    }

}

消费者:

这里可以手动消费

import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class ConsumerService {
    @Resource(name="delayAmqpTemplate")
    private AmqpTemplate delayAmqpTemplate;

    public void recive() {
        System.out.println("Received: " + delayAmqpTemplate.receiveAndConvert());
    }
}

执行生产者操作

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.annotation.Resource;

@Controller
@RequestMapping(value="/mqTest")
public class TestController {

    @Resource(name = "producerService")
    private ProducerService producerService;

    @Resource(name = "consumerService")
    private ConsumerService consumerService;


    @ResponseBody
    @RequestMapping(value="/producerTest")
    public void producerTest() throws Exception {
        String  s = "我是生产者测试";
        producerService.send(s);
    }

    @ResponseBody
    @RequestMapping(value="/consumerTest")
    public void consumerTest() throws Exception {
        consumerService.recive();
    }
}

通过监听死信队列的消息来进行消费

import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageListener;

public class DelayTask implements MessageListener {
    @Override
    public void onMessage(Message message) {
        try {
            String receivedMsg = new String(message.getBody(), "UTF-8");
            System.out.println("Received : " + receivedMsg);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

参考:rubbitMq实战指南

需要此书籍的pdf文档的可以留言,我私发邮箱

猜你喜欢

转载自blog.csdn.net/wind_cp/article/details/84661359