RabbitMQ消息应答模式、事务、公平转发

1.消息应答模式(手动、自动)

1.1应答模式

        为了确保消息不会丢失,RabbitMQ支持消息应答。消费者发送一个消息应答,告诉RabbitMQ这个消息已经接收并且处理完毕了。RabbitMQ就可以删除它了。

        如果一个消费者挂掉却没有发送应答,RabbitMQ会理解为这个消息没有处理完全,然后交给另一个消费者去重新处理。这样,你就可以确认即使消费者偶尔挂掉也不会丢失任何消息了。 没有任何消息超时限制;只有当消费者挂掉时,RabbitMQ才会重新投递。即使处理一条消息会花费很长的时间。

        消息应答是默认打开的。我们通过显示的设置autoAsk=true关闭这种机制。现即自动应答开,一旦我们完成任务,消费者会自动发送应答。通知RabbitMQ消息已被处理,可以从内存删除。如果消费者因宕机或链接失败等原因没有发送ACK(不同于ActiveMQ,在RabbitMQ里,消息没有过期的概念),则RabbitMQ会将消息重新发送给其他监听在队列的下一个消费者


.1.2自动应答
// 4.设置应答模式,true的时候为自动应答,false为手动应答,需要处理
 channel.basicConsume(QUEUE_EMAIL, true, defaultConsumer);

生产者

public class Producer {

    private static String EXCHANGE = "sms_email";

    public static void main(String[] args) throws IOException, TimeoutException {
        //1.创建连接
        Connection connection = ConnectionUtils.newConnection();
        // 2.创建通道
        Channel channel = connection.createChannel();
        //3.绑定的交换机 参数1交互机名称 参数2 exchange类型
        channel.exchangeDeclare(EXCHANGE, "fanout");
        String msg = "测试生产消息" + new Date();
        try {
//            channel.txSelect();
            // 4.生产消息
            channel.basicPublish(EXCHANGE, "", null, msg.getBytes());
//            int i = 1 / 0;
            System.out.println("生产者生产消息:" + msg);
//            channel.txCommit();
        } catch (IOException e) {
            e.printStackTrace();
//            channel.txRollback();
        } finally {
            channel.close();
            connection.close();
        }
    }
}

短信消费者

public class SmsConsumer {
    private static String QUEUE_SMS = "sms";

    private static String EXCHANGE = "sms_email";

    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        System.out.println("短信消费者");
        //1.创建连接
        Connection connection = ConnectionUtils.newConnection();
        // 2.创建通道
        final Channel channel = connection.createChannel();
        // 3.声明队列
        /**
         * String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
         * queue:这没什么好说的,队列名
         *
         * durable:是否持久化,那么问题来了,这是什么意思?持久化,指的是队列持久化到数据库中。在之前的博文中也说过,如果RabbitMQ服务挂了怎么办,队列丢失了自然是不希望发生的。持久化设置为true的话,即使服务崩溃也不会丢失队列
         * exclusive:是否排外,what? 这又是什么呢。设置了排外为true的队列只可以在本次的连接中被访问,也就是说在当前连接创建多少个channel访问都没有关系,但是如果是一个新的连接来访问,对不起,不可以,下面是我尝试访问了一个排外的queue报的错。还有一个需要说一下的是,排外的queue在当前连接被断开的时候会自动消失(清除)无论是否设置了持久化
         * autoDelete:这个就很简单了,是否自动删除。也就是说queue会清理自己。但是是在最后一个connection断开的时候
         * 设置队列的其他一些参数
         */
        channel.queueDeclare(QUEUE_SMS, false, false, false, null);
        /**
         * String queue, String exchange, String routingKey
         */
        channel.queueBind(QUEUE_SMS, EXCHANGE, "message");
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msgString = new String(body, "UTF-8");
                System.out.println("短信消费者开始获取消息.........." );
                int i = 1 / 0;
                // 手动应答消息
                channel.basicAck(envelope.getDeliveryTag(), false);
                System.out.println("短信消费者结束获取消息:" + msgString);

            }
        };
        // 4.设置应答模式,true的时候为自动应答,false为手动应答,需要处理
        channel.basicConsume(QUEUE_SMS, false, defaultConsumer);
    }
}

邮件消费者

public class SmsConsumer {
    private static String QUEUE_SMS = "sms";

    private static String EXCHANGE = "sms_email";

    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        System.out.println("短信消费者");
        //1.创建连接
        Connection connection = ConnectionUtils.newConnection();
        // 2.创建通道
        final Channel channel = connection.createChannel();
        // 3.声明队列
        /**
         * String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
         * queue:这没什么好说的,队列名
         *
         * durable:是否持久化,那么问题来了,这是什么意思?持久化,指的是队列持久化到数据库中。在之前的博文中也说过,如果RabbitMQ服务挂了怎么办,队列丢失了自然是不希望发生的。持久化设置为true的话,即使服务崩溃也不会丢失队列
         * exclusive:是否排外,what? 这又是什么呢。设置了排外为true的队列只可以在本次的连接中被访问,也就是说在当前连接创建多少个channel访问都没有关系,但是如果是一个新的连接来访问,对不起,不可以,下面是我尝试访问了一个排外的queue报的错。还有一个需要说一下的是,排外的queue在当前连接被断开的时候会自动消失(清除)无论是否设置了持久化
         * autoDelete:这个就很简单了,是否自动删除。也就是说queue会清理自己。但是是在最后一个connection断开的时候
         * 设置队列的其他一些参数
         */
        channel.queueDeclare(QUEUE_SMS, false, false, false, null);
        /**
         * String queue, String exchange, String routingKey
         */
        channel.queueBind(QUEUE_SMS, EXCHANGE, "message");
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msgString = new String(body, "UTF-8");
                System.out.println("短信消费者开始获取消息.........." );
                int i = 1 / 0;
                // 手动应答消息
                channel.basicAck(envelope.getDeliveryTag(), false);
                System.out.println("短信消费者结束获取消息:" + msgString);

            }
        };
        // 4.设置应答模式,true的时候为自动应答,false为手动应答,需要处理
        channel.basicConsume(QUEUE_SMS, false, defaultConsumer);
    }
}

异常状态
int i = 1 / 0,我们在邮件和短信消费者代码里面加入,在手动或者自动应答之前抛出异常,就会导致消费都没被消费,而如果在应答之后抛出异常,只能用事务来处理,使用的消息队列是sms和email.
在这里插入图片描述
看到消息都是出于等待被消费的状态
正常
注释掉int i = 1/0,
手动应答(队列sms):通过debug可以看到,执行完1,消费还没被确认
在这里插入图片描述
直到2执行完成后,才被确认
在这里插入图片描述
在这里插入图片描述
自动应答(队列email):执行完成后就被确认了
在这里插入图片描述

1.消息事务

事务的实现主要是对信道(Channel)的设置,主要的方法有三个:

channel.txSelect()声明启动事务模式;
channel.txComment()提交事务;
channel.txRollback()回滚事务;

生产者回滚:消费队列中看不到任何消息

   channel.txSelect();
        // 4.生产消息
        channel.basicPublish(EXCHANGE, "", null, msg.getBytes());
        int i = 1 / 0;
        channel.txCommit();
        System.out.println("生产者生产消息:" + msg);
    } catch (IOException e) {
        e.printStackTrace();
        channel.txRollback();
        }

消费者回滚:消息没有被消费,进行了回滚

try {
        channel.txSelect();
        System.out.println("短信消费者开始获取消息.........." );
        // 手动应答消息
        channel.basicAck(envelope.getDeliveryTag(), false);
        int i = 1 / 0;
        channel.txCommit();
        System.out.println("短信消费者结束获取消息:" + msgString);
    } catch (IOException e) {
        channel.txRollback();
    }

在这里插入图片描述

3.公平转发

目前消息转发机制是平均分配,这样就会出现俩个消费者,奇数的任务很耗时,偶数的任何工作量很小,造成的原因就是近当消息到达队列进行转发消息。并不在乎有多少任务消费者并未传递一个应答给RabbitMQ。仅仅盲目转发所有的奇数给一个消费者,偶数给另一个消费者。 为了解决这样的问题,我们可以使用basicQos方法,传递参数为prefetchCount= 1。这样告诉RabbitMQ不要在同一时间给一个消费者超过一条消息。 换句话说,只有在消费者空闲的时候会发送下一条信息。调度分发消息的方式,也就是告诉RabbitMQ每次只给消费者处理一条消息,也就是等待消费者处理完毕并自己对刚刚处理的消息进行确认之后,才发送下一条消息,防止消费者太过于忙碌,也防止它太过去清闲。

**通过 设置channel.basicQos(1);** 我们生产了10条消息, 01消费者,休眠时间比较短,消费了9条消息

在这里插入图片描述

消费者02,休眠时间较长,最后只消费了1条信息
在这里插入图片描述

总结:也就是通过参数设置,我们可以选择让空闲的消费者多去消费消息,而忙碌的消费者,则等空闲再去消费消息

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