消息中间件--RabbitMQ(三) RabbitMQ消息确认机制

在使用RabbitMQ时,我们最关心的问题就是消息是否成功投递以及消息是否被成功消费。RabbitMQ为我们提供了几种方式,一种是RabbitMQ事务,由于事务使用事务的性能较差,所以RabbitMQ团队提供了更好的方案,即使用发送确认模式和消息应答模式

发送确认
在使用发送确认模式时,生产者将信道设置为confirm模式,一旦信道进入confirm模式,所有在该信道上发布的消息都会被指派一个唯一的ID(从1开始),由这个id在生产者与RabbitMQ之间进行消息的确认。
image.png

如何使用:

  1. 设置信道为confirm模式

channel.confirmSelect();

  1. 开启channel监听

void addConfirmListener(ConfirmListener listener);

Produece Demo:

public class DirectProducer {

    public static final String EXCHANGE_NAME = "direct_exchange";

    public static final String ROUTING_KEY = "direct_key";

    public static void main(String[] args) throws Exception{
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");
        factory.setVirtualHost("/");

        // 新建连接
        Connection connection = factory.newConnection();

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

        // 声明交换器
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);

        // 开启发送者确认模式
        channel.confirmSelect();

        channel.addConfirmListener(new ConfirmListener() {
            public void handleAck(long deliveryTag, boolean multiple) throws IOException {
                System.out.println("消息投递成功, deliveryTag:" + deliveryTag + ", multiple:" + multiple);
            }

            public void handleNack(long deliveryTag, boolean multiple) throws IOException {
                System.out.println("消息投递失败, deliveryTag:" + deliveryTag + ", multiple:" + multiple);
            }
        });

        String msg = "hello rabbitmq";
        // 发送消息
        channel.basicPublish(EXCHANGE_NAME, ROUTING_KEY + "a",true, null, msg.getBytes());
        System.out.println("消息投递完成,消息:" + msg + ", 路由键:" + ROUTING_KEY);

        //channel.close();
        //connection.close();
    }
}

发送端结果:
image.png

Consumer Demo:

public class DirectConsumer {

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

        // 创建连接
        Connection connection = connectionFactory.newConnection();

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

        // 声明交换器
        channel.exchangeDeclare(DirectProducer.EXCHANGE_NAME, BuiltinExchangeType.DIRECT);

        String queueName = "direct_queue";

        // 声明队列
        channel.queueDeclare(queueName, false, false, false, null);

        // 将队列绑定到交换器
        channel.queueBind(queueName, DirectProducer.EXCHANGE_NAME, DirectProducer.ROUTING_KEY);

        // 声明消费者
        Consumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body, "UTF-8");
                System.out.println("收到的消息:" + msg + ", 路由键:" + envelope.getRoutingKey());
            }
        };

        // 消费者消费指定队列的消息
        channel.basicConsume(queueName, true, consumer);
    }
}

image.png

不可路由的消息
当消息发送后,交换机发现消息不能路由到任何一个队列时,RabbitMQ会忽略这条消息。RabbitMQ给我们提供了一个mandatory属性,如果在消息发送时设置了mandatory为true,那么当消息无法路由时,会将消息返回给发送者,通知失败
image.png

如何使用:
1 发送消息时设置mandatory为true
void basicPublish(String exchange, String routingKey, boolean mandatory, BasicProperties props, byte[] body)

2 开启channel监听
void addReturnListener(ReturnListener listener);

Produder Demo

public class DirectProducer {

    public static final String EXCHANGE_NAME = "direct_exchange";

    public static final String ROUTING_KEY = "direct_key";

    public static void main(String[] args) throws Exception{
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");
        factory.setVirtualHost("/");

        // 新建连接
        Connection connection = factory.newConnection();

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

        // 声明交换器
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);

        // 开启发送者确认模式
        channel.confirmSelect();

        channel.addConfirmListener(new ConfirmListener() {
            // deliveryTag: 消息在信道的唯一标识  multiple: 是否批量确认
            public void handleAck(long deliveryTag, boolean multiple) throws IOException {
                System.out.println("消息投递成功, deliveryTag:" + deliveryTag + ", multiple:" + multiple);
            }

            public void handleNack(long deliveryTag, boolean multiple) throws IOException {
                System.out.println("消息投递失败, deliveryTag:" + deliveryTag + ", multiple:" + multiple);
            }
        });

        // 失败确认
        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("消息路由失败");
                System.out.println("replyCode:" + replyCode);
                System.out.println("replyText:" + replyText);
                System.out.println("exchange:" + exchange);
                System.out.println("routingKey:" + routingKey);
                System.out.println("message:" + new String(body, "UTF-8"));
            }
        });

        String msg = "hello rabbitmq";
        // 发送消息
        channel.basicPublish(EXCHANGE_NAME, ROUTING_KEY + "a",true, null, msg.getBytes());
        System.out.println("消息投递完成,消息:" + msg + ", 路由键:" + ROUTING_KEY);

        //channel.close();
        //connection.close();
    }
}

生产端结果:
image.png

Consumer Demo

public class DirectConsumer {

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

        // 创建连接
        Connection connection = connectionFactory.newConnection();

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

        // 声明交换器
        channel.exchangeDeclare(DirectProducer.EXCHANGE_NAME, BuiltinExchangeType.DIRECT);

        String queueName = "direct_queue";

        // 声明队列
        channel.queueDeclare(queueName, false, false, false, null);

        // 将队列绑定到交换器
        channel.queueBind(queueName, DirectProducer.EXCHANGE_NAME, DirectProducer.ROUTING_KEY);

        // 声明消费者
        Consumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body, "UTF-8");
                System.out.println("收到的消息:" + msg + ", 路由键:" + envelope.getRoutingKey());
            }
        };

        // 消费者消费指定队列的消息
        channel.basicConsume(queueName, true, consumer);
    }
}

image.png

消费者消息应答
消费者收到的每一条消息都必须进行确认。消息确认后,RabbitMQ才会从队列删除这条消息,RabbitMQ不会为未确认的消息设置超时时间,它判断此消息是否需要重新投递给消费者的唯一依据是消费该消息的消费者连接是否已经断开。消息的确认分为自动确认和手动确认两种方式,默认是使用的自动确认

自动确认:消费者在声明队列时,可以指定autoAck参数,当autoAck=true时,一旦消费者接收到了消息,就视为自动确认了消息。
手动确认:当autoAck=false时,RabbitMQ会等待消费者显式发回ack信号后才从内存(和磁盘,如果是持久化消息的话)中移去消息。

当autoAck=false,但是消费端没有进行ack时,消息不会被删除,若此时消费端连接断开,则服务器端会安排该消息重新进入队列,等待投递给下一个消费者

自动确认Demo这里不再提供,第二节中的所有Demo都是使用的自动应答机制,Demo请参考https://segmentfault.com/a/1190000023639929

手动确认机制Demo
Producer Demo

public class DirectProducer {

    public static final String EXCHANGE_NAME = "direct_exchange";

    public static final String ROUTING_KEY = "direct_key";

    public static void main(String[] args) throws Exception{
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");
        factory.setVirtualHost("/");

        // 新建连接
        Connection connection = factory.newConnection();

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

        // 声明交换器
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);

        // 开启发送者确认模式
        channel.confirmSelect();

        channel.addConfirmListener(new ConfirmListener() {
            // deliveryTag: 消息在信道的唯一标识  multiple: 是否批量确认
            public void handleAck(long deliveryTag, boolean multiple) throws IOException {
                System.out.println("消息投递成功, deliveryTag:" + deliveryTag + ", multiple:" + multiple);
            }

            public void handleNack(long deliveryTag, boolean multiple) throws IOException {
                System.out.println("消息投递失败, deliveryTag:" + deliveryTag + ", multiple:" + multiple);
            }
        });

        // 失败确认
        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("消息路由失败");
                System.out.println("replyCode:" + replyCode);
                System.out.println("replyText:" + replyText);
                System.out.println("exchange:" + exchange);
                System.out.println("routingKey:" + routingKey);
                System.out.println("message:" + new String(body, "UTF-8"));
            }
        });

        String msg = "hello rabbitmq";
        // 发送消息
        channel.basicPublish(EXCHANGE_NAME, ROUTING_KEY,true, null, msg.getBytes());
        System.out.println("消息投递完成,消息:" + msg + ", 路由键:" + ROUTING_KEY);

        //channel.close();
        //connection.close();
    }
}

生产端结果
image.png

Consumer Demo

public class DirectConsumer {

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

        // 创建连接
        Connection connection = connectionFactory.newConnection();

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

        // 声明交换器
        channel.exchangeDeclare(DirectProducer.EXCHANGE_NAME, BuiltinExchangeType.DIRECT);

        String queueName = "direct_queue";

        // 声明队列
        channel.queueDeclare(queueName, false, false, false, null);

        // 将队列绑定到交换器
        channel.queueBind(queueName, DirectProducer.EXCHANGE_NAME, DirectProducer.ROUTING_KEY);

        // 声明消费者
        Consumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body, "UTF-8");
                System.out.println("收到的消息:" + msg + ", 路由键:" + envelope.getRoutingKey());
                // TODO 可以在此处进行消息消费是否成功的判断,消费失败可以做其它处理
                System.out.println("消息消费,deliveryTag:" + envelope.getDeliveryTag());
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        };

        // 消费者消费指定队列的消息
        channel.basicConsume(queueName, false, consumer);
    }
}

消费端结果
image.png

你可能感兴趣的:(消息中间件--RabbitMQ(三) RabbitMQ消息确认机制)