[TOC] #### 1. 回顾一下 --- 上一讲我们聊到,用户下单的时候,发短信、发邮件、通知物流这些操作,其实不需要让用户等着 但如果你仔细想想,就会发现一个问题:「让用户等」只是表面问题,同步调用真正可怕的地方,远不止慢这么简单 上一讲的下单代码: ```php public function createOrder($userId, $goodsId) { $this->reduceStock($goodsId); // 扣库存 $order = $this->insertOrder($userId, $goodsId); // 创建订单 $this->sendSms($userId, $order->id); // 发短信 $this->sendEmail($userId, $order->id); // 发邮件 $this->notifyLogistics($order->id); // 通知物流 return $order; } ``` 这 5 步是串行执行的,一步做完才做下一步,也就是同步调用执行。 看起来很规矩,对吧 ?但你有没有想过:如果第 3 步发短信的时候,接口挂了,会怎样 ? #### 2. 问题一:一个环节出错,全部跟着崩 --- 短信服务商的接口挂了,`sendSms()` 直接报错。后面的代码根本不会执行。 发邮件?没机会了。通知物流?也没机会了。更要命的是,如果代码没做好异常处理,可能连订单都创建失败了。 用户点了一下「下单」,页面报了个 500 错误。他不知道订单到底有没有创建成功,只能再点一次。 这就引出了一个更严重的问题:短信服务挂了,凭什么影响用户下单? 发短信和创建订单有什么关系?没有关系。 但在同步调用的代码里,它们被绑在了一条线上。一个断了,全部断。这就是 `耦合`。 #### 3. 问题二:慢的会拖死快的 --- 假设短信接口平时响应 500ms,偶尔抖动一下变成 5 秒。 5 秒听起来不长,但别忘了,一个 `PHP-FPM` 进程同一时间只能处理一个请求。 如果短信接口要 5 秒,这个进程就被占住了 5 秒。 服务器一共 100 个进程,100 个用户同时下单,全部卡在发短信这一步。这时候第 101 个用户来了,他连页面都打不开。 因为所有进程都被占满了。短信服务慢,结果连正常的商品浏览都访问不了。 这就像餐厅只有一个服务员,他在等厨房做一道菜,结果其他客人进来了也没人接待。 不是厨房的问题,是整个系统被一个慢环节拖垮了。 #### 4. 问题三:越忙越慢,越慢越忙 --- 秒杀场景下,这个问题会被放大到极致。瞬间几千个请求涌入,每个请求都要调短信接口。 短信服务商那边也扛不住了,响应时间从 500ms 飙到 3 秒,再到 10 秒,最后直接超时。 超时了怎么办?重试。越超时越重试,越重试越拥堵。短信服务商那边的请求更多了,更扛不住了。 这就形成了一个恶性循环:越忙越慢,越慢越忙,最后整个系统雪崩。 #### 5. 问题四:扩展困难 --- 有一天产品经理说:「下单之后再加一个积分系统,给用户加积分」。 你得改代码,加一行调用。 过两天又说:「再加一个优惠券核销」。再改代码,再加一行。 ```php $this->sendSms($userId, $order->id); $this->sendEmail($userId, $order->id); $this->notifyLogistics($order->id); $this->addPoints($userId, $order->id); // 新加的 $this->useCoupon($userId, $order->id); // 又新加的 $this->notifyRecommend($userId, $order->id); // 又又新加的 ``` 每加一个功能,下单流程就多一步,响应时间就更长,而且这些新加的系统,随便哪个挂了,都可能影响下单。 下单系统变成了一个「万能入口」,什么都往里面塞,改的人越多,风险越大,谁都不敢轻易动它。 #### 6. 本讲小结 --- 同步调用的问题,总结下来就四点: + 强耦合:一个服务挂了,其它服务跟着遭殃 + 资源浪费:快的等慢的,进程被白白占用 + 雪崩风险:越忙越慢,恶性循环 + 扩展困难:每加一个功能,系统就更脆弱一点 那怎么解决这些问题呢 ? 核心思路就三个字:拆开来。把不需要同步执行的任务,从主流程里拆出去。让它们各走各的路,互不影响。 下一讲,我们就来聊聊:什么是异步、解耦、削峰。