RabbitMq的ack用法

仔细查看一下 Consumer 的回调方法:

            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                ......
                consumerChannel1.basicAck(envelope.getDeliveryTag(), false);
            }

当我们需要确认一条消息已经被消费时,我们调用的 basicAck 方法的第一个参数是 Delivery Tag。

Delivery Tag 用来标识信道中投递的消息。RabbitMQ 推送消息给 Consumer 时,会附带一个 Delivery Tag,以便 Consumer 可以在消息确认时告诉 RabbitMQ 到底是哪条消息被确认了。

RabbitMQ 保证在每个信道中,每条消息的 Delivery Tag 从 1 开始递增。

运行下面的例子可以直观的看到这点:

gordon.study.rabbitmq.ack.TestAckBasic.java

<span style="color:#333333"><code><span style="color:#0000ff">public</span> <span style="color:#0000ff">class</span> <span style="color:#a31515">TestAckBasic</span> {
 
    <span style="color:#0000ff">private</span> <span style="color:#0000ff">static</span> <span style="color:#0000ff">final</span> String QUEUE_NAME = <span style="color:#a31515">"hello"</span>;
 
    <span style="color:#0000ff">public</span> <span style="color:#0000ff">static</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">main</span>(String[] argv) <span style="color:#0000ff">throws</span> Exception {
        ConnectionFactory factory = <span style="color:#0000ff">new</span> ConnectionFactory();
        factory.setHost(<span style="color:#a31515">"localhost"</span>);
        Connection connection = factory.newConnection();
        <span style="color:#0000ff">final</span> Channel consumerChannel1 = connection.createChannel();
        consumerChannel1.queueDeclare(QUEUE_NAME, <span style="color:#0000ff">false</span>, <span style="color:#0000ff">false</span>, <span style="color:#0000ff">false</span>, <span style="color:#0000ff">null</span>);
        consumerChannel1.basicQos(3);
        Consumer consumer1 = <span style="color:#0000ff">new</span> DefaultConsumer(consumerChannel1) {
            <span style="color:#2b91af">@Override</span>
            <span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">handleDelivery</span>(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, <span style="color:#0000ff">byte</span>[] body)
                    <span style="color:#0000ff">throws</span> IOException {
                String message = <span style="color:#0000ff">new</span> String(body, <span style="color:#a31515">"UTF-8"</span>);
                System.out.printf(<span style="color:#a31515">"in consumer A (delivery tag is %d): %s\n"</span>, envelope.getDeliveryTag(), message);
                <span style="color:#0000ff">try</span> {
                    TimeUnit.MILLISECONDS.sleep(200);
                } <span style="color:#0000ff">catch</span> (InterruptedException e) {
                }
                consumerChannel1.basicAck(envelope.getDeliveryTag(), <span style="color:#0000ff">false</span>);
            }
        };
        consumerChannel1.basicConsume(QUEUE_NAME, <span style="color:#0000ff">false</span>, consumer1);
 
        <span style="color:#0000ff">final</span> Channel consumerChannel2 = connection.createChannel();
        consumerChannel2.basicQos(3);
        Consumer consumer2 = <span style="color:#0000ff">new</span> DefaultConsumer(consumerChannel2) {
            <span style="color:#2b91af">@Override</span>
            <span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">handleDelivery</span>(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, <span style="color:#0000ff">byte</span>[] body)
                    <span style="color:#0000ff">throws</span> IOException {
                String message = <span style="color:#0000ff">new</span> String(body, <span style="color:#a31515">"UTF-8"</span>);
                System.out.printf(<span style="color:#a31515">"in consumer B (delivery tag is %d): %s\n"</span>, envelope.getDeliveryTag(), message);
                <span style="color:#0000ff">try</span> {
                    TimeUnit.MILLISECONDS.sleep(200);
                } <span style="color:#0000ff">catch</span> (InterruptedException e) {
                }
                consumerChannel2.basicAck(envelope.getDeliveryTag(), <span style="color:#0000ff">false</span>);
            }
        };
        consumerChannel2.basicConsume(QUEUE_NAME, <span style="color:#0000ff">false</span>, consumer2);
 
        Channel senderChannel = connection.createChannel();
        <span style="color:#0000ff">for</span> (<span style="color:#0000ff">int</span> i = 0; i < 10;) {
            String message = <span style="color:#a31515">"NO. "</span> + ++i;
            TimeUnit.MILLISECONDS.sleep(100);
            senderChannel.basicPublish(<span style="color:#a31515">""</span>, QUEUE_NAME, <span style="color:#0000ff">null</span>, message.getBytes(<span style="color:#a31515">"UTF-8"</span>));
        }
        senderChannel.close();
    }
}</code></span>

result:

in consumer A (delivery tag is 1): NO. 1
in consumer B (delivery tag is 1): NO. 2
in consumer A (delivery tag is 2): NO. 3
in consumer B (delivery tag is 2): NO. 4
in consumer A (delivery tag is 3): NO. 5
in consumer B (delivery tag is 3): NO. 6
in consumer A (delivery tag is 4): NO. 7
in consumer B (delivery tag is 4): NO. 8
in consumer A (delivery tag is 5): NO. 9
in consumer B (delivery tag is 5): NO. 10

可见,两个信道的 delivery tag 分别从 1 递增到 5。(如果修改代码,将两个 Consumer 共享同一个信道,则 delivery tag 是从 1 递增到 10,参考 gordon.study.rabbitmq.ack.TestAckInOneChannel.java

basicAck 方法的第二个参数 multiple 取值为 false 时,表示通知 RabbitMQ 当前消息被确认;如果为 true,则额外将比第一个参数指定的 delivery tag 小的消息一并确认。(批量确认针对的是整个信道,参考gordon.study.rabbitmq.ack.TestBatchAckInOneChannel.java。)

对同一消息的重复确认,或者对不存在的消息的确认,会产生 IO 异常,导致信道关闭。

 

B. 忘了确认会怎样

如果我们注释掉22行,让 consumerChannel1 不再确认消息,世界会怎样?

Unacked messages

只要程序还在运行,这3条消息就一直是 Unacked 状态,无法被 RabbitMQ 重新投递。更厉害的是,RabbitMQ 消息消费并没有超时机制,也就是说,程序不重启,消息就永远是 Unacked 状态。处理运维事件时不要忘了这些 Unacked 状态的消息

当程序关闭时(实际只要 Consumer 关闭就行),这3条消息会恢复为 Ready 状态。
 

C. 取消确认

当消费消息出现异常时,我们需要取消确认,这时我们可以使用 Channel 的 basicReject 方法。

    void basicReject(long deliveryTag, boolean requeue) throws IOException;

第一个参数指定 delivery tag,第二个参数说明如何处理这个失败消息。requeue 值为 true 表示该消息重新放回队列头,值为 false 表示放弃这条消息

一般来说,如果是系统无法处理的异常,我们一般是将 requeue 设为 false,例如消息格式错误,再处理多少次也是异常。调用第三方接口超时这类异常 requeue 应该设为 true。

从 basicReject 方法参数可见,取消确认不支持批量操作(类似于 basicAck 的 multiple 参数)。所以,RabbitMQ 增加了 basicNack 方法以提供批量取消能力。参考 https://www.rabbitmq.com/nack.html

PS:Reject 的消息重新推送来时,delivery tag 就是新的值了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值