第一篇:为什么需要消息队列?

为什么需要消息队列?

上一篇我们聊完了 Redis 系列的最后一篇《Redis 为什么不能当数据库?》。

Redis 解决的是一个问题:如何让系统访问数据更快。但是,当系统继续发展,流量继续增加,只靠 Redis 还不够。因为很多时候,系统慢并不是因为数据库慢,也不是因为缓存慢,而是因为:

一次请求,需要同步完成太多事情。

这也是消息队列(Message Queue,MQ)出现的原因。

一个最开始的订单系统

刚开始开发一个电商系统,下单逻辑可能非常简单。

java 复制代码
@PostMapping("/order")
public Long createOrder(OrderRequest request) {

    Order order = orderService.create(request);

    return order.getId();
}

用户提交订单,保存订单。

返回结果没有任何问题。

但是随着业务发展,订单创建之后,需要做的事情越来越多。

比如:

扣减库存

发送短信

发放优惠券

增加积分

更新会员等级

通知物流系统

写入数据分析平台

于是代码慢慢变成:

java 复制代码
@PostMapping("/order")
public Long createOrder(OrderRequest request) {

    Order order = orderService.create(request);

    stockService.reduce(order);

    couponService.send(order);

    pointService.add(order);

    smsService.send(order);

    logisticsService.notify(order);

    return order.getId();
}

从业务角度看,这段代码没有错。

订单创建之后,这些事情确实都需要做。

但是从系统设计角度,它开始出现问题。

第一个问题:接口越来越慢

假设每个服务耗时:

创建订单 20ms

扣库存 30ms

优惠券 50ms

积分 20ms

短信 500ms

物流通知 200ms

最终接口耗时:20 + 30 + 50 + 20 + 500 + 200 = 820ms

用户点击一次下单,需要等待接近 1 秒。

但是仔细想一下:

用户真的需要等待短信发送完成吗?

需要等待积分增加完成吗?

需要等待物流系统收到通知吗?

其实不需要。用户真正关心的是:我的订单有没有创建成功。其他事情,可以稍后完成。

第二个问题:服务之间越来越耦合

更麻烦的是,订单服务现在知道太多东西。

它知道:

订单服务

↓

库存服务

↓

短信服务

↓

优惠券服务

↓

积分服务

↓

物流服务

如果以后新增一个需求:

"下单后发送邮件"

怎么办?

继续修改:emailService.send(order);

如果邮件系统异常:

创建订单

成功

↓

发送邮件

失败

整个接口怎么办?返回失败?

但是订单已经创建了,这就出现了一个很经典的问题:

非核心业务失败,影响核心业务。那能不能让订单服务只负责订单?

重新思考一下,订单服务真正需要做什么?

其实只有:

  1. 创建订单
  2. 告诉其他系统:
    "订单创建成功了"

至于:

谁需要这个消息;

怎么处理;

什么时候处理;

订单服务不应该关心。

于是架构变成:

用户请求

↓

订单服务

↓

发送消息

↓

消息队列

↓

其他服务消费

代码变成:

java 复制代码
@PostMapping("/order")
public Long createOrder(OrderRequest request) {

    Order order = orderService.create(request);

    rocketMQTemplate.send(
        "order-created",
        order.getId()
    );

    return order.getId();
}

订单服务只负责发送:"订单创建成功" MQ 到底做了什么?

很多人理解 MQ:MQ 就是帮我存一条消息,这个理解不完整。

真正重要的是:MQ 在系统之间增加了一层缓冲。

以前:

订单服务

↓

短信服务

订单服务必须等待短信服务完成。

现在:

订单服务

↓

Broker

↓

短信服务

订单服务只需要把消息交给 Broker,后面的事情异步完成。这就是消息队列最核心的价值。

那为什么不用 HTTP 调用?既然服务之间可以 HTTP 调用,为什么还需要 MQ?

比如:smsService.send(order);

换成:POST /sms/send 不也可以吗?

区别在于:HTTP 是主动调用。

调用方必须知道:

谁提供服务;

地址是什么;

接口是什么;

对方是否成功。

MQ 是消息通知。

生产者只关心:消息有没有发送出去。

消费者只关心:有没有自己感兴趣的消息。

两者的关系完全不同。

MQ 底层为什么需要 Broker?

这里其实已经涉及 MQ 的核心设计。

很多初学者会想:既然订单服务要通知短信服务。

为什么不直接:

订单服务

↓

短信服务

而要增加:

订单服务

↓

Broker

↓

短信服务

中间这个 Broker 就是 MQ 的核心。

它解决三个问题:

  1. 消息暂存

如果短信服务挂了:

订单服务

↓

Broker

↓

短信服务(异常)

消息不会消失,等短信服务恢复后继续消费。

  1. 消费速度不同

订单创建,每秒 10000 次。

短信发送,每秒只能处理 1000 次。

如果直接调用:订单服务也会被拖慢。

有了 MQ:

10000 条消息

↓

Broker

↓

消费者慢慢处理

生产和消费速度被隔离。

  1. 多个消费者订阅

同一个订单消息:

订单创建事件

↓

Broker

/ |

库存 积分 短信

不同系统消费自己关心的数据,订单服务不需要知道它们存在。

RocketMQ 中消息到底怎么流转?

以 RocketMQ 为例。

一次消息发送,并不是:

Producer

↓

Consumer

而是:

Producer

↓

NameServer

↓

Broker

↓

CommitLog

↓

Consumer

Producer 发送消息,Broker 保存消息。Consumer 从 Broker 拉取消息。

后面的文章,我们会继续拆:

为什么消息需要 Broker?

Broker 为什么选择 CommitLog?

为什么 RocketMQ 写消息这么快?

消息为什么不会丢?

消息为什么会重复消费?

这些才是 MQ 真正有意思的地方。

总结

消息队列出现,不是因为开发者喜欢增加中间件。

而是因为系统发展到一定阶段后,简单的同步调用已经无法满足需求。

当一个接口需要同时通知几十个系统时:

同步调用会让系统越来越慢,服务依赖会越来越复杂。任何一个下游异常,都可能影响核心流程。

MQ 做的事情,本质上就是:把一次强依赖的同步调用,变成一次可靠的消息通知。

它让系统从:你必须马上帮我完成

变成:我告诉你发生了什么,你什么时候处理由你决定

这就是消息队列存在的意义。

上一篇:《Redis 为什么不能当数据库?》

下一篇:《消息为什么能够解耦系统?》

相关推荐
此时不提桶,更待何时1 天前
06-09-A-Kafka架构与存储原理详解
架构·kafka·linq
醉颜凉2 天前
Kafka 与 RabbitMQ/RocketMQ 选型对比:场景匹配、性能基准与迁移成本
kafka·消息队列·rabbitmq·rocketmq·中间件选型
筑梦之路2 天前
K8S yaml文件部署kafka集群(Kraft模式)——筑梦之路
容器·kafka·kubernetes
筑梦之路2 天前
Kafka KRaft 模式 Kubernetes 部署手册(StatefulSet + apache/kafka 官方镜像)——筑梦之路
kafka·kubernetes·apache
智码看视界3 天前
技术选型指南:Pulsar vs Kafka:消息队列选型终极对比与压测
kafka·消息队列·pulsar·消息中间件·流处理·高吞吐·架构选型
俊哥大数据3 天前
Flink1.20.3 实时消费 Kafka 数据并解析入湖 Paimon1.4.2 全流程实战
分布式·flink·kafka·数据湖·paimon
今年下半年4 天前
记录一次IBMS物联网项目的架构设计及技术栈应用
物联网·websocket·网络协议·tcp/ip·微服务·udp·kafka
Nano叶落10 天前
Kafka 消费积压排查入门:Docker 搭环境亲手制造一次 ‘消息堵死‘,10 分钟看懂 Lag
kafka
吉甫作诵11 天前
Kafka 集群安装与运维实战:消费组排查、Offset 重置与副本重分配
大数据·运维·分布式·kafka·消息队列
imDwAaY11 天前
消息队列四大核心问题:顺序性、幂等性、可靠性与一致性
学习·kafka·rabbitmq