rabbitmq延时队列

延时队列
在实际业务场景中可能会用到延时消息发送,例如支付场景,准时支付、超过未支付将执行不同的方案,其中超时未支付可以看做一个延时消息。
RabbitMQ本身不具有延时消息队列的功能,但是可以通过TTL(Time To Live)、DLX(Dead Letter Exchanges)特性实现。其原理给消息设置过期时间,在消息队列上为过期消息指定转发器,这样消息过期后会转发到与指定转发器匹配的队列上,变向实现延时队列。利用RabbitMQ的这种特性,应该可以实现很多现实中的业务,我们可以发挥想象。
rabbitmq-delayed-message-exchange,我们也可以使用插件来实现延时队列。利用TTL、DLX实现的延时队列可以中断,使用插件实现的延时队列是否可以中断?.该功能是已插件的形式实现的。在做实验时确保有安装过该插件
查询插件列表:

rabbitmq-plugins list

rabbitmq延时队列_第1张图片
image.png

如果没有安装插件
下载参考: https://blog.csdn.net/youjin/article/details/82586888

参考:https://blog.csdn.net/azhegps/article/details/53815117
https://www.cnblogs.com/haoxinyue/p/6613706.html

使用RabbitMQ来实现延迟任务必须先了解RabbitMQ的两个概念:消息的TTL和死信Exchange,通过这两者的组合来实现上述需求

过期方式有两种:
1.队列过期时间,发送该队列(queue1)的消息都是统一过期时间,过期后会进入死信队列(queue2)
2.消息过期时间,如果依次发送了10s,5s,3s的三条消息到该队列(queue1)的消息,队列会在10s过期才将消息从
queue1转发到queue2死信队列中(在队列queue1没有消费的情况下),监听队列queue2的消费者,这个时候才会消费到信息,
除非发送的消息达到queue1的
消费者消费的时候,也会在10s后才会消费这个过期消息,只有当过期的消息到了队列的顶端(队首),
才会被真正的丢弃或者进入死信队列(queue2)
如果发送的是5s,10s,3s的消息,则5s先消费,再消费10s和3s消息

实例:
1.先创建exchange:test.message
并绑定队列:my.timeout.message
routing: my.routing.key

2.创建队列:my.timeout.message
x-dead-letter-exchange: timeout-exchange
x-dead-letter-routing-key: test.timeout.message
durable: true

rabbitmq延时队列_第2张图片
image.png

3.创建消费过期转发exchange:timeout-exchange,类型fanout方式

并绑定队列:my.timeout.message
4.创建队列:my.timeout.message


rabbitmq延时队列_第3张图片
image.png

5.发送者

import java.io.IOException;
import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import com.rabbitmq.client.AMQP.BasicProperties;  
import com.rabbitmq.client.AMQP.BasicProperties.Builder;  
import com.rabbitmq.client.BuiltinExchangeType;  
import com.rabbitmq.client.Channel;  
import com.rabbitmq.client.Connection;  
  
/**
 * https://www.cnblogs.com/haoxinyue/p/6613706.html
 * 发送消息类 
 * @author
 * 
 */  
public class Send{
    private static final String EXCHANGE_NAME = "test.message";

    private final static String QUEUE_NAME = "my.timeout.message";

    private static final String ROUTKEY="my.routing.key";
    /** 
     * 在topic转发器的基础上练习延时转发,发送消息时指定消息过期时间 
     * 消息已发送到queue上,但未有consumer进行消费 
     * @param object 消息主体 
     * @throws IOException 
     */  
    public static void sendAToB(String object) throws Exception{
        Connection conn=MqManager.newConnection();  
        Channel channel=conn.createChannel();  
        //声明headers转发器  
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.HEADERS,true);
        //定义headers存储的键值对  
        Map headers=new HashMap();  
        headers.put("key", "123456");  
        headers.put("token", "654321");
      Map args = new HashMap();
       // args.put("x-message-ttl",12000); //消息过去
        args.put("x-dead-letter-exchange", "timeout-exchange");//过期消息转向路由
        args.put("x-dead-letter-routing-key", "test.timeout.message");//过期消息转向路由相匹配routingkey
       channel.queueDeclare(QUEUE_NAME, true, false, false, args);
        //把键值对放在properties  
        Builder properties=new BasicProperties.Builder();  
        properties.headers(headers);  
        properties.deliveryMode(2);//持久化
        //指定消息过期时间为12秒,队列上也可以指定消息的过期时间,两者以较小时间为准  
        properties.expiration("12000");//(12000)延时12秒,不会及时删除(在consuemr消费时判定是否过期,因为每条消息的过期时间不一致,删除过期消息就需要扫描整个队列)
        channel.basicPublish(EXCHANGE_NAME,ROUTKEY ,properties.build(),object.getBytes());
        System.out.println("Send '"+object+"'"+",时间:"+DateUtils.dataToStr(new Date()));
        channel.close();  
        conn.close();  
    }
    public static void main(String[] args) throws Exception {  
        sendAToB("我开始测试延时消息了3!");
    }  
} 

6.延期消费者

import java.io.IOException;
import java.util.Date;

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.BuiltinExchangeType;  
import com.rabbitmq.client.Channel;  
import com.rabbitmq.client.Connection;  
import com.rabbitmq.client.Consumer;  
import com.rabbitmq.client.DefaultConsumer;  
import com.rabbitmq.client.Envelope;  
  
/** 
 * 延时消息处理类 
 * @author  
 * 

* 即使一个消息比在同一队列中的其他消息提前过期,提前过期的也不会优先进入死信队列,它们还是按照入库的顺序让消费者消费。 * 如果第一进去的消息过期时间是1小时,那么死信队列的消费者也许等1小时才能收到第一个消息。 * 参考官方文档发现“Only when expired messages reach the head of a queue will they actually be discarded (or dead-lettered).”只有当过期的消息到了队列的顶端(队首), * 才会被真正的丢弃或者进入死信队列。所以在考虑使用RabbitMQ来实现延迟任务队列的时候,需要确保业务上每个任务的延迟时间是一致的。 * 如果遇到不同的任务类型需要不同的延时的话,需要为每一种不同延迟时间的消息建立单独的消息队列 * * *

*/ public class DelayRecv { private static final String EXCHANGE_NAME = "timeout-exchange"; private final static String QUEUE_NAME = "my.timeout.message2"; private static final String ROUTKEY="test.timeout.message"; /** * 创建队列并声明consumer用于处理转发过来的延时消息 * @throws Exception */ public static void delayRecv() throws Exception{ Connection conn=MqManager.newConnection(); final Channel channel=conn.createChannel(); channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT,true); String queueName=channel.queueDeclare().getQueue(); System.out.println("队列名称"+queueName); channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ROUTKEY); Consumer consumer=new DefaultConsumer(channel){ @Override public void handleDelivery(String consumerTag,Envelope envelope,AMQP.BasicProperties properties,byte[] body) throws IOException{ String mes= new String(body); System.out.println(envelope.getRoutingKey()+":delay Received :'"+mes+"' done,接受时间:"+DateUtils.dataToStr(new Date())); // channel.basicAck(envelope.getDeliveryTag(), true); // if(mes.indexOf("8")>0){ // System.out.println("测试回滚"); // channel.basicNack(envelope.getDeliveryTag(),true, true); // System.out.println("测试回滚完成"); // } } }; System.out.println("执行开始 "); //关闭自动应答机制,默认开启;这时候需要手动进行应该 channel.basicConsume(QUEUE_NAME, true, consumer); System.out.println("执行完了"); } public static void main(String[] args) throws Exception { delayRecv(); } }
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

/**
 */
public class MqManager {

    public static Connection newConnection()throws Exception{
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");
        factory.setUsername("guest");
        factory.setPassword("guest");
        factory.setVirtualHost("/");
        // 获取到连接以及mq通道
        Connection connection = factory.newConnection();
        return connection;
    }
}

sprintboot实现方式请见:https://www.jianshu.com/p/e75dab831d95

你可能感兴趣的:(rabbitmq延时队列)