在实际落地生产的时候,如果没有高并发场景的,完全可以自己基于某个 MQ 中间件开发一个可靠消息服务。
如果有高并发场景的,可以用 RocketMQ 的分布式事务支持上面的那套流程都可以实现。
今天给大家分享的一个核心主题,就是这套方案如何保证 99.99% 的高可用。
大家应该发现了这套方案里保障高可用性最大的一个依赖点,就是 MQ 的高可用性。
任何一种 MQ 中间件都有一整套的高可用保障机制,无论是 RabbitMQ、RocketMQ 还是 Kafka。
所以在大公司里使用可靠消息最终一致性方案的时候,我们通常对可用性的保障都是依赖于公司基础架构团队对 MQ 的高可用保障。
也就是说,大家应该相信兄弟团队,99.99% 可以保障 MQ 的高可用,绝对不会因为 MQ 集群整体宕机,而导致公司业务系统的分布式事务全部无法运行。
但是现实是很残酷的,很多中小型的公司,甚至是一些中大型公司,或多或少都遇到过 MQ 集群整体故障的场景。
MQ 一旦完全不可用,就会导致业务系统的各个服务之间无法通过 MQ 来投递消息,导致业务流程中断。
比如最近就有一个朋友的公司,也是做电商业务的,就遇到了 MQ 中间件在自己公司机器上部署的集群整体故障不可用,导致依赖 MQ 的分布式事务全部无法跑通,业务流程大量中断的情况。
这种情况,就需要针对这套分布式事务方案实现一套高可用保障机制。
基于 KV 存储的队列支持的高可用降级方案
大家来看看下面这张图,这是我曾经指导过朋友的一个公司针对可靠消息最终一致性方案设计的一套高可用保障降级机制。
这套机制不算太复杂,可以非常简单有效的保证那位朋友公司的高可用保障场景,一旦 MQ 中间件出现故障,立马自动降级为备用方案。
①自行封装 MQ 客户端组件与故障感知
首先第一点,你要做到自动感知 MQ 的故障接着自动完成降级,那么必须动手对 MQ 客户端进行封装,发布到公司 Nexus 私服上去。
然后公司需要支持 MQ 降级的业务服务都使用这个自己封装的组件来发送消息到 MQ,以及从 MQ 消费消息。
在你自己封装的 MQ 客户端组件里,你可以根据写入 MQ 的情况来判断 MQ 是否故障。
比如说,如果连续 10 次重新尝试投递消息到 MQ 都发现异常报错,网络无法联通等问题,说明 MQ 故障,此时就可以自动感知以及自动触发降级开关。
②基于 KV 存储中队列的降级方案
如果 MQ 挂掉之后,要是希望继续投递消息,那么就必须得找一个 MQ 的替代品。
举个例子,比如我那位朋友的公司是没有高并发场景的,消息的量很少,只不过可用性要求高。此时就可以使用类似 Redis 的 KV 存储中的队列来进行替代。
由于 Redis 本身就支持队列的功能,还有类似队列的各种数据结构,所以你可以将消息写入 KV 存储格式的队列数据结构中去。
PS:关于 Redis 的数据存储格式、支持的数据结构等基础知识,请大家自行查阅了,网上一大堆。
但是,这里有几个大坑,一定要注意一下:
第一个,任何 KV 存储的集合类数据结构,建议不要往里面写入数据量过大,否则会导致大 Value 的情况发生,引发严重的后果。
因此绝不能在 Redis 里搞一个 Key,就拼命往这个数据结构中一直写入消息,这是肯定不行的。
第二个,绝对不能往少数 Key 对应的数据结构中持续写入数据,那样会导致热 Key 的产生,也就是某几个 Key 特别热。
大家要知道,一般 KV 集群,都是根据 Key 来 Hash 分配到各个机器上的,你要是老写少数几个 Key,会导致 KV 集群中的某台机器访问过高,负载过大。
基于以上考虑,下面是笔者当时设计的方案:
- 根据它们每天的消息量,在 KV 存储中固定划分上百个队列,有上百个 Key 对应。
- 这样保证每个 Key 对应的数据结构中不会写入过多的消息,而且不会频繁的写少数几个 Key。
- 一旦发生了 MQ 故障,可靠消息服务可以对每个消息通过 Hash 算法,均匀的写入固定好的上百个 Key 对应的 KV 存储的队列中。
同时需要通过 ZK 触发一个降级开关,整个系统在 MQ 这块的读和写全部立马降级。
③下游服务消费 MQ 的降级感知
下游服务消费 MQ 也是通过自行封装的组件来做的,此时那个组件如果从 ZK 感知到降级开关打开了,首先会判断自己是否还能继续从 MQ 消费到数据?
如果不能了,就开启多个线程,并发的从 KV 存储的各个预设好的上百个队列中不断的获取数据。
每次获取到一条数据,就交给下游服务的业务逻辑来执行。通过这套机制,就实现了 MQ 故障时候的自动故障感知,以及自动降级。如果系统的负载和并发不是很高的话,用这套方案大致是没问题的。
因为在生产落地的过程中,包括大量的容灾演练以及生产实际故障发生时的表现来看,都是可以有效的保证 MQ 故障时,业务流程继续自动运行的。
④故障的自动恢复
如果降级开关打开之后,自行封装的组件需要开启一个线程,每隔一段时间尝试给 MQ 投递一个消息看看是否恢复了。
如果 MQ 已经恢复可以正常投递消息了,此时就可以通过 ZK 关闭降级开关,然后可靠消息服务继续投递消息到 MQ,下游服务在确认 KV 存储的各个队列中已经没有数据之后,就可以重新切换为从 MQ 消费消息。
⑤更多的业务细节
上面说的那套方案是一套通用的降级方案,但是具体的落地是要结合各个公司不同的业务细节来决定的,很多细节多没法在文章里体现。
比如说你们要不要保证消息的顺序性?是不是涉及到需要根据业务动态,生成大量的 Key?等等。
此外,这套方案实现起来还是有一定的成本的,所以建议大家尽可能还是 Push 公司的基础架构团队,保证 MQ 的 99.99% 可用性,不要宕机。
其次就是根据大家公司实际对高可用的需求来决定,如果感觉 MQ 偶尔宕机也没事,可以容忍的话,那么也不用实现这种降级方案。
但是如果公司领导认为 MQ 中间件宕机后,一定要保证业务系统流程继续运行,那么还是要考虑一些高可用的降级方案,比如本文提到的这种。
最后再说一句,真要是一些公司涉及到每秒几万几十万的高并发请求,那么对 MQ 的降级方案会设计的更加的复杂,那就远远不是这么简单可以做到的。