RabbitMQ學習之延時隊列

在實際的業務中我們會遇見生產者產生的消息,不立即消費,而是延時一段時間在消費。RabbitMQ本身沒有直接支持延遲隊列功能,但是我們可以根據其特性Per-Queue Message TTL和 Dead Letter Exchanges實現延時隊列。也可以通過改特性設置消息的優先級。

1.Per-Queue Message TTL
RabbitMQ可以針對消息和隊列設置TTL(過期時間)。隊列中的消息過期時間(Time To Live, TTL)有兩種方法可以設置。第一種方法是通過隊列屬性設置,隊列中所有消息都有相同的過期時間。第二種方法是對消息進行單獨設置,每條消息TTL可以不同。如果上述兩種方法同時使用,則消息的過期時間以兩者之間TTL較小的那個數值爲準。消息在隊列的生存時間一旦超過設置的TTL值,就成爲dead message,消費者將無法再收到該消息。
2.Dead Letter Exchanges
當消息在一個隊列中變成死信後,它能被重新publish到另一個Exchange。消息變成Dead Letter一向有以下幾種情況:
消息被拒絕(basic.reject or basic.nack)並且requeue=false
消息TTL過期
隊列達到最大長度
實際上就是設置某個隊列的屬性,當這個隊列中有Dead Letter時,RabbitMQ就會自動的將這個消息重新發布到設置的Exchange中去,進而被路由到另一個隊列,publish可以監聽這個隊列中消息做相應的處理,這個特性可以彌補RabbitMQ 3.0.0以前支持的immediate參數中的向publish確認的功能。

一、在隊列上設置TTL


1.建立delay.exchange


這裏Internal設置爲NO,否則將無法接受dead letter,YES表示這個exchange不可以被client用來推送消息,僅用來進行exchange和exchange之間的綁定。

2.建立延時隊列(delay queue)


如上配置延時5min隊列(x-message-ttl=300000)

x-max-length:最大積壓的消息個數,可以根據自己的實際情況設置,超過限制消息不會丟失,會立即轉向delay.exchange進行投遞

x-dead-letter-exchange:設置爲剛剛配置好的delay.exchange,消息過期後會通過delay.exchange進行投遞

這裏不需要配置"dead letter routing key"否則會覆蓋掉消息發送時攜帶的routingkey,導致後面無法路由爲剛纔配置的delay.exchange

3.配置延時路由規則

需要延時的消息到exchange後先路由到指定的延時隊列

1)創建delaysync.exchange通過Routing key將消息路由到延時隊列


2.配置delay.exchange 將消息投遞到正常的消費隊列

配置完成。

下面使用代碼測試一下:

生產者:

package cn.slimsmart.study.rabbitmq.delayqueue.queue;

import java.io.IOException;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

public class Producer {

	private static String queue_name = "test.queue";

	public static void main(String[] args) throws IOException {
		ConnectionFactory factory = new ConnectionFactory();
		factory.setHost("10.1.199.169");
		factory.setUsername("admin");
		factory.setPassword("123456");
		Connection connection = factory.newConnection();
		Channel channel = connection.createChannel();
		// 聲明隊列
		channel.queueDeclare(queue_name, true, false, false, null);
		String message = "hello world!" + System.currentTimeMillis();
		channel.basicPublish("delaysync.exchange", "deal.message", null, message.getBytes());
		System.out.println("sent message: " + message + ",date:" + System.currentTimeMillis());
		// 關閉頻道和連接
		channel.close();
		connection.close();
	}
}
消費者:

package cn.slimsmart.study.rabbitmq.delayqueue.queue;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.QueueingConsumer;

public class Consumer {

	private static String queue_name = "test.queue";

	public static void main(String[] args) throws Exception {
		ConnectionFactory factory = new ConnectionFactory();
		factory.setHost("10.1.199.169");
		factory.setUsername("admin");
		factory.setPassword("123456");
		Connection connection = factory.newConnection();
		Channel channel = connection.createChannel();
		// 聲明隊列
		channel.queueDeclare(queue_name, true, false, false, null);
		QueueingConsumer consumer = new QueueingConsumer(channel);
		// 指定消費隊列
		channel.basicConsume(queue_name, true, consumer);
		while (true) {
			// nextDelivery是一個阻塞方法(內部實現其實是阻塞隊列的take方法)
			QueueingConsumer.Delivery delivery = consumer.nextDelivery();
			String message = new String(delivery.getBody());
			System.out.println("received message:" + message + ",date:" + System.currentTimeMillis());
		}
	}

}
二、在消息上設置TTL


實現代碼:

生產者:

package cn.slimsmart.study.rabbitmq.delayqueue.message;

import java.io.IOException;
import java.util.HashMap;

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

public class Producer {

	private static String queue_name = "message_ttl_queue";

	public static void main(String[] args) throws IOException {
		ConnectionFactory factory = new ConnectionFactory();
		factory.setHost("10.1.199.169");
		factory.setUsername("admin");
		factory.setPassword("123456");
		Connection connection = factory.newConnection();
		Channel channel = connection.createChannel();
		HashMap<String, Object> arguments = new HashMap<String, Object>();
		arguments.put("x-dead-letter-exchange", "amq.direct");
		arguments.put("x-dead-letter-routing-key", "message_ttl_routingKey");
		channel.queueDeclare("delay_queue", true, false, false, arguments);

		// 聲明隊列
		channel.queueDeclare(queue_name, true, false, false, null);
		// 綁定路由
		channel.queueBind(queue_name, "amq.direct", "message_ttl_routingKey");

		String message = "hello world!" + System.currentTimeMillis();
		// 設置延時屬性
		AMQP.BasicProperties.Builder builder = new AMQP.BasicProperties.Builder();
		// 持久性 non-persistent (1) or persistent (2)
		AMQP.BasicProperties properties = builder.expiration("300000").deliveryMode(2).build();
		// routingKey =delay_queue 進行轉發
		channel.basicPublish("", "delay_queue", properties, message.getBytes());
		System.out.println("sent message: " + message + ",date:" + System.currentTimeMillis());
		// 關閉頻道和連接
		channel.close();
		connection.close();
	}
}
消費者:

package cn.slimsmart.study.rabbitmq.delayqueue.message;

import java.util.HashMap;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.QueueingConsumer;

public class Consumer {

	private static String queue_name = "message_ttl_queue";

	public static void main(String[] args) throws Exception {
		ConnectionFactory factory = new ConnectionFactory();
		factory.setHost("10.1.199.169");
		factory.setUsername("admin");
		factory.setPassword("123456");
		Connection connection = factory.newConnection();
		Channel channel = connection.createChannel();
		HashMap<String, Object> arguments = new HashMap<String, Object>();
		arguments.put("x-dead-letter-exchange", "amq.direct");
		arguments.put("x-dead-letter-routing-key", "message_ttl_routingKey");
		channel.queueDeclare("delay_queue", true, false, false, arguments);

		// 聲明隊列
		channel.queueDeclare(queue_name, true, false, false, null);
		// 綁定路由
		channel.queueBind(queue_name, "amq.direct", "message_ttl_routingKey");

		QueueingConsumer consumer = new QueueingConsumer(channel);
		// 指定消費隊列
		channel.basicConsume(queue_name, true, consumer);
		while (true) {
			// nextDelivery是一個阻塞方法(內部實現其實是阻塞隊列的take方法)
			QueueingConsumer.Delivery delivery = consumer.nextDelivery();
			String message = new String(delivery.getBody());
			System.out.println("received message:" + message + ",date:" + System.currentTimeMillis());
		}
	}

}

查看資料:

http://www.rabbitmq.com/ttl.html 
http://www.rabbitmq.com/dlx.html 
http://www.rabbitmq.com/maxlength.html
https://www.cloudamqp.com/docs/delayed-messages.html 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章