MQ分布式服务容器

MQ问题梳理转自陈琰AC

2023-01-12  本文已影响0人  前浪浪奔浪流

一、为什么使用 MQ?

1.1 解耦

1.1.1 解耦

例如电商系统核心是交易服务,交易服务要调用另外三个服务,订单服务、库存服务、仓储服务。


image.png

这三个服务如果有一个服务不可用,交易服务就无法正常运行,所以交易服务是强耦合另外三个服务。

引入MQ之后,交易服务只跟MQ交互,把消息发到MQ里面就行了,无需关心另外三个服务是否可用。这时候交易服务跟另外三个服务就是弱耦合的关系,耦合性被降低了。

哪怕是另外三个服务暂时不可用,也不影响交易服务的运行,只要其他服务运行起来后,把MQ里面的消息消费了就行。(降级接口)


image.png
1.1.2 解耦

假设 A 系统在用户发生某个操作的时候,需要把用户提交的数据同时推送到 B、C 两个系统的时候。这个时候负责 A 系统的哥们想:没事啊,B、C 两个系统给我提供一个 HTTP 接口或者 RPC 接口,我把数据推送过去不就完事了嘛,负责 A 系统的哥们美滋滋。

一切看起来很美好,但是随着业务快速迭代,这个时候系统 D 也想要这个数据。那既然这样,A 系统的开发同学就改咯,在发送数据给 B、C 的同时加上一个 D。但是,越到后面越发现,麻烦来了。整个系统好像不止这个数据要发送给 B、C、D、还有第二、第三个数据要发送给 B、C、D。甚至有时候又加入了 E、F 等系统,他们也要这个数据。并且有时候可能 B 系统突然又不要这个数据了,A 系统改来改去,A 系统的开发哥们头皮发麻。更复杂的场景是,数据通过接口传给其他系统有时候还要考虑重试、超时等一些异常情况。

这个时候,就该我们的 MQ 粉墨登场了,这种情况下使用 MQ 来解耦是再合适不过了,因为负责 A 系统的哥们只需要把消息扔到 MQ 就行了,其他系统按需来订阅消息就好了。就算某个系统不需要这个数据了,也不会需要 A 系统改动代码。

1.2 异步

没有引入MQ的时候,交易服务需要同步调用三个服务,如果调用一个服务需要耗时1秒,那么同步调用三个服务需要耗时3秒。在引入MQ之后,全都改成了异步调用,整个耗时不到1秒,大大提高了接口的性能。

1.3 削峰

如果一秒内同时来了5000笔交易,而订单服务每秒只能处理100笔交易,那么后面的4900笔交易失败。在引入MQ之后,交易服务可以把交易数据先发送到MQ中,而订单服务再慢慢从MQ拉取交易信息处理。从而避免突发流量压垮服务器。

1.3.1 削峰填谷

举个例子,比如我们的订单系统,在下单的时候就会往数据库写数据。但是数据库只能支撑每秒 1000 左右的并发写入,并发量再高就容易宕机。低峰期的时候并发也就 100 多个,但是在高峰期时候,并发量会突然激增到 5000 以上,这个时候数据库肯定死了。

但是使用了 MQ 之后,情况就变了,消息被 MQ 保存起来了,然后系统就可以按照自己的消费能力来消费,比如每秒 1000 个数据,这样慢慢写入数据库,这样就不会打死数据库了。

至于为什么叫做削峰填谷呢?如果没有用 MQ 的情况下,并发量高峰期的时候是有一个“顶峰”的,然后高峰期过后又是一个低并发的“谷”。但是使用了 MQ 之后,限制消费消息的速度为 1000QPS,但是这样一来,高峰期产生的数据势必会被积压在 MQ 中,高峰就被“削”掉了。但是因为消息积压,在高峰期过后的一段时间内,消费消息的速度还是会维持在 1000QPS,直到消费完积压的消息,这就叫做“填谷”。

二、引入MQ之后的问题

2.1 系统可用性降低

本来整个系统有四个服务,我们只需要保证这四个服务可用就行了。现在又多引入了一个MQ,我们还要保证MQ的可用,所以整个系统的可用性降低。

2.2 系统复杂性提高

本来交易服务是同步调用另外三个服务,如果另外三个服务不可用,交易服务能立即感知到。引入MQ之后,整个系统的稳定性就要靠MQ保证了。

这时候,我们就要考虑到发到MQ里面的消息怎么避免丢失的问题?顺序性消费的问题,就是同一笔交易的下单消息应该比撤单消息先处理。重复性消费的问题,就是同一笔下单交易的消息可能被多次处理。

当然,每种问题都有具体的解决方案,避免消息丢失可以使用MQ集群,顺序性消费可以把消息发到同一个分区,重复性消费可以在消费端做幂等性处理。

2.3 重复消费问题

2.3.1 问题场景

重复消费问题可以说是 MQ 中普遍存在的问题, 不管你用哪种 MQ 都无法避免。有哪些场景会出现重复的消息呢?

如果重复消息不做正确的处理,会对业务造成很大的影响,产生重复数据或者导致数据异常,比如会员系统多开通了一个月的会员等。

2.3.2 解决方案

不管是由于生产者产生的重复消息,还是由于消费者导致的重复消息,我们都可以在消费者中解决这个问题。

这就要求消费者在做业务处理时,要做幂等设计。在这里我推荐增加一张消费消息表,来解决 MQ的这类问题。

消费消息表中,使用 messageId 做唯一索引。在处理业务逻辑之前,先根据 messageId 查询一下该消息有没有处理过。如果已经处理过了则直接返回成功,如果没有处理过,则继续做业务处理。


image.png

2.4 数据一致性问题(异步分布式事务问题)

2.4.1 问题场景

当服务间是同步调用的时候,我们还可以使用本地事务来控制数据的一致性。但是引入MQ之后,服务间的调用都是异步了,就没办法使用本地事务,也就无法做到数据的强一致性了。

例如,调用订单服务下单成功了,但是调用库存服务扣减库存失败,就会导致超卖,是严重的线上事故。

这时候怎么办?
方案一:需要事务强一致的,不用消息异步,如下单、减库存要放在一个事务里控制,加积分这种非核心的业务才用消息异步处理。

image.png

方案二:可以使用MQ事务消息(只有RocketMQ才有事务消息功能,RocketMQ收发事务消息)。

事务状态有以下三种:

步骤一: A 服务向消息中间件发布消息

如果消息中间件在最后的过程中,长时间没有收到服务A 发送的 Commit 或 Rollback 指令,这个时候就需要依靠 超时询问机制。

步骤二: 消息中间件向B服务投递消息

消息中间件收到A服务的提交 Commit指令后便会将该消息投递给B服务,然后将自己的状态置为阻塞等待状态。B服务收到消息中间件发送的消息后便开始处理任务B,处理完成后便会向消息中间件发出回应。但是在消息中间件阻塞等待的时候同样会出现问题。

正常情况:消息中间件投递完消息后,进入阻塞等待状态,在收到确认应答后便认为事务处理完成,该流程结束。
等待超时情况:在等待确认应答超时之后就会重新进行投递,直到B服务器返回消费成功响应为止。而消息重试的次数和时间间隔都可以设置,如果最终还是不能成功进行投递,则需要人工干预。

2.4.2 解决方案

我们都知道数据一致性分为:强一致性、弱一致性、最终一致性。

而 MQ 为了性能考虑使用的是最终一致性,那么必定会出现数据不一致的问题。这类问题大概率是因为消费者读取消息后,业务逻辑处理失败导致的。这时候可以增加重试机制。重试分为同步重试和异步重试。

有些消息量比较小的业务场景,可以采用同步重试。在消费消息时如果处理失败,立刻重试 3-5 次,如果还是失败则写入到记录表中。但如果消息量比较大,则不建议使用这种方式。因为如果出现网络异常,可能会导致大量的消息不断重试,影响消息读取速度造成消息堆积。


image.png

消息量比较大的业务场景,建议采用异步重试。在消费者处理失败之后,立刻写入重试表,有个 job(如采用xxljob) 专门定时重试。

还有一种做法:如果消费失败,自己给同一个 topic 发一条消息。在后面的某个时间点,自己又会消费到那条消息,起到了重试的效果。如果对消息顺序要求不高的场景,可以使用这种方式。

2.5 消息丢失问题

2.5.1 问题场景

同样消息丢失问题,也是 MQ 中普遍存在的问题,不管你用哪种 MQ 都无法避免。有哪些场景会出现消息丢失问题呢?

导致消息丢失问题的原因挺多的, 生产者、 MQ 服务器、 消费者都有可能产生问题。我在这里就不一一列举了。最终的结果会导致消费者无法正确的处理消息,而导致数据不一致的情况。

2.5.2 解决方案

不管你是否承认,有时候消息真的会丢。即使这种概率非常小,也会对业务有影响。生产者、MQ 服务器、消费者都有可能会导致消息丢失的问题。为了解决这个问题,我们可以增加一张消息发送表。

2.6 消息顺序问题

2.6.1 问题场景

有些业务数据是有状态的,比如订单有下单、支付、完成、退货等状态。 如果订单数据作为消息体,就会涉及顺序问题了。

例如消费者收到同一个订单的两条消息。第一条消息的状态是下单,第二条消息的状态是支付,这是没问题的。但如果第一条消息的状态是支付,第二条消息的状态是下单就会有问题了。没有下单就先支付了?


image.png

消息顺序问题是一个非常棘手的问题,比如:

2.6.2 解决方案

消息顺序问题是一种常见问题。我们以 Kafka 消费订单消息为例,订单有下单、 支付、 完成、 退货等状态。这些状态是有先后顺序的,如果顺序错了会导致业务异常。

解决这类问题之前,我们需要先确认:消费者是否真的需要知道中间状态,只知道最终状态行不行?


image.png

其实很多时候,我真的需要知道的是最终状态。这时可以把流程优化一下:


image.png
这种方式可以解决大部分的消息顺序问题。

但如果真的有需要保证消息顺序的需求,那么可以将订单号路由到不同的 partition。同一个订单号的消息,每次到发到同一个partition。
2.7 消息堆积
2.7.1 问题场景
如果消息消费者读取消息的速度,能够跟上消息生产者的节奏,那么整套 MQ 机制就能发挥最大作用。

但是很多时候,由于某些批处理或者其他原因,导致消费速度小于生产速度。这样会直接导致消息堆积问题,从而影响业务功能。

这里以下单 开通会员为例,如果消息出现堆积会导致用户下单之后,很久之后才能变成会员。这种情况肯定会引起大量用户投诉。

2.7.2 解决方案
那么消息堆积问题该如何解决呢?这个要看消息是否需要保证顺序。如果不需要保证顺序,可以读取消息之后用多线程处理业务逻辑。


image.png

这样就能增加业务逻辑处理速度,解决消息堆积问题。但是线程池的核心线程数和最大线程数需要合理配置,不然可能会浪费系统资源。

如果需要保证顺序,可以读取消息之后将消息按照一定的规则分发到多个队列中,然后在队列中用单线程处理。

image.png
资料来源:
面试官竟然问我为啥要用MQ,幸亏我看了参考答案

面霸篇:MQ 的 5 大问题详解

消息中间件学习总结(18)——MQ常见面试题总结

作者:陈琰AC
链接:https://www.jianshu.com/p/439f74dd62a9

上一篇下一篇

猜你喜欢

热点阅读