[TOC] #### 1. 回顾一下 --- 上一讲我们知道了 Consumer 要一直运行,有消息就处理,但现实不会那么顺利,消费者会挂 + 可能是代码报错了,可能是服务器重启了,可能是网络断了 问题来了:消费者拿到一条消息,处理到一半突然挂了,这条消息怎么办 ? #### 2. 先看一个场景 --- 假设消费者收到一条「发送短信」的消息 它开始处理:调用短信接口、等响应,就在这时候,消费者进程崩了,短信可能发出去了,也可能没发出去 但不管发没发出去,有一个问题:RabbitMQ 知道这条消息处理完了吗 ? #### 3. 两种确认模式 --- RabbitMQ 有两种消息确认模式:自动确认模式、手动确认模式 自动确认模式: + 消息发给消费者,RabbitMQ 立刻认为「这条消息处理成功了」,直接从队列里删除 + 这时候消费者如果突然挂了(比如:消费者业务逻辑代码报错),消息就没了 手动确认模式: + 消息发给消费者,RabbitMQ 不急着删,等消费者主动告诉它「我处理完了」,它才删除 + 消费者挂了,没来得及确认,RabbitMQ 会把消息重新发给其他消费者,消息不会丢 #### 4. 自动确认的风险 --- 你可能会想:自动确认不是更简单吗?为什么要用手动的? 看一个例子: 消费者收到消息,开始处理,处理到一半,服务器断电了,重启之后,这条消息已经没了,RabbitMQ 以为它处理成功了 但实际业务上,这条消息可能根本没处理完,用户没收到短信,订单没通知物流,但消息已经从队列里消失了 自动确认的代价就是:消费者出问题,消息可能丢失 #### 5. 怎么开启手动确认 --- 代码只需要改一个地方,修改消费者: ```php // 第四个参数(no_ack)设为 false,表示关闭自动确认 $channel->basic_consume('order_queue', '', false, false, false, false, $callback); ``` 然后在处理完消息之后,手动确认: ```php $callback = function ($msg) { $data = json_decode($msg->body, true); // 处理业务逻辑 echo "处理订单:{$data['order_id']}\n"; // 处理完了,告诉 RabbitMQ 可以删了 $msg->ack(); }; ``` 使用手动确认模式就这两步: + 关闭自动确认 + 消费者处理完之后调用 `ack()` #### 6. 消费者挂了会怎样 --- 假设消费者拿到一条消息,处理到一半挂了 因为没有发送 `ack()`,RabbitMQ 等了一会儿没收到确认,它会认为这个消费者出了问题,把消息重新放回队列 另一个消费者启动(或者一直在运行),取到这条消息,重新处理,消息没有丢 #### 7. 还有其他操作吗 --- 除了 `ack()`(确认成功),RabbitMQ 还提供了拒绝消息和重新投递等能力 这些操作可以让你在处理失败时决定:是把消息放回队列重试,还是直接丢弃 具体的用法我们在后面的章节再详细讲,现在只需要记住:重要业务一定要开手动 `ACK` #### 8. 本文小结 --- | 模式 | 行为 | 风险 | | ------------ | ------------ | ------------ | | 自动确认 | 消息发出去就删除 | 消费者挂了,消息会丢 | | 手动确认 | 消费者确认后才删除 | 消费者挂了,消息会重新投递 | 核心要点: + 自动确认模式下,RabbitMQ 认为消息发出去就算成功了 + 手动确认模式下,消费者要主动告诉 RabbitMQ「我处理完了」 + 消费者挂了没确认,消息会自动重新投递 重要业务一定要开手动 ACK。 下一讲,我们来聊另一个问题:RabbitMQ 重启之后,消息还在吗 ?