11-RabbitMQ的高级特性

下面的内容整理自:RabbitMQ实战指南.朱忠华

1 mandatory参数

mandatory参数源于channel.basicPublish方法,函数签名如下:

void basicPublish(String exchange,
                  String routingKey,
                  boolean mandatory,
                  boolean immediate,
                  BasicProperties props,
                  byte[] body) throws IOException;

mandatory参数设置为true的时候,如果exchange无法根据自身的type以及routing key找到一个符合条件的queue,那么RabbitMQ会调用Basic.Return命令将消息返回给生产者;当mandatory参数设置为false的时候,出现上述情形,则消息直接被丢弃。

如果消息生产者在调用channel.basicPublish方法的时候,设置mandatory参数为true,则可以通过调用channel.addReturnListener来添加一个ReturnListener,用以监听被RabbitMQ送回来的消息。

public class MandatoryProducer {

    public static final String EXCHANGE_MANDATORY = "exchange-mandatory";

    public static void main(String[] args) {
        // 1、创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("192.168.99.100");
        factory.setPort(5672);
        factory.setUsername("admin");
        factory.setPassword("password");
        // 2、获取连接、通道
        try (Connection connection = factory.newConnection();
             Channel channel = connection.createChannel()) {
            // 3、声明发送消息到哪个exchange
            channel.exchangeDeclare(EXCHANGE_MANDATORY, BuiltinExchangeType.DIRECT);
            // 4、设置ReturnListener
            channel.addReturnListener(new ReturnListener() {
                @Override
                public void handleReturn(int replyCode,
                                         String replyText,
                                         String exchange,
                                         String routingKey,
                                         AMQP.BasicProperties properties,
                                         byte[] body) throws IOException {
                    System.out.println("Receive return message: " + new String(body));
                    System.out.println(String.format("replyCode: %d\nreplyText: %s\nexchange: %s\nroutingKey: %s\n",
                            replyCode, replyText, exchange, routingKey));
                }
            });
            // 5、发送消息
            int count = 1;
            while (true) {
                String message = "Mandatory message, count = " + count;
                count++;
                // 设置mandatory = true
                channel.basicPublish(EXCHANGE_MANDATORY, "aaa", true, null, message.getBytes(StandardCharsets.UTF_8));
                System.out.println("Send message: " + message);
                Thread.sleep(2000);
            }
        } catch (TimeoutException | IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果:

"C:\Program Files\Java\jdk1.8.0_231\bin\java.exe" ...
Send message: Mandatory message, count = 1
Receive return message: Mandatory message, count = 1
replyCode: 312
replyText: NO_ROUTE
exchange: exchange-mandatory
routingKey: aaa

Send message: Mandatory message, count = 2
Receive return message: Mandatory message, count = 2
replyCode: 312
replyText: NO_ROUTE
exchange: exchange-mandatory
routingKey: aaa

2 备份交换机

备份交换机(Alternate Exchange)的作用是可以将那些未被路由的消息存储在RabbitMQ中,再在需要的时候处理这些消息。

可以通过在使用channel.exchangeDeclare声明交换机的时候,指定alternate-exchange参数来指定一个备份交换机。

// 参数
Map<String, Object> arguments = new HashMap<>();
arguments.put("alternate-exchange", "alter-exchange");
// 声明一个normal-exchange,指定他的备份交换机为alter-exchange
channel.exchangeDeclare("normal-exchange", BuiltinExchangeType.DIRECT, true, false, arguments);
// 声明备份交换机alter-exchange
channel.exchangeDeclare("alter-exchange", BuiltinExchangeType.FANOUT, true, false, null);
// 分别绑定queue
channel.queueDeclare("normal-queue", true, false, false, null);
channel.queueBind("normal-queue", "normal-exchange", "normal-key");
channel.queueDeclare("alter-queue", true, false, false, null);
channel.queueBind("alter-queue", "alter-exchange", "");

上面的代码声明了两个交换机normal-exchangealter-exchange,其中指定了alter-exchange为备份交换机。normal-queue通过normal-key绑定到normal-exchange上,alter-queue绑定到alter-exchange上。

当消息无法被normal-exchange路由的时候,将会被alter-exchange路由到alter-queue

注意:如果备份交换机和mandatory参数一起使用,则mandatory参数无效。

3 过期时间

TTLTime To Live的简称,过期时间。

RabbitMQ可以对消息、队列设置过期时间TTL

3.1 设置消息的TTL

有两种方法:

  • 通过队列属性设置消息的TTL 队列中所有的消息都有相同的TTL
  • 对消息本身单独设置TTL 每条消息可以有自己的TTL

当两种TTL都设置了,则消息的TTL以两者中较小的那个数值为准。

消息在队列中的时间一旦超过了设置的TTL,则消息将变成“死信”(Dead Message)。

对于第一种方式,一旦消息过期将会立即从队列中删除。对于第二种,消息过期了不一定立即从队列中删除,而是等到消息被消费的时候才做判断。

3.1.1 通过队列属性设置消息的TTL

在使用channel.queueDeclare声明队列的时候,通过x-message-ttl参数设置消息的TTL,单位为毫秒。

Map<String,Object> arguments = new HashMap<>();
arguments.put("x-message-ttl", 6000);
channel.queueDeclare("queue", true, false, false, arguments);

如果不设置TTL,表示消息不会过期;如果TTL设为0,表示除非此时可以直接将消息投递给消费者,否则该消息会被立即丢弃。

3.1.2 对消息本身单独设置TTL

在使用channel.basicPublish方法发送消息的时候,加入expiration参数设置消息的TTL,单位为毫秒。

AMQP.BasicProperties.Builder builder = new AMQP.BasicProperties.Builder();
builder.deliveryMode(2); // 持久化消息
builder.expiration("6000"); // 设置TTL = 6000 ms
channel.basicPublish("exchange", "routingKey", false, builder.build(), "message".getBytes());

3.2 设置队列的TTL

在使用channel.queueDeclare声明队列的时候,可以通过添加x-expires参数来设置队列的过期时间,单位为毫秒。

队列的过期时间定义了队列被自动删除前处于未使用状态的时间。未使用表示队列上没有任何消费者,队列没有被重新声明,且在过期时间内也未调用过Basic.Get命令。

Map<String, Object> arguments = new HashMap<>();
arguments.put("x-expires", 5 * 60 * 1000); // 5分钟
channel.queueDeclare("queue", false, false, false, arguments);

4 死信交换机、死信队列

可以在使用channel.queueDeclare声明队列的时候,通过参数x-dead-letter-exchange设置死信交换机。

死信交换机(Dead-Letter-Exchange,简称DLX)和一般的交换机没什么区别。绑定到DLX上面的队列叫做死信队列。

当消息变为死信,可以发送到死信队列上进行处理。

消息变为死信,一般有以下几种情况:

  • 消息被拒绝(Basic.Reject/Basic.Nack),并且设置requeuefalse
  • 消息过期;
  • 队列达到最大长度。
// 声明普通交换机
channel.exchangeDeclare("normal-exchange", BuiltinExchangeType.DIRECT);
// 声明死信交换机
channel.exchangeDeclare("dlx-exchange", BuiltinExchangeType.FANOUT);
// 队列参数
Map<String, Object> arguments = new HashMap<>();
arguments.put("x-message-ttl", 10000);
arguments.put("x-dead-letter-exchange", "dlx-exchange");
// 声明普通队列并绑定到normal-exchange
channel.queueDeclare("normal-queue", true, false, false, arguments);
channel.queueBind("normal-queue", "normal-exchange", "routingKey");
// 声明死信队列并绑定到dlx-exchange
channel.queueDeclare("dlx-queue", true, false, false, null);
channel.queueBind("dlx-queue", "dlx-exchange", "");

5 延迟队列

延迟队列用于存储延迟消息。

这些消息被发送之后,不希望立刻被消息者消费,而是希望等待一定时间之后再被消费。

RabbitMQ本身没有提供延迟队列的功能,但是可以通过死信交换机DLX和过期时间TTL来实现。

实现原理:消费者不去订阅普通队列,而是去订阅死信队列,当消息在普通队列过期之后,会被发送到相应的死信队列中,消息的过期时间就是延迟时间。

11-RabbitMQ的高级特性_第1张图片

6 优先级队列

优先级队列可以处理消息的优先级,高优先级的消息将会优先被消费者消费。

在调用channel.queueDeclare声明队列的时候,可以通过蛇者x-max-priority来设置队列允许的最高优先级的数值,默认最低为0

Map<String, Object> arguments = new HashMap<>();
arguments.put("x-max-priority", 10);
channel.queueDeclare("priority-queue", true, false, false, arguments);

在发送的消息中设置消息的优先级:

AMQP.BasicProperties.Builder builder = new AMQP.BasicProperties.Builder();
builder.priority(5); // 设置消息的优先级
channel.basicPublish("priority-exchange", "routingKey", false, builder.build(), "message".getBytes());

你可能感兴趣的:(RabbitMQ)