在选择消息中间件的时候我们要先明确,消息队列中间件的作用。其实也就是削峰填谷和异步解耦
削峰填谷
而关于削峰填谷是啥呢?在高并发场景下,系统可能会在短时间内收到大量请求,我们就拿电商平台举例。
例如,电商平台在促销活动时,大量用户同时下单。如果系统直接处理这些请求,可能会导致服务器负载过高,甚至出现系统崩溃的情况。削峰填谷就是一种应对这种情况的策略。它的核心思想是把瞬间的高流量请求通过消息队列缓存起来,让系统按照自身能够承受的处理能力,从消息队列中逐步获取请求进行处理,就像把山峰一样的高流量削平,填补低谷时期的系统空闲资源。
它的工作原理其实就是当大量请求涌入时,这些请求首先被发送到消息队列,而不是直接到后端业务处理系统。消息队列就像一个缓冲区,将请求按照先进先出(FIFO)或者其他设定的规则进行排队。例如,使用 RabbitMQ 作为消息队列,生产者(发送请求的应用程序)将消息(请求)发送到队列中,消费者(处理请求的应用程序)按照自己的节奏从队列中获取消息并处理。这样可以避免后端业务处理系统被大量并发请求瞬间压垮。
我们以骑手送餐为例,如下:
关于创建订单流程。
1.用户在 App 中下单,App 会调用网关相关接口创建订单,网关接收到请求后, 并不是直接调用内部商户订单中心来创建订单接口,而是先发送一条消息到 MQ。
2. 商户接单模块(Consumer)订阅 MQ 中的消息,处理消息的时候调用内部商户 订单中心创建订单接口,创建一条真正的订单数据到数据库。
3. 创建订单后,商户订单中心将再发送一条消息到 MQ 服务器。然后骑手分配模块 (Consumer)订阅消息,调用派单服务相关接口,引导骑手进行外卖配送。
4. 同时,数据同步组件(Canal)将数据库中的数据准实时同步到 Es 服务器。
为什么网关不直接调用外部的创建订单接口,而是将数据先写入到 MQ 中呢? 我们不妨设想一下,商户订单中心支持的最大并发为 1w/tps。如果某一个业务高峰期, 从网关进入的流量突然飙升到 1.5w/tps,而且持续了 10 分钟,商户订单系统会直接 崩溃,造成服务不可用等严重故障!
那该如何解决呢? 有人可能会说,我们可以使用限流机制保护商户订单系统。例如,我们只允许 9000TPS 的流量从网关进入到商户订单中心,直接拒绝多余的流量,让客户端重试。这确实可以 解决问题,但会带来经济损失和糟糕的用户体验。
这个时候我们有一个更加友好的解决方案:引入消息中间件。
引入消息中间件的目的是让它来扛住海量流量,流量先进入到消息队列中,然后消费端 下游系统可以慢慢消费消息中间件中的数据,这样能有效保护下游系统不被瞬时的流量击破。这种方案可能带来的最坏结果就是,消费这些消息会存在延迟。但这些订单都可以成功创建,真正的交易行为已经产生了。接下来要做的就是根据实际情况扩容或者缩 容,尽快将积压的数据处理掉。
不过我们这个时候引入消息中间件,其实潜台词是它们的性能必须满足下面几个基本要求:高吞吐量、低延迟,还要具体消息堆积能力。
我们再看一下订单查询流程:
1. 用户在 App 端发起订单查询,App 会调用网关的订单查询接口,网关再将请求 转发到内部的订单查询服务;
2. 订单查询服务不是在 MySQL 数据库,而是直接查询 Es 中的数据。这里一个设计的亮点是,引入了数据同步组件 Canal,将 MySQL 数据库中的数据实 时同步到了 Es。这样查询订单时只查 Es 就可以了,实现了订单写入与订单查询在异 构数据源的读写分离。
异步解耦
而关于异步解耦的问题也可以通过电商平台用户注册送积分、送优惠券这个场景来理解异步解耦。如果不使用消息中间件,电商平台送积分的实现也许是下图这个样子:
我们简单看一下这个流程。
1. 用户在网站前端注册页面填写相关信息,然后调用账号中心服务,注册账号。
2. 账户中心首先执行用户注册逻辑处理(例如判断用户是否已注册、是否符合注册条 件等),然后写入到数据库。
3. 注册成功后,需要调用积分中心(赠送积分接口)给用户送积分。
4. 送完积分后,再调用优惠券相关接口,为用户赠送优惠券。
5. 成功送完积分、优惠券后,向用户返回“注册成功” 从架构角度看,上面这个实现方法有一个非常严重的问题,那就是可扩展性低。 例如,如果要在春节期间调整活动策略,在发送积分的同时,还需要额外发送新春大礼包,开发人员为了实现这一功能,就不得不修改用户注册流程,并重新部署用户注册模块。 从功能维度来看,这次需求的变更集中在活动相关的内容。用户注册本身的逻辑并未发 生变化,但由于用户注册逻辑与活动模块存在耦合,两个模块必须一起调整和发布,这 就对系统稳定性造成了影响。 另外,调用积分、优惠券两个远程 RPC 请求让用户注册主流程变长,在高并发场景下, 用户注册这一环容易成为系统瓶颈。要解决上面这两个明显的设计缺陷,常用的方案是引入消息中间件,让用户注册主流程 和商家活动异步解耦合。
改造后的时序图如下:
账户中心完成用户注册相关逻辑后,会向 MQ 发送一条消息到 MQ 服务器,然后就 直接给用户返回“注册成功”。赠送优惠券、积分等与活动相关的需求我们可以异步执行,这样,无论后续互动逻辑发生什么变化,账户中心都不需要发布新版本。 引入送积分服务(MQ 消费者应用)和送优惠券服务(MQ 消费者应用)会订阅消息, 并根据消息调用积分中心、优惠券中心的服务。如果后续活动发生变化,例如取消送积分活动但开始赠送新春大礼包,那我们只需停止送积分服务应用,增加送新春大礼包的 消费者应用,就可以真正做到对新增开放,对修改关闭。
消息队列中间件
目前市场上,消息队列可以说是三分天下,如下:
而关于使用中间件的选择,一般都是根据业务方向来选择的。
在使用中间件时一般都会遇到很多问题,一个非常行之有效的方法就是深入研究源码。
这时候,如果中间件的编写语言和团队技术栈不匹配,将会极大地增加深入研究这款中 间件的难度。如果团队对中间件的掌控能力很弱,自然很难保持中间件的稳定运行。 在进行具体的选型时,我们可以结合自己团队的实际情况。
例如如果公司或团队的技术栈以 Golang 为主,建议选择 RabbitMQ,RabbitMQ 在 性能上的缺陷可以通过搭建多套集群加以规避。
如果公司或团队的技术栈以 Java 为主,我建议使用 Kafka 或 RocketMQ。 RocketMQ 和 Kafka 都是性能优秀的中间件,在这两者之间进行选择时可以更多 地关注功能特性。RocketMQ 提供了消息重试、消息过滤、消息轨迹、消息检索 等功能特性,特别是 RocketMQ 的消息检索功能,因此 RocketMQ 很适合核心 业务场景。而 kafka 更加擅长于日志、大数据计算、流式计算等场景。