四、RabbitMQ消息的消费

1.消息的获得方式

1.1 拉取Get

属于一种轮询模型,发送一次get请求,获得一个消息。如果此时RabbitMQ中没有消息,会获得一个表示空的回复。总的来说,这种方式性能比较差,很明显,每获得一条消息,都要和RabbitMQ进行网络通信发出请求。而且对RabbitMQ来说,RabbitMQ无法进行任何优化,因为它永远不知道应用程序何时会发出请求。

  • 普通生产者
package getmessage;

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

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 *
 */
public class GetMessageProducer {

    public final static String EXCHANGE_NAME = "direct_logs";

    public static void main(String[] args) throws IOException, TimeoutException {
        /**
         * 创建连接连接到MabbitMQ
         */
        ConnectionFactory factory = new ConnectionFactory();
        // 设置MabbitMQ所在主机ip或者主机名
        factory.setHost("127.0.0.1");

        // 创建一个连接
        Connection connection = factory.newConnection();
        // 创建一个信道
        Channel channel = connection.createChannel();
        // 指定转发
        channel.exchangeDeclare(EXCHANGE_NAME, "direct");

        //所有日志严重性级别
        for (int i = 0; i < 3; i++) {
            //每一次发送一条不同严重性的日志

            // 发送的消息
            String message = "Hello World_" + (i + 1);
            //参数1:exchange name
            //参数2:routing key
            channel.basicPublish(EXCHANGE_NAME, "error",
                    null, message.getBytes());
            System.out.println(" [x] Sent 'error':'"
                    + message + "'");
        }
        // 关闭频道和连接
        channel.close();
        connection.close();
    }

}

  • 消费者
package getmessage;

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

/**
 * @Author: Rab
 * @Date: 2020-04-15 21:45
 * @Description:
 */
public class GetMessageConsumer {

    public static void main(String[] args) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");

        // 打开连接和创建频道,与发送端一样
        Connection connection = factory.newConnection();
        final Channel channel = connection.createChannel();

        channel.exchangeDeclare(GetMessageProducer.EXCHANGE_NAME,
                "direct");
        // 声明一个队列
        String queueName = "focuserror";
        channel.queueDeclare(queueName,
                false, false,
                false, null);

        String severity = "error";//只关注error级别的日志,然后记录到文件中去。
        channel.queueBind(queueName,
                GetMessageProducer.EXCHANGE_NAME, severity);

        System.out.println(" [*] Waiting for messages......");

        while (true) {
            GetResponse getResponse = channel.basicGet(queueName, true);
            if (null != getResponse) {
                System.out.println("received["
                        +getResponse.getEnvelope().getRoutingKey()+"]"
                        +new String(getResponse.getBody()));
            }
            Thread.sleep(1000);
        }
    }
}

1.2 推送Consume

属于一种推送模型。注册一个消费者后,RabbitMQ会在消息可用时,自动将消息进行推送给消费者

  • 普通生产者
package exchange.direct;

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

/**
 * @Author: Rab
 * @Date: 2020-04-15 11:13
 * @Description:
 */
public class DirectProducer {

    public final static String EXCHANGE_NAME = "direct_logs";

    public static void main(String[] args) throws Exception {
        //创建连接
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        Connection connection = connectionFactory.newConnection();

        //创建信道
        Channel channel = connection.createChannel();

        //创建交换器
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);

        //日志消息级别,作为路由键使用
        String[] serverities = {"error", "info", "warning"};

        for (int i = 0; i < 9; i++) {
            String severity = serverities[i % 3];
            String msg = "Hellol,RabbitMq" + (i + 1);

            //发布消息
            channel.basicPublish(EXCHANGE_NAME, severity, null, msg.getBytes());
        }
        channel.close();
        connection.close();
    }
}
  • 消费者
package exchange.direct;

import com.rabbitmq.client.*;

import java.io.IOException;

/**
 * @Author: Rab
 * @Date: 2020-04-15 11:44
 * @Description: 普通消费者
 */
public class NormalConsumer {

    public static void main(String[] args) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");

        //打开连接和创建信道,与发送端一样
        Connection connection = factory.newConnection();

        Channel channel = connection.createChannel();

        channel.exchangeDeclare(DirectProducer.EXCHANGE_NAME, BuiltinExchangeType.DIRECT);

        //声明一个队列
        String queueName = "focuserror";

        channel.queueDeclare(queueName, false, false,
                false, null);

        //将队列和交换器通过路由键绑定,表示只关注info级别的消息
        String routeKey = "info";
        channel.queueBind(queueName, DirectProducer.EXCHANGE_NAME, routeKey);

        System.out.println("waiting for message........");

        //声明一个消费者
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String message = new String(body, "UTF-8");
                System.out.println("Received[" + envelope.getRoutingKey()
                        + "]" + message);
            }
        };

        channel.basicConsume(queueName, true, consumer);
    }
}

2.消息的应答

前面说过,消费者收到的每一条消息都必须进行确认。消息确认后,RabbitMQ才会从队列删除这条消息,RabbitMQ不会为未确认的消息设置超时时间,它判断此消息是否需要重新投递给消费者的唯一依据是消费该消息的消费者连接是否已经断开。这么设计的原因是RabbitMQ允许消费者消费一条消息的时间可以很久很久。

2.1 自动确认

消费者在声明队列时,可以指定autoAck参数,当autoAck=true时,一旦消费者接收到了消息,就视为自动确认了消息。如果消费者在处理消息的过程中,出了错,就没有什么办法重新处理这条消息,所以我们很多时候,需要在消息处理成功后,再确认消息,这就需要手动确认。

2.2 自行手动确认

当autoAck=false时,RabbitMQ会等待消费者显式发回ack信号后才从内存(和磁盘,如果是持久化消息的话)中移去消息。否则,RabbitMQ会在队列中消息被消费后立即删除它。

采用消息确认机制后,只要令autoAck=false,消费者就有足够的时间处理消息(任务),不用担心处理消息过程中消费者进程挂掉后消息丢失的问题,因为RabbitMQ会一直持有消息直到消费者显式调用basicAck为止。

当autoAck=false时,对于RabbitMQ服务器端而言,队列中的消息分成了两部分:一部分是等待投递给消费者的消息;一部分是已经投递给消费者,但是还没有收到消费者ack信号的消息。如果服务器端一直没有收到消费者的ack信号,并且消费此消息的消费者已经断开连接,则服务器端会安排该消息重新进入队列,等待投递给下一个消费者(也可能还是原来的那个消费者)。

  • 生产者
package ackfalse;

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

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 */
public class AckFalseProducer {

    public final static String EXCHANGE_NAME = "direct_logs";

    public static void main(String[] args) throws IOException, TimeoutException {
        /**
         * 创建连接连接到MabbitMQ
         */
        ConnectionFactory factory = new ConnectionFactory();
        // 设置MabbitMQ所在主机ip或者主机名
        factory.setHost("127.0.0.1");

        // 创建一个连接
        Connection connection = factory.newConnection();
        // 创建一个信道
        Channel channel = connection.createChannel();
        // 指定转发
        channel.exchangeDeclare(EXCHANGE_NAME, "direct");

        //所有日志严重性级别
        for (int i = 0; i < 3; i++) {
            //每一次发送一条不同严重性的日志

            // 发送的消息
            String message = "Hello World_" + (i + 1);
            //参数1:exchange name
            //参数2:routing key
            channel.basicPublish(EXCHANGE_NAME, "error",
                    null, message.getBytes());
            System.out.println(" [x] Sent 'error':'"
                    + message + "'");
        }
        // 关闭频道和连接
        channel.close();
        connection.close();
    }
}
  • 消费者
package ackfalse;

import com.rabbitmq.client.*;
import exchange.direct.DirectProducer;

import java.io.IOException;

/**
 * @Author: Rab
 * @Date: 2020-04-16 07:18
 * @Description:
 */
public class AckFalseConsumerA {

    public static void main(String[] args) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");

        // 打开连接和创建频道,与发送端一样
        Connection connection = factory.newConnection();
        final Channel channel = connection.createChannel();
        channel.exchangeDeclare(DirectProducer.EXCHANGE_NAME,
                "direct");

        /*声明一个队列*/
        String queueName = "focuserror";
        channel.queueDeclare(queueName, false, false,
                false, null);

        /*绑定,将队列和交换器通过路由键进行绑定*/
        String routekey = "error";/*表示只关注error级别的日志消息*/
        channel.queueBind(queueName, DirectProducer.EXCHANGE_NAME, routekey);

        System.out.println("waiting for message........");

        final Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                try {
                    String message = new String(body, "UTF-8");
                    System.out.println("Received["+envelope.getRoutingKey()
                            +"]"+message);
                    //确认
                    channel.basicAck(envelope.getDeliveryTag(), false);
                } catch (Exception e) {
                    e.printStackTrace();
                    //拒绝
                    channel.basicNack(envelope.getDeliveryTag(), false, true);
                }
            }
        };
        /*消费者正式开始在指定队列上消费消息*/
        channel.basicConsume(queueName, false, consumer);
    }
}

我们一般使用手动确认的方法是,将消息的处理放在try/catch语句块中,成功处理了,就给RabbitMQ一个确认应答,如果处理异常了,就在catch中,进行消息的拒绝

3.QoS预取模式

在确认消息被接收之前,消费者可以预先要求接收一定数量的消息,在处理完一定数量的消息后,批量进行确认。如果消费者应用程序在确认消息之前崩溃,则所有未确认的消息将被重新发送给其他消费者。所以这里存在着一定程度上的可靠性风险。

这种机制一方面可以实现限速(将消息暂存到RabbitMQ内存中)的作用,一方面可以保证消息确认质量(比如确认了但是处理有异常的情况)。

注意:消费确认模式必须是非自动ACK机制(这个是使用baseQos的前提条件,否则会Qos不生效),然后设置basicQos的值;另外,还可以基于consume和channel的粒度进行设置(global)。

  • 普通生产者
package qos;

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

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 */
public class QosProducer {

    public final static String EXCHANGE_NAME = "direct_logs";

    public static void main(String[] args) throws IOException, TimeoutException {
        /**
         * 创建连接连接到MabbitMQ
         */
        ConnectionFactory factory = new ConnectionFactory();
        // 设置MabbitMQ所在主机ip或者主机名
        factory.setHost("127.0.0.1");

        // 创建一个连接
        Connection connection = factory.newConnection();
        // 创建一个信道
        Channel channel = connection.createChannel();
        // 指定转发
        channel.exchangeDeclare(EXCHANGE_NAME, "direct");

        //发送210条消息,其中第210条消息表示本批次消息的结束
        for (int i = 0; i < 210; i++) {
            // 发送的消息
            String message = "Hello World_" + (i + 1);
            if (i == 209) {
                message = "stop";
            }
            //参数1:exchange name
            //参数2:routing key
            channel.basicPublish(EXCHANGE_NAME, "error",
                    null, message.getBytes());
            System.out.println(" [x] Sent 'error':'"
                    + message + "'");
        }
        // 关闭频道和连接
        channel.close();
        connection.close();
    }
}
  • 单条确认
package qos;

import com.rabbitmq.client.*;
import exchange.direct.DirectProducer;

import java.io.IOException;

/**
 * @Author: Rab
 * @Date: 2020-04-16 07:38
 * @Description:
 */
public class QosConsumerMain {

    public static void main(String[] args) throws Exception {

        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");

        // 打开连接和创建频道,与发送端一样
        Connection connection = factory.newConnection();
        final Channel channel = connection.createChannel();
        channel.exchangeDeclare(QosProducer.EXCHANGE_NAME,
                "direct");

        /*声明一个队列*/
        String queueName = "focuserror";
        channel.queueDeclare(queueName, false, false,
                false, null);

        /*绑定,将队列和交换器通过路由键进行绑定*/
        String routekey = "error";/*表示只关注error级别的日志消息*/
        channel.queueBind(queueName, QosProducer.EXCHANGE_NAME, routekey);

        System.out.println("waiting for message........");

        /*声明了一个消费者*/
        final Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag,
                                       Envelope envelope,
                                       AMQP.BasicProperties properties,
                                       byte[] body) throws IOException {
                String message = new String(body, "UTF-8");
                System.out.println("Received[" + envelope.getRoutingKey()
                        + "]" + message);
                //确认
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        };

        channel.basicQos(150, true);

        BatchAckConsumer batchAckConsumer = new BatchAckConsumer(channel);

        channel.basicConsume(queueName, false, consumer);

        channel.basicConsume(queueName, false, batchAckConsumer);

    }
}
  • 批量确认
package qos;

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;

import java.io.IOException;

/**
 * @Author: Rab
 * @Date: 2020-04-16 07:44
 * @Description:
 */
public class BatchAckConsumer extends DefaultConsumer {

    private int messasgeCount = 0;

    /**
     * Constructs a new instance and records its association to the passed-in channel.
     *
     * @param channel the channel to which this consumer is attached
     */
    public BatchAckConsumer(Channel channel) {
        super(channel);

        System.out.println("批量消费者启动...");
    }

    @Override
    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
        String message = new String(body, "UTF-8");
        System.out.println("批量消费者Received[" + envelope.getRoutingKey()
                + "]" + message);
        messasgeCount++;

        if (messasgeCount % 50 == 0) {
            this.getChannel().basicAck(envelope.getDeliveryTag(), true);
            System.out.println("批量消费者进行消息的确认-------------");
        }

        if (message.equals("stop")) {
            this.getChannel().basicAck(envelope.getDeliveryTag(), true);
            System.out.println("批量消费者进行最后部分业务消息的确认-------------");
        }
    }
}

我们可以进行批量确认,也可以进行单条确认

basicQos方法参数详细解释:

  • prefetchSize:最多传输的内容的大小的限制,0为不限制,但据说prefetchSize参数,rabbitmq没有实现。
  • prefetchCount:会告诉RabbitMQ不要同时给一个消费者推送多于N个消息,即一旦有N个消息还没有ack,则该consumer将block掉,直到有消息ack
  • global:true\false 是否将上面设置应用于channel,简单点说,就是上面限制是channel级别的还是consumer级别。

如果同时设置channel和消费者,会怎么样?AMQP规范没有解释如果使用不同的全局值多次调用basic.qos会发生什么。 RabbitMQ将此解释为意味着两个预取限制应该彼此独立地强制执行; 消费者只有在未达到未确认消息限制时才会收到新消息。

  • channel.basicQos(10, false); // Per consumer limit
  • channel.basicQos(15, true); // Per channel limit
  • channel.basicConsume(“my-queue1”, false, consumer1);
  • channel.basicConsume(“my-queue2”, false, consumer2);

也就是说,整个通道加起来最多允许15条未确认的消息,每个消费者则最多有10条消息。

4.消费者中的事务

使用方法和生产者一致
假设消费者模式中使用了事务,并且在消息确认之后进行了事务回滚,会是什么样的结果?
结果分为两种情况:

  1. autoAck=false的返回结果,再做最终决定是确认消息还是重新放回队列,手动应对的时候是支持事务的,也就是说即使你已经手动确认了消息已经收到了,但RabbitMQ对消息的确认会等事务如果你手动确认之后,又回滚了事务,那么以事务回滚为准,此条消息会重新放回队列;
  2. autoAck=true如果自动确认为true的情况是不支持事务的,也就是说你即使在收到消息之后在回滚事务也是于事无补的,队列已经把消息移除了。

5.可靠性和性能的权衡

在这里插入图片描述

6.消息的拒绝

6.1 Reject和Nack

消息确认可以让RabbitMQ知道消费者已经接受并处理完消息。但是如果消息本身或者消息的处理过程出现问题怎么办?需要一种机制,通知RabbitMQ,这个消息,我无法处理,请让别的消费者处理。这里就有两种机制,Reject和Nack。

Reject在拒绝消息时,可以使用requeue标识,告诉RabbitMQ是否需要重新发送给别的消费者。不重新发送,一般这个消息就会被RabbitMQ丢弃。Reject一次只能拒绝一条消息。

Nack则可以一次性拒绝多个消息。这是RabbitMQ对AMQP规范的一个扩展。

  • 普通生产者
package rejectmsg;

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

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 */
public class RejectProducer {

    public final static String EXCHANGE_NAME = "direct_logs";

    public static void main(String[] args) throws IOException, TimeoutException {
        /**
         * 创建连接连接到RabbitMQ
         */
        ConnectionFactory factory = new ConnectionFactory();
        // 设置MabbitMQ所在主机ip或者主机名
        factory.setHost("127.0.0.1");

        // 创建一个连接
        Connection connection = factory.newConnection();
        // 创建一个信道
        Channel channel = connection.createChannel();
        // 指定转发
        channel.exchangeDeclare(EXCHANGE_NAME, "direct");

        //所有日志严重性级别
        for (int i = 0; i < 10; i++) {
            //每一次发送一条不同严重性的日志

            // 发送的消息
            String message = "Hello World_" + (i + 1);
            //参数1:exchange name
            //参数2:routing key
            channel.basicPublish(EXCHANGE_NAME, "error",
                    null, message.getBytes());
            System.out.println(" [x] Sent 'error':'"
                    + message + "'");
        }
        // 关闭频道和连接
        channel.close();
        connection.close();
    }
}
  • Reject拒绝
package rejectmsg;

import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 */
public class RejectRequeuConsumer {

    public static void main(String[] argv)
            throws IOException, TimeoutException {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");

        // 打开连接和创建频道,与发送端一样
        Connection connection = factory.newConnection();
        final Channel channel = connection.createChannel();
        channel.exchangeDeclare(RejectProducer.EXCHANGE_NAME,
                "direct");

        /*声明一个队列*/
        String queueName = "focuserror";
        channel.queueDeclare(queueName, false, false,
                false, null);

        /*绑定,将队列和交换器通过路由键进行绑定*/
        String routekey = "error";/*表示只关注error级别的日志消息*/
        channel.queueBind(queueName, RejectProducer.EXCHANGE_NAME, routekey);

        System.out.println("waiting for message........");

        /*声明了一个消费者*/
        final Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag,
                                       Envelope envelope,
                                       AMQP.BasicProperties properties,
                                       byte[] body) throws IOException {
                try {
                    String message = new String(body, "UTF-8");
                    System.out.println("Received["
                            + envelope.getRoutingKey()
                            + "]" + message);
                    //抛出异常
                    throw new RuntimeException("处理异常"+message);
                } catch (Exception e) {
                    e.printStackTrace();
                    //拒绝
                    channel.basicReject(envelope.getDeliveryTag(), true);
                }

            }
        };
        /*消费者正式开始在指定队列上消费消息*/
        channel.basicConsume(queueName, false, consumer);
    }
}

6.2 死信交换器

RabbitMQ对AMQP规范的一个扩展。被投递消息被拒绝后的一个可选行为,往往用在对问题消息的诊断上。

消息变成死信一般是以下几种情况:

  • 消息被拒绝,并且设置 requeue 参数为 false
  • 消息过期
  • 队列达到最大长度

死信交换器仍然只是一个普通的交换器,创建时并没有特别要求和操作。在创建队列的时候,声明该交换器将用作保存被拒绝的消息即可,相关的参数是x-dead-letter-exchange。

在这里插入图片描述

  • 普通生产者
package dlx;

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

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 */
public class DlxProducer {

    public final static String EXCHANGE_NAME = "dlx_make";

    public static void main(String[] args) throws IOException, TimeoutException {
        /**
         * 创建连接连接到MabbitMQ
         */
        ConnectionFactory factory = new ConnectionFactory();
        // 设置MabbitMQ所在主机ip或者主机名
        factory.setHost("127.0.0.1");

        // 创建一个连接
        Connection connection = factory.newConnection();
        // 创建一个信道
        Channel channel = connection.createChannel();
        // 指定转发
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);

        /*日志消息级别,作为路由键使用*/
        String[] serverities = {"error", "info", "warning"};
        for (int i = 0; i < 3; i++) {
            String severity = serverities[i % 3];
            String msg = "Hellol,RabbitMq" + (i + 1);
            /*发布消息,需要参数:交换器,路由键,其中以日志消息级别为路由键*/
            channel.basicPublish(EXCHANGE_NAME, severity, null,
                    msg.getBytes());
            System.out.println("Sent " + severity + ":" + msg);
        }
        // 关闭频道和连接
        channel.close();
        connection.close();
    }
}
  • 消费者:队列绑定死信交换器
package dlx;

import com.rabbitmq.client.*;

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

/**
 * @Author: Rab
 * @Date: 2020-04-16 10:07
 * @Description:
 */
public class WillMakeDlxConsumer {

    public static void main(String[] args) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");

        // 打开连接和创建频道,与发送端一样
        Connection connection = factory.newConnection();
        final Channel channel = connection.createChannel();
        channel.exchangeDeclare(DlxProducer.EXCHANGE_NAME,
                BuiltinExchangeType.TOPIC);

        /*声明一个队列,并绑定死信交换器*/
        String queueName = "dlx_make";

        HashMap<String, Object> arguments = new HashMap<>();
        arguments.put("x-dead-letter-exchange", DlxProcessConsumer.DLX_EXCHANGE_NAME);
        channel.queueDeclare(queueName, false, false, false, arguments);

        channel.queueBind(queueName, DlxProducer.EXCHANGE_NAME, "#");

        System.out.println("waiting for message........");

        /*声明了一个消费者*/
        final Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag,
                                       Envelope envelope,
                                       AMQP.BasicProperties properties,
                                       byte[] body) throws IOException {
                String message = new String(body, "UTF-8");

                if (envelope.getRoutingKey().equals("error")) {
                    System.out.println("Received["
                            + envelope.getRoutingKey()
                            + "]" + message);
                    channel.basicAck(envelope.getDeliveryTag(), false);
                } else {
                    System.out.println("Will reject["
                            + envelope.getRoutingKey()
                            + "]" + message);
                    channel.basicReject(envelope.getDeliveryTag(), false);
                }
            }
        };
        /*消费者正式开始在指定队列上消费消息*/
        channel.basicConsume(queueName, false, consumer);
    }
}
  • 消费者:投递死信时绑定路由键
package dlx;

import com.rabbitmq.client.*;

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

/**
 * @Author: Rab
 * @Date: 2020-04-16 10:22
 * @Description:
 */
public class WillMakeWarnDlxConsumer {

    public static void main(String[] args) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");

        // 打开连接和创建频道,与发送端一样
        Connection connection = factory.newConnection();
        final Channel channel = connection.createChannel();

        channel.exchangeDeclare(DlxProducer.EXCHANGE_NAME, BuiltinExchangeType.TOPIC);

        String queueName = "dlx_warn_make";

        HashMap<String, Object> arguments = new HashMap<>();
        arguments.put("x-dead-letter-exchange", DlxProcessWarnConsumer.DLX_EXCHANGE_NAME);

        arguments.put("x-dead-letter-routing-key", DlxProcessWarnConsumer.DLX_ROUTE_KEY);

        channel.queueDeclare(queueName, false, false, false, arguments);

        channel.queueBind(queueName, DlxProducer.EXCHANGE_NAME, "#");

        System.out.println("waiting for message........");

        /*声明了一个消费者*/
        final Consumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag,
                                       Envelope envelope,
                                       AMQP.BasicProperties properties,
                                       byte[] body) throws IOException {
                String message = new String(body, "UTF-8");

                if(envelope.getRoutingKey().equals("error")){
                    System.out.println("Received["
                            +envelope.getRoutingKey()
                            +"]"+message);
                    channel.basicAck(envelope.getDeliveryTag(),
                            false);
                }else{
                    System.out.println("Will reject["
                            +envelope.getRoutingKey()
                            +"]"+message);
                    channel.basicReject(envelope.getDeliveryTag(),
                            false);
                }

            }
        };
        /*消费者正式开始在指定队列上消费消息*/
        channel.basicConsume(queueName,false,consumer);

    }
}
  • 死信交换器消费者
package dlx;

import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 *类说明:普通的消费者,消费死信队列dlx_warn_accept,路由键为dlx_other
 */
public class DlxProcessOtherConsumer {


    public static void main(String[] argv)
            throws IOException, TimeoutException {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");

        // 打开连接和创建频道,与发送端一样
        Connection connection = factory.newConnection();
        final Channel channel = connection.createChannel();
        channel.exchangeDeclare(DlxProcessWarnConsumer.DLX_EXCHANGE_NAME,
                BuiltinExchangeType.TOPIC);

        /*声明一个队列*/
        String queueName = "dlx_other";
        channel.queueDeclare(queueName,false,false,
                false,null);

        /*绑定,将队列和交换器通过路由键进行绑定*/
        channel.queueBind(queueName,
                DlxProcessWarnConsumer.DLX_EXCHANGE_NAME,
                "dlx_other");

        System.out.println("waiting for message........");

        /*声明了一个死信消费者*/
        final Consumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag,
                                       Envelope envelope,
                                       AMQP.BasicProperties properties,
                                       byte[] body) throws IOException {
                String message = new String(body, "UTF-8");
                System.out.println("Received dead letter["
                        +envelope.getRoutingKey()
                        +"]"+message);
            }
        };
        /*消费者正式开始在指定队列上消费消息*/
        channel.basicConsume(queueName,true,consumer);
    }
}
  • 消费绑定路由键的死信交换器
package dlx;

import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 *类说明:普通的消费者,消费死信队列dlx_warn_accept,路由键为dlx_warn
 */
public class DlxProcessWarnConsumer {

    public final static String DLX_EXCHANGE_NAME = "dlx_warn_accept";
    public final static String DLX_ROUTE_KEY = "dlx_warn";

    public static void main(String[] argv)
            throws IOException, TimeoutException {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");

        // 打开连接和创建频道,与发送端一样
        Connection connection = factory.newConnection();
        final Channel channel = connection.createChannel();
        channel.exchangeDeclare(DLX_EXCHANGE_NAME,
                BuiltinExchangeType.TOPIC);

        /*声明一个队列*/
        String queueName = "dlx_warn_accept";
        channel.queueDeclare(queueName,false,false,
                false,null);

        /*绑定,将队列和交换器通过路由键进行绑定*/
        channel.queueBind(queueName,
                DLX_EXCHANGE_NAME,DLX_ROUTE_KEY);

        System.out.println("waiting for message........");

        /*声明了一个死信消费者*/
        final Consumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag,
                                       Envelope envelope,
                                       AMQP.BasicProperties properties,
                                       byte[] body) throws IOException {
                String message = new String(body, "UTF-8");
                System.out.println("Received dead letter["
                        +envelope.getRoutingKey()
                        +"]"+message);
            }
        };
        /*消费者正式开始在指定队列上消费消息*/
        channel.basicConsume(queueName,true,consumer);
    }
}

6.3 死信交换器和备用交换器的区别

1、备用交换器是主交换器无法路由消息,那么消息将被路由到这个新的备用交换器,而死信交换器则是接收过期或者被拒绝的消息。

2、备用交换器是在声明主交换器时发生联系,而死信交换器则声明队列时发生联系。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值