关于分布式:vivo鲁班RocketMQ平台的消息灰度方案

55次阅读

共计 8161 个字符,预计需要花费 21 分钟才能阅读完成。

一、计划背景

RocketMQ(以下简称 MQ)作为消息中间件在事务管理,异步解耦,削峰填谷,数据同步等利用场景中有着宽泛应用。当业务零碎进行灰度公布时,Dubbo 与 HTTP 的调用能够基于业界通用的灰度形式在咱们的微服务治理与网关平台来实现,但 MQ 已有的灰度计划都不能齐全解决音讯的隔离与切换连接问题,为此,咱们在鲁班 MQ 平台(蕴含根因剖析、资源管理、订阅关系校验、延时优化等等的扩大)减少了 MQ 灰度性能的扩大实现。

二、RocketMQ 技术特点

为什么 MQ 的灰度计划迟迟没有实现呢?咱们先来回顾一下 RocketMQ 的几个核心技术点。

2.1 存储模型的简述

(图 2.1  MQ 的存储模型)

CommitLog:音讯体理论存储的中央,当咱们发送的任一业务音讯的时候,它最终会存储在 commitLog 上。MQ 在 Broker 进行集群部署(这里为也简洁,不波及主从局部)时,同一业务音讯只会落到集群的某一个 Broker 节点上。而这个 Broker 上的 commitLog 就会存储所有 Topic 路由到它的音讯,当音讯数据量达到 1 个 G 后会从新生成一个新的 commitLog。

Topic:音讯主题,示意一类音讯的逻辑汇合。每条音讯只属于一个 Topic,Topic 中蕴含多条音讯,是 MQ 进行音讯发送订阅的根本单位。属于一级音讯类型,偏重于业务逻辑设计。

Tag:音讯标签,二级音讯类型,每一个具体的音讯都能够选择性地附带一个 Tag,用于辨别同一个 Topic 中的音讯类型,例如订单 Topic,能够应用 Tag=tel 来辨别手机订单,应用 Tag=iot 来示意智能设施。在生产者发送音讯时,能够给这个音讯指定一个具体的 Tag,在生产方能够从 Broker 中订阅获取感兴趣的 Tag,而不是全副音讯(注:谨严的拉取过程,并不全是在 Broker 端过滤,也有可能局部在生产方过滤,在这里不开展形容)。

Queue:实际上 Topic 更像是一个逻辑概念供咱们应用,在源码层级看,Topic 以 Queue 的模式散布在多个 Broker 上,一个 topic 往往蕴含多条 Queue(注:全局程序音讯的 Topic 只有一条 Queue,所以能力保障全局的程序性),Queue 与 commitLog 存在映射关系。能够了解为音讯的索引,且只有通过指定 Topic 的具体某个 Queue,能力找到音讯。(注:相熟 kafka 的同学能够类比 partition)。

生产组及其 ID:示意一类 Producer 或 Consumer,这类 Producer 或 Consumer 通常生产或生产同利用域的音讯,且音讯生产与生产的逻辑统一。每个生产组能够定义全局维一的 GroupID 来标识,由它来代表生产组。不同的生产组在生产时相互隔离,不会影响彼此的生产位点计算。

2.2 音讯发送与生产

(图 2.2  音讯发送与拉取模型)

2.2.1 客户端标识

在生产者或消费者集群中,每一个 MQ 客户端的运行实例,在 MQ 的客户端会保障产生惟一的 ClientID。注:同一利用实例中,既充当生产者,也充当消费者时,其 ClientID 实际上是同一个取值。

2.2.2 音讯发送

当向某个 Topic 发送音讯的时候,会首先取得这个 Topic 的元数据,元数据会包含它有多少个 Queue,每个 Queue 属于哪个 Broker。默认的状况下,发送方会抉择一条 Queue 发送以后音讯,算法类型是轮询,也就是下一条音讯会抉择另一条 Queue 进行发送。另外 MQ 也提供了指定某条 Queue 或者自定义抉择 Queue 的办法进行音讯的发送,自定义抉择 Queue 需实现 MessageQueueSelector 接口。

2.2.3 音讯生产

进行音讯的生产时,同一生产组 (GroupID) 的消费者会订阅 Topic,消费者首先取得 Topic 的元数据,也就是会取得这个 Topic 的所有 Queue 信息。而后这些 Queue 按规定调配给各个具体的客户端(ClientID),各个客户端依据调配到的 Queue 计算对应的须要拉取音讯的 offset 并生成 PullRequest,拉取音讯并生产实现后,客户端会生成 ACK 并更新生产进度。

这里的生产进度是该批音讯未生产胜利的最小 offset,如图 2.3 所示,一批音讯中如果 1、5 未生产,其余的音讯已生产,此时更新的 offset 仍是 1,消费者如果宕机重启,会从 1 号开始生产音讯,此时 2、3、4 号音讯将会反复生产。

(图 2.3  生产进度更新示意图)

因而 RocketMQ 只保障了音讯不会失落,无奈保障音讯不会反复生产,音讯的幂等性须要业务本人实现。

另外,生产方能够指定生产某些 Tag 的音讯,在 pullRequest 进行拉取时,会在 Broker 里会依照存储模型的 Queue 索引信息按 hash 值进行疾速过滤,返回到生产方,生产方也会对返回的音讯进行精准的过滤。

2.3 订阅关系一致性

在生产端,同一生产组内(GroupID 雷同,本节所形容的前提都是同一生产组内)的各个利用实例 MQ 客户端须要放弃订阅关系的一致性,所谓订阅关系的一致性就是同一生产组内的所有客户端所订阅的 Topic 和 Tag 必须完全一致,如果组内订阅关系不统一,音讯生产的逻辑就会凌乱,甚至导致音讯失落。

2.3.1 订阅关系的保护

每一个利用实例 MQ 客户端都有独立的 ClientID,咱们简略地解说一下订阅关系的保护:

  • 各个 MQ 生产客户端会带着它的 ClientID 向所有 Broker 发送心跳包,心跳包中含有具体的本客户端订阅的 Topic & Tag 等信息,registerConsumer 办法会依照生产组将客户端分组存储,同一生产组内的 ClientID 信息在同一个 ConsumerGroupInfo 中;
  • Broker 在接管到任何生产客户端发过来的心跳包后,会在 ConsumerManager 类中的 ConcurrentMapconsumerTable 依照生产组名称 GroupID 作为 key 存储不同的生产组信息,同一生产组的订阅信息会在每一次收到心跳包后,都会按以后的订阅 Topic & Tag 信息进行更新保护,也就是相当于只保留最新的心跳包订阅信息(心跳包中的 subVersion 会标记心跳包版本,当重均衡后果产生扭转后,subVersion 会更新,Broker 只会保留最新版本的心跳包中的订阅信息),不论这个心跳包来源于这个生产组的哪个 ClientID。(因为 Tag 是依赖于 Topic 的属性,Topic 和 Tag 订阅关系不统一时 Broker 对应的处理结果也略有不同,具体可见 updateSubscription 办法)。

2.3.2 订阅不统一影响

在这里应用例子的形式来论述订阅关系不统一带来的局部问题。假如同一组内的消费者 clientA 订阅了 TOPIC\_A,clientB 订阅了 TOPIC\_B;TOPIC\_A、TOPIC\_B 均为 4 条 Queue,在进行生产调配时,最终 Queue 的调配后果如下:

(表 2.1  音讯费者的 Queue 调配后果表)

因为 clientB 没有订阅 TOPIC\_A,clientA 也没有订阅 TOPIC\_B,所以 TOPIA\_A 中的 Queue-2、Queue-3,TOPIC\_B 中的 Queue-0、Queue- 1 中的音讯则无奈失常生产。而且在理论过程中,clientA 也没法失常生产 TOPIC_B 的音讯,这时会在客户端看到很多异样,有些 Queue 的音讯得不到生产产生沉积。

此外,在订阅关系中,不同 client 所订阅的 Tag 不一样时,也会产生拉取的音讯与所须要订阅的音讯不匹配的问题。

三、业界 MQ 灰度计划

(图 3.1  灰度调用示意图)

通常,业务灰度只严格地保障 RPC 服务之间的调用,局部音讯灰度流量的散失或谬误是能够容忍的,如图 3 - 1 所示,V\_BFF 产生的灰度音讯会被 V\_TRADE 的失常版本与灰度版本收到并随机生产,导致局部灰度流量没有进入冀望的环境,但整体 RPC 服务的调用还是隔离了灰度与非灰度环境。当业务对音讯生产的逻辑进行了更改,或者不想让灰度的音讯影响线上的数据时,MQ 的灰度就必须要实现。

因为订阅关系的限度,以后的业界实现的 MQ 灰度计划都是失常版本与灰度版本应用不同的 GroupID 来实现。以下的计划均应用了不同的 GroupID。

3.1 影子 Topic 的计划

新建一系列新的 Topic 来解决隔离灰度的音讯。例如对于 TOPIC\_ORDER 会创立 TOPIC\_ORDER_GRAY 来给灰度环境应用。

发送方在发送时,对灰度的音讯写入到影子 Topic 中。生产时,灰度环境只应用灰度的分组订阅灰度的 Topic。

3.2 Tag 的计划

发送方在发送时,对灰度环境产生的音讯的 Tag 加注灰度标识。生产方,每次灰度版本公布时只订阅灰度 Tag 的音讯,失常的版本订阅非灰度的 Tag。

3.3 UserProperty 的计划

发送方在发送时,对灰度环境产生的音讯的 UserProperty 加注灰度标识。生产方的客户端须要进行改写,依据 UserProperty 来过滤,失常的版本会跳过这类音讯,灰度的版本会解决灰度音讯。

3.4 以后的计划缺点

以上三种计划各自的劣势在这里不做比拟,但都存在以下独特的缺点(也有其它的缺点或开发诉求,但不致命),无奈真正实现灰度状态切换回失常状态时音讯不失落解决,导致整个灰度计划都是从入门到放弃的过程:

  • 因为应用不同的生产组,那么灰度版本验证通过后,如何正确地连接回原失常版本的生产组的生产位移,做到高效地不失落信息处理呢?
  • 灰度的音讯如何确保精确地生产结束,做到落在灰度标识的音讯做到高效地不失落信息处理呢?
  • 开启灰度时,灰度音讯的位点从那里开始?状态的细节化如何管控?

四、鲁班 MQ 平台的灰度计划

实质上,MQ 灰度问题的外围就是高效地将灰度与非灰度的音讯隔离开,生产方依照本人的需要来精确获取到对应版本的音讯;当灰度实现后,可能正确地拼接回来音讯的位移,做到不失落解决必要的音讯,也就是状态细节上的治理。为了实现这个目标,本计划别离在以下几点进行了革新。

本计划中波及到的代码为测试代码,次要用于阐明计划,理论代码会更精密解决。

4.1 Queue 的隔离应用

(图 4.1  Queue 的辨别应用)

咱们曾经晓得了 Queue 是 topic 的理论执行单元,咱们的思路就是 借助 Queue 来实现 v1(失常)音讯、v2(灰度)音讯的辨别 ,咱们能够固定首尾两条【可配置】Queue 专门用来发送与接管灰度的音讯,其余的 Queue 用来发送失常的线上音讯。咱们应用 雷同的生产组(也就是和业界的通用计划不一样,咱们会应用雷同的 GroupID),让灰度消费者参加灰度 Queue 的重均衡,非灰度消费者参加非灰度 Queue 的重均衡。

这里咱们解决了音讯的存储隔离问题。

4.2 Broker 订阅关系革新

灰度版本往往须要变更 Topic 或 Tag,因为咱们没有新增独立的灰度生产组,当灰度版本变更 Topic/Tag 时,生产组内订阅关系就会不统一,前文也简略解释了订阅关系一致性的原理,咱们须要在 Broker 做出对应的革新,来兼容灰度与非灰度订阅关系不统一的状况。

同一生产组的订阅信息会在保护在 ConsumerGroupInfo 的 subscriptionTable 中,能够在 ConsumerGroupInfo 中减少 创立一份 graySubscriptionTable 用来存储灰度版本的订阅信息 ,客户端向 Broker 发送的 心跳包会革新成带有本身的灰度标记 grayFlag,依据 灰度标记 grayFlag来抉择订阅关系存储在 subscriptionTable 还是 graySubscriptionTable;在拉取音讯时,同样向 Broker 传来 grayFlag 来抉择从 subscriptionTable 还是 graySubscriptionTable 中获取对应的订阅信息。

这里咱们解决了生产订阅一致性问题。

4.3 Producer 的革新

发送方的革新绝对简洁,只需确定发送的音讯是否为灰度音讯,通过实现 MessageQueueSelector 接口,将灰度音讯投递到指定数量的灰度 Queue 即可。这里咱们把用于灰度的 grayQueueSize 定义到配置核心中去,以后更多是约定应用 Broker 的指定 Queue 号作为灰度应用。

TOPIC\_V\_ORDER 共有 6 条 Queue,如图 4.2 所示,灰度音讯只会发送至首尾的 0 号与 5 号 Queue,非灰度音讯则会抉择其余的 4 条 Queue 发送音讯。

(图 4.2  发送后果)

这里咱们解决了生产者正确投递的问题。

4.4 Consumer 的革新

生产方波及的革新点次要是灰度 Queue 与非灰度 Queue 的重均衡调配策略,与各个客户端灰度标记 grayFlag 的更新与同步。

灰度重均衡策略的外围就是 分类解决灰度和非灰度的 Queue,要将灰度的 Queue 调配至灰度 ClientID,将非灰度的 Queue 调配至非灰度的 ClientID,因而,在重均衡之前,会通过 Namesrv 获取同组内的所有客户端 clientId 对应最新的 grayFlag(也就是状态会记录到 Namesrv)。

当灰度版本须要变更为线上版本时,各客户端会同步 grayFlag 到 Namesrv,同时,为了防止灰度音讯还未生产实现,在更新 grayFlag 之前会先判断灰度 Queue 中是否存在未生产的音讯,在保障灰度音讯生产实现后才会进行 grayFlag 的更新。

消费者需应用 AllocateMessageQueueGray 作为重均衡策略,传入灰度 Queue 的数量,灰度消费者 setGrayFlag 为 true,能够看出只生产了首尾的 0 号与 5 号 Queue 的音讯,非灰度消费者 setGrayFlag 为 false,能够看出只会生产两头的 4 条 Queue 的音讯,在控制台也能够十分清晰的看到 Queue 的调配后果,grayFlag 为 true 的 v2 客户端调配到了首尾的 Queue,grayFlag 为 false 的 v1 客户端则调配到了两头的 4 条 Queue。

(图 4.3  生产与订阅后果)

当灰度版本须要切换至线上版本时,只需调用 updateClientGrayFlag 来更新状态即可,能够看出在调用 updateClientGrayFlag 后,原先 v2 的两个灰度客户端在生产完灰度 Queue 的音讯后,grayFlag 才真正变为 false【状态在 namesrv 保留】,退出到两头的 4 条非灰度 Queue 的重均衡中,原先首尾的 2 条灰度 Queue 则没有消费者订阅。

(图 4.4  grayFlag 更新)

这里咱们解决了状态切换的细节管制解决问题。

4.5 Namesrv 的革新

前文提到过,消费者在重均衡时是须要获取组内所有客户端的灰度标识 grayFlag,因而,咱们须要一个中央来长久化存储这些 grayFlag,这个中央是每个消费者都能够拜访的,咱们抉择将这些信息存储在 Namesrv。

  • Namesrv 绝对比拟轻量,稳定性很好;
  • 消费者自身就会与 Namesrv 建设长连贯,如果该 namesrv 挂掉,消费者会主动连贯下一个 Namesrv,直到有可用连贯为止;
  • Broker 是理论存储音讯的中央,本身运行压力就绝对较大,用来做灰度数据的同步肯定水平上会加大 Broker 的压力。

然而 Namesrv 自身是无状态的节点,节点之间是不会进行信息同步的,灰度数据的一致性须要借助数据库来保障,Namesrv 独特拜访同一套数据库就好了,数据库长久化存储灰度信息,每次更新 v1、v2 的灰度状态时,通过 Namesrv 批改数据库的数据,在每次重均衡之前,再通过 Namesrv 拉取本人生产组内的所有实例的灰度状态即可。

(图 4.5  Namesrv 存储灰度数据示意图)

这里咱们解决了状态存储与同步的问题。

五、灰度场景的校验

测试是校验计划可行性的真谛,上面用简略的 demo 来验证鲁班平台的 MQ 灰度计划。

5.1 灰度版本 Topic & Tag 不变

这种场景在 4.3、4.4 时曾经做了验证,不再赘述。

5.2 灰度版本 Topic 减少

假如 v1、v2 的订阅信息如表 5.1 所示,则 Topic 订阅后果如图 5.1 所示,TOPIC\_V\_ORDER 被 v1、v2 同时订阅,首尾两条 Queue 调配给灰度 v2 的客户端,两头 4 条 Queue 则调配给非灰度 v1 的客户端;TOPIC\_V\_PAYMENT 只被灰度版本 v2 订阅,则只会将首尾两条 Queue 调配给 v2 的客户端,其余四条 Queue 不会被客户端订阅。咱们向 TOPIC\_V\_ORDER 别离发送 4 条非灰度音讯和灰度音讯,向 TOPIC\_V\_PAYMENT 发送 4 条灰度音讯,从图 5.2 中能够看出TOPIC\_V\_ORDER 中的非灰度音讯由 v1 的两个客户端胜利生产,TOPIC\_V\_ORDER 与 TOPIC\_V\_PAYMENT 的灰度音讯则由 v2 的两个客户端胜利生产。

(表 5.1  订阅信息表)

(图 5.1  订阅后果)

(图 5.2  生产后果)

5.3 灰度版本 Topic 缩小

假如 v1、v2 的订阅信息如表 5.2 所示,则 Topic 订阅后果如图 5.3 所示,TOPIC\_V\_ORDER 被 v1、v2 同时订阅,首尾两条 Queue 调配给灰度 v2 的客户端,两头 4 条 Queue 则调配给非灰度 v1 的客户端;TOPIC\_V\_PAYMENT 只被非灰度版本 v1 订阅,则只会将两头的四条 Queue 调配给 v1 的客户端,首尾两条 Queue 不会被客户端订阅。咱们向 TOPIC\_V\_ORDER 别离发送 4 条非灰度音讯和灰度音讯,向 TOPIC\_V\_PAYMENT 发送 4 条非灰度音讯,从图 5.4 中能够看出TOPIC\_V\_ORDER 与 TOPIC\_V\_PAYMENT 的非灰度音讯由 v1 的两个客户端胜利生产,TOPIC\_V\_ORDER 中的灰度音讯则由 v2 的两个客户端胜利生产

(表 5.2  订阅信息表)

(图 5.3  订阅后果)

(图 5.4  生产后果)

5.4 灰度版本 Tag 变动

假如 v1、v2 的订阅信息如表 5.3 所示,则 Topic 订阅后果如图 5.5 所示,TOPIC\_V\_ORDER 被 v1、v2 同时订阅,首尾两条 Queue 调配给灰度 v2 的客户端,两头 4 条 Queue 则调配给非灰度 v1 的客户端,咱们向 TOPIC\_V\_ORDER 别离发送 4 条 Tag=v1 的非灰度音讯和 Tag=v2 的灰度音讯,从图 5.6 中能够看出 Tag 为 v1 的非灰度音讯由 v1 的两个客户端胜利生产,Tag 为 v2 的灰度音讯则由 v2 的两个客户端胜利生产

(表 5.3  订阅信息表)

(图 5.5  订阅后果)

(图 5.6  生产后果)

5.5 灰度版本 Topic & Tag 混合变动

假如 v1、v2 的订阅信息如表 5.4 所示,则 Topic 订阅后果如图 5.7 所示,与 5.2 状况雷同不再赘述。咱们向 TOPIC\_V\_ORDER 别离发送 4 条 Tag=v1 的非灰度音讯和 Tag=v2 的灰度音讯,向 TOPIC\_V\_PAYMENT 发送 4 条灰度音讯,生产后果如图 5.8 所示,能够看出 v2 的两个客户端胜利生产了 TOPIC\_V\_PAYMENT 及 TOPIC\_V\_ORDER 中 Tag=v2 的灰度音讯,而 v1 的两个客户端则只生产了TOPIC\_V\_ORDER 中 Tag=v1 的非灰度音讯

(表 5.4  订阅信息表)

(图 5.7  订阅后果)

(图 5.8  生产后果)

六、结语

理论的 MQ 灰度版本,咱们还对 MQ 的发送与生产方做了对立的封装,业务方只需配置 graySwitch、grayFlag 即可,graySwtich 标记是否须要开启灰度音讯,在 graySwitch 开启的前提下,grayFlag 才会失效,用来标记以后客户端是否为灰度客户端。

在多零碎交互时,业务零碎可通过开关 graySwitch 来管制是否全量生产其余零碎的灰度与非灰度音讯,通过 grayFlag 来管制是独自生产灰度音讯还是非灰度音讯。graySwitch、grayFlag 参数可放在配置核心做到热失效,当须要切换灰度流量时,可开发相应的脚本统一化更改 grayFlag,实现全链路灰度流量的无损切换。

另外,咱们对于切换状态借助 Namesrv 做了充沛细节上的管制,保障在真正执行切换前,未生产完的音讯会被生产结束才真正的执行切换。

在此,也非常感谢阿里开源 RocketMQ 这个消息中间件!

作者:vivo 流程 IT 团队 -Ou Erli、Xiong Huanxin

正文完
 0