[TOC] #### 1. 前言 --- 用户注册成功了,你需要给他发一封欢迎邮件,再发一条短信验证码 听起来很简单,对吧 ? 但有没有想过:这一步应该放在哪里 ? #### 2. 业务为什么难做 --- 用户注册的流程大概是这样: 1. 接收用户提交的数据 2. 校验参数 3. 写入数据库 4. 发送欢迎邮件 5. 发送短信验证码 6. 返回注册成功 问题出在第 4 步和第 5 步 发邮件要调邮件服务商的接口,发短信要调短信服务商的接口 这两个接口的响应时间,我们控制不了,快的时候 500ms,慢的时候 3 秒,挂了的时候直接超时 如果放在主流程里同步执行,用户注册就要等这些都做完才能看到结果 用户注册一个账号要等 3 秒,换你你受得了吗 ? 更要命的是,如果短信接口挂了,用户连注册都注册不了 发短信和注册有什么关系?没关系。但它拖垮了注册流程 #### 3. 不用 RabbitMQ 会怎样 --- 你可能想:那我用异步啊,起一个后台进程处理 最简单的做法:往数据库里插一条待发送记录,然后用定时任务扫描 ```php // 注册成功后 DB::table('pending_messages')->insert([ 'type' => 'sms', 'data' => json_encode(['phone' => $phone, 'code' => $code]), 'status' => 'pending', 'created_at' => now(), ]); ``` 定时任务每分钟扫一次表,把 pending 的消息发出去。 能用,但有几个问题: + 延迟:最多延迟 1 分钟,验证码用户可能等不及 + 数据库压力:每分钟查一次表,量大了扛不住 + 重试麻烦:发送失败了要自己写重试逻辑 + 扩展困难:以后加邮件、加推送,代码越写越乱 #### 4. 用 RabbitMQ 怎么解决 --- 用户注册成功后,把发短信和发邮件的任务丢进队列: + 用户只等校验 + 写库的时间,大约 50ms,发短信和发邮件,后台慢慢处理 ```php public function register($data) { // 1. 校验参数 $this->validate($data); // 2. 写入数据库 $user = $this->createUser($data); // 3. 丢任务到队列 $smsMessage = new AMQPMessage(json_encode([ 'phone' => $user->phone, 'code' => $this->generateCode(), ]), ['delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT]); $emailMessage = new AMQPMessage(json_encode([ 'email' => $user->email, 'username' => $user->username, ]), ['delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT]); $channel->basic_publish($smsMessage, '', 'sms_queue'); $channel->basic_publish($emailMessage, '', 'email_queue'); // 4. 直接返回 return ['message' => '注册成功']; } ``` #### 5. 消费者处理 --- 发送失败了,nack 回去,消息重新入队,等会儿再试,不需要自己写重试逻辑,RabbitMQ 帮你处理 ```php // 短信消费者 $callback = function ($msg) { $data = json_decode($msg->body, true); try { sendSms($data['phone'], $data['code']); $msg->ack(); } catch (\Exception $e) { // 发送失败,稍后重试 $msg->nack(true); } }; $channel->basic_consume('sms_queue', '', false, false, false, false, $callback); ``` #### 6. 还能扩展 --- 上面的代码里,注册系统分别往 `sms_queue` 和 `email_queue` 发消息。 如果用 Fanout Exchange,可以做得更干净: + 注册系统只发送一条「用户注册成功」的消息,不用关心谁要处理 + 邮件系统、短信系统、微信通知系统、推送系统各自订阅,各取各的 + 以后新增通知方式,不需要修改注册代码,新系统自己去订阅就行 这才是真正的解耦 #### 7. 本文小结 --- 这个案例用到的核心能力: | 能力 | 体现 | | ------------ | ------------ | | 异步 | 用户不用等发短信发邮件 | | 解耦 | 注册系统不关心通知方式 | | 可靠性 | 持久化 + 手动 ACK,消息不丢 | | 重试 | nack 后自动重新入队 | 一个看起来简单的功能,背后涉及到消息队列的多个核心概念 这也是为什么把它放在实战第一章,它足够简单,但能串起前面学的很多知识 下一讲,我们来做订单超时自动取消