第一篇:为什么需要消息队列?
为什么需要消息队列?
上一篇我们聊完了 Redis 系列的最后一篇《Redis 为什么不能当数据库?》。
Redis 解决的是一个问题:如何让系统访问数据更快。但是,当系统继续发展,流量继续增加,只靠 Redis 还不够。因为很多时候,系统慢并不是因为数据库慢,也不是因为缓存慢,而是因为:
一次请求,需要同步完成太多事情。
这也是消息队列(Message Queue,MQ)出现的原因。
一个最开始的订单系统
刚开始开发一个电商系统,下单逻辑可能非常简单。
@PostMapping("/order")publicLongcreateOrder(OrderRequestrequest){Orderorder=orderService.create(request);returnorder.getId();}用户提交订单,保存订单。
返回结果没有任何问题。
但是随着业务发展,订单创建之后,需要做的事情越来越多。
比如:
扣减库存
发送短信
发放优惠券
增加积分
更新会员等级
通知物流系统
写入数据分析平台
于是代码慢慢变成:
@PostMapping("/order")publicLongcreateOrder(OrderRequestrequest){Orderorder=orderService.create(request);stockService.reduce(order);couponService.send(order);pointService.add(order);smsService.send(order);logisticsService.notify(order);returnorder.getId();}从业务角度看,这段代码没有错。
订单创建之后,这些事情确实都需要做。
但是从系统设计角度,它开始出现问题。
第一个问题:接口越来越慢
假设每个服务耗时:
创建订单 20ms
扣库存 30ms
优惠券 50ms
积分 20ms
短信 500ms
物流通知 200ms
最终接口耗时:20 + 30 + 50 + 20 + 500 + 200 = 820ms
用户点击一次下单,需要等待接近 1 秒。
但是仔细想一下:
用户真的需要等待短信发送完成吗?
需要等待积分增加完成吗?
需要等待物流系统收到通知吗?
其实不需要。用户真正关心的是:我的订单有没有创建成功。其他事情,可以稍后完成。
第二个问题:服务之间越来越耦合
更麻烦的是,订单服务现在知道太多东西。
它知道:
订单服务
↓
库存服务
↓
短信服务
↓
优惠券服务
↓
积分服务
↓
物流服务
如果以后新增一个需求:
“下单后发送邮件”
怎么办?
继续修改:emailService.send(order);
如果邮件系统异常:
创建订单
成功
↓
发送邮件
失败
整个接口怎么办?返回失败?
但是订单已经创建了,这就出现了一个很经典的问题:
非核心业务失败,影响核心业务。那能不能让订单服务只负责订单?
重新思考一下,订单服务真正需要做什么?
其实只有:
- 创建订单
- 告诉其他系统:
“订单创建成功了”
至于:
谁需要这个消息;
怎么处理;
什么时候处理;
订单服务不应该关心。
于是架构变成:
用户请求
↓
订单服务
↓
发送消息
↓
消息队列
↓
其他服务消费
代码变成:
@PostMapping("/order")publicLongcreateOrder(OrderRequestrequest){Orderorder=orderService.create(request);rocketMQTemplate.send("order-created",order.getId());returnorder.getId();}订单服务只负责发送:“订单创建成功” MQ 到底做了什么?
很多人理解 MQ:MQ 就是帮我存一条消息,这个理解不完整。
真正重要的是:MQ 在系统之间增加了一层缓冲。
以前:
订单服务
↓
短信服务
订单服务必须等待短信服务完成。
现在:
订单服务
↓
Broker
↓
短信服务
订单服务只需要把消息交给 Broker,后面的事情异步完成。这就是消息队列最核心的价值。
那为什么不用 HTTP 调用?既然服务之间可以 HTTP 调用,为什么还需要 MQ?
比如:smsService.send(order);
换成:POST /sms/send 不也可以吗?
区别在于:HTTP 是主动调用。
调用方必须知道:
谁提供服务;
地址是什么;
接口是什么;
对方是否成功。
MQ 是消息通知。
生产者只关心:消息有没有发送出去。
消费者只关心:有没有自己感兴趣的消息。
两者的关系完全不同。
MQ 底层为什么需要 Broker?
这里其实已经涉及 MQ 的核心设计。
很多初学者会想:既然订单服务要通知短信服务。
为什么不直接:
订单服务
↓
短信服务
而要增加:
订单服务
↓
Broker
↓
短信服务
中间这个 Broker 就是 MQ 的核心。
它解决三个问题:
- 消息暂存
如果短信服务挂了:
订单服务
↓
Broker
↓
短信服务(异常)
消息不会消失,等短信服务恢复后继续消费。
- 消费速度不同
订单创建,每秒 10000 次。
短信发送,每秒只能处理 1000 次。
如果直接调用:订单服务也会被拖慢。
有了 MQ:
10000 条消息
↓
Broker
↓
消费者慢慢处理
生产和消费速度被隔离。
- 多个消费者订阅
同一个订单消息:
订单创建事件
↓
Broker
/ |
库存 积分 短信
不同系统消费自己关心的数据,订单服务不需要知道它们存在。
RocketMQ 中消息到底怎么流转?
以 RocketMQ 为例。
一次消息发送,并不是:
Producer
↓
Consumer
而是:
Producer
↓
NameServer
↓
Broker
↓
CommitLog
↓
Consumer
Producer 发送消息,Broker 保存消息。Consumer 从 Broker 拉取消息。
后面的文章,我们会继续拆:
为什么消息需要 Broker?
Broker 为什么选择 CommitLog?
为什么 RocketMQ 写消息这么快?
消息为什么不会丢?
消息为什么会重复消费?
这些才是 MQ 真正有意思的地方。
总结
消息队列出现,不是因为开发者喜欢增加中间件。
而是因为系统发展到一定阶段后,简单的同步调用已经无法满足需求。
当一个接口需要同时通知几十个系统时:
同步调用会让系统越来越慢,服务依赖会越来越复杂。任何一个下游异常,都可能影响核心流程。
MQ 做的事情,本质上就是:把一次强依赖的同步调用,变成一次可靠的消息通知。
它让系统从:你必须马上帮我完成
变成:我告诉你发生了什么,你什么时候处理由你决定
这就是消息队列存在的意义。
上一篇:《Redis 为什么不能当数据库?》
下一篇:《消息为什么能够解耦系统?》