引言
自今年7月份BifroMQ正式开源以来,在社群内获取了一定关注,也有许多用户也表达了对产品背后技术的兴趣。因此BifroMQ团队决定推送技术解读系列文章,深入剖析BifroMQ的各组件及其设计原则,给对该项目感兴趣的同学带来更深入的产品解析。本篇文章作为BifroMQ技术揭秘专题系列开篇文章,将详细介绍BifroMQ如何通过各种机制确保集群的高可用性,包括分布式集群架构、故障检测与自动逐出机制,以及各类负载均衡策略。后续将会有更多文章全方位、多维度详细剖析BifroMQ更多技术内容,敬请关注。
去中心化集群
在BifroMQ架构中,配备MQTT协议工作负载的拆分方案,使每类工作负载均可在独立的子集群中运作。这些功能性子集群构建于去中心化底层集群构建框架(base-cluster)上。BifroMQ集群框架包含两个逻辑层级:Underlay Cluster和Overlay Cluster,此种构建使得架构更加清晰和解耦。有关此设计更多细节,请参见(《BifroMQ技术架构概览》,详见:https://bifromq.io/zh-Hans/blog/bifromq-tech-architecture/)。
▌Underlay Cluster
Underlay Cluster作为BifroMQ集群体系核心,该层成员代表一个运行中的BifroMQ进程,成员之间可直接利用进程绑定的主机网络地址(HostAddress)进行通信。BifroMQ采用Gossip类的Membership协议(SWIM Protocol
,详见:https://ieeexplore.ieee.org/document/1028914)实现集群成员失败检测(FailureDetection),并且对Membership信息的同步机制做进一步优化,使得整体上具有的以下技术特性:
集群构建无需依赖传统的注册中心或命名服务,有效消除单点故障的运维风险,显著提升集群的高可用性。
通过采用SWIM协议,Underlay Cluster可确保节点间探活机制的准确性,保障集群拓扑的最终一致性。
使用CRDT技术实现集群Membership信息在节点间同步,可以同步时效的同时做到极低的带宽占用。
集群构建过程
在去中心化的集群架构中,集群成员地位均平等,不存在专门负责管理集群拓扑信息的管控角色节点。从更高的视角来观察,运行BifroMQ StandardCluster服务进程的任何节点,实际上都可以视为一个拥有“单个节点”的独立集群。因此,集群的构建实质上是各独立集群的融合过程。为了实现这一过程,base-cluster框架提供“join”操作,以便有效合并这些独立集群。
▲独立集群融合
如图所示,集群的join操作可以由任一节点向目标集群的任一节点发起。为简化讨论,我们将作为join操作目标的节点称为“seeds”。在部署过程中,这些seed节点的地址通常会被配置在新加入集群节点的配置文件(seedEndpoints,详见:https://bifromq.io/zh-Hans/docs/configuration/file_configs_manual/)中。集群成功合并后,BifroMQ集群中的每个节点都可以在本地获取到完整的集群Membership信息。
在节点配置中直接指定seed地址的方式,在容器化环境下可能存在一定的局限性。为解决这一问题,base-cluster框架内置DNS解析功能。在容器环境中,可通过将所有节点纳入固定网络Domain(例如External DNS或Kubernetes Service),简化集群部署过程。这样,新加入的节点可以使用由该Domain解析出的任意Remote地址作为seed,以完成join过程。
▲集群join过程
失败检测与自动驱逐
在BifroMQ中,当节点正常退出时,会主动清理自身在集群中的注册身份,并与集群中的其他节点同步此变化。每个节点持续对其他节点进行失败检测(Failure Detection)。一旦检测到异常,会立即将该节点的相关信息从本地的集群Membership中移除,并迅速完成集群间Membership同步,防止不再活跃的节点对集群正常功能产生影响。
▲失败检测与自动驱逐过程
与自动驱逐机制相配合的是BifroMQ自愈机制,可以有效防止由于网络抖动或误判导致健康节点的注册信息被错误移除。每个节点会在观察到集群信息发生变化时,重新检查自身注册信息。如信息缺失,节点将主动进行补充,从而确保集群信息的完整性和最终一致性。
脑裂恢复
对于去中心化的集群服务来说,网络分区(Network Partition)导致的集群脑裂,即单个集群分裂为多个互相隔离的集群,此种情况无法避免。
base-cluster对可能出现的脑裂故障做出一定保障,提高集群部署的高可用性,具体过程如下:当网络发生分区时,分裂被隔离的集群双方均会检测到对方节点的失败,这些失败被移除的节点会加入至本地的"愈合列表",列表中的成员会被定时尝试join,直到超过部署时设定的 MTTR(Mean Time To Repair,详见:https://en.wikipedia.org/wiki/Mean_time_to_repair)。不难发现,脑裂恢复的过程实际上与集群构建过程一致。
▲脑裂恢复
▌Overlay Cluster
Overlay Cluster又称为Agent Cluster,构建于Underlay Cluster之上,利用Underlay Cluster的能力实现Membership管理和成员间通信,主要用于实现由不同逻辑服务单元组成的代理集群。借助Underlay Cluster的高效构建机制,Agent Cluster能够自动实现集群构建,大幅简化部署和运维过程。
在 BifroMQ 中,由Agent Cluster实现的子服务集群,可被分类为无状态集群和有状态集群两类:无状态集群以RPC服务为主;有状态集群通常基于base-kv构建内置分布式KV存储引擎。
RPC服务集群
RPC服务集群成员通常被定义为客户端和服务端两种角色。利用Agent Cluster特性,RPC客户端与服务端无需依赖外部注册中心,即可实现高效服务端发现与多样化客户端请求路由逻辑。
▲RPC服务集群架构
BifroMQ有状态子服务集群与分布式KV存储引擎
在BifroMQ有状态子服务集群中,内置强一致性的分布式KV存储引擎(base-kv)。此引擎具备基于Multi-Raft的sharding功能,为BifroMQ高度可靠性的关键组成部分。集群Membership成员服务信息由Agent Cluster维护,而每个Range分片内副本则通过Raft协议来实现强一致性同步。因此,要确保有状态服务高可靠性,则必须充分利用并符合Raft协议的特性要求。
KVRange Balancer
base-kv使用内置Range Balancer框架实现高效Range副本管理。该框架综合考虑当前集群拓扑结构和实时负载数据,生成用于均衡Range副本集的指令。这些指令包括Leader转移(LeaderTransfer)、成员配置变更(ConfigChange)、Range拆分(Split)和Range合并(Merge)。通过此系列操作,base-kv能够有效地实现集群负载平衡,优化吞吐性能,同时达成高可用性的目标。 值得一提的是,Range Balancer同样采用去中心化的设计理念,在base-kv的每个节点中,Balancer只负责管理本地的Leader Range。此种设计使多个节点上的Balancer能够并行运作,最终达成全局一致的均衡目标。
▲KVRange Balancer
为确保用户可享受即插即用的便利性,BifroMQ已预先内置系列常用的均衡策略实现。然而,对于那些有特定需求的高级用户,BifroMQ还提供定制化均衡策略的能力(Balancer SPI),以便根据特定的应用场景进行优化。以下是内置均衡策略的简要介绍,用户可以根据个性化需求,通过配置来启用这些策略。
ReplicaCntBalancer
ReplicaCntBalancer主要作用为调节和平衡Range的副本数量。一旦启用,此功能将允许Range的副本数根据BifroMQ StandardCluster集群中部署节点的数量灵活调整。这意味着,ReplicaCntBalancer能够根据集群当前规模,自动优化并实现最佳可用性配置,从而确保高效运行。
▲ReplicaCntBalancer
如图所示,工作过程如下:
集群扩容情况:假设最初BifroMQ集群中有3个节点,此时KVRange的副本数也是3,使得集群能够容忍1个节点的故障。当新增2个节点后,ReplicaCntBalancer自动将这些新节点纳入副本配置,并通过数据同步将副本数增加至5。这样,即可容忍2个节点的不可用状态,从而提升容错能力。
集群缩容情况:在另一种场景中,当BifroMQ集群包含5个节点且KVRange副本数同样为5时,若发生两个节点的宕机,依照Raft算法,仍能保持正常运作。然而,如果副本配置不变,再有一个节点宕机将导致整个Raft集群无法运作。此时,ReplicaCntBalancer会调整副本配置,调整VoterSet仅包含剩余的3个可用节点。这样调整使集群在容忍1个节点不可用的情况下依然保持高可用性。
RangeSplitBalancer
在BifroMQ中,单个Range的副本集将通过Raft协议进行管理,其负载能力受WAL复制机制限制,因此存在一定性能上限。特别是当业务工作负载高度集中于单个Range时,这种限制会变得尤为明显。在这种情况下,为提高系统整体性能,将一个Range拆分成多个部分是提升系统并行任务处理能力的有效手段。
RangeSplitBalancer正是base-kv内置并用于实现这一功能的负载策略。它通过分析实际业务负载情况,及时生成Range拆分指令,从而优化系统处理能力并提高性能表现。
▲RangeSplitBalancer
RangeLeaderBalancer
在Raft协议中,Leader节点承担所有写请求和部分读请求的工作负载,因此,当多个RangeLeader副本集中在同一节点时,易产生负载热点,从而影响系统性能。为解决这一问题,base-kv集群在发生多个Range分裂的情况下,可开启RangeLeaderBalancer。
RangeLeaderBalancer专门负责监控和调整Range副本在各节点上的分布,并通过在节点间迁移Range,确保每个base-kv节点上的Leader副本数量均衡,避免过度集中的负载热点。这种均衡提高整体负载处理的效率的同时,还可降低单个节点出现故障时对集群瞬时可用性的影响。
▲RangeLeaderBalancer
RecoveryBalancer
在Non-Byzantine容错的强一致性协议中,正常工作节点数量 n 必须满足 n≥2f+1 的条件,其中 f 代表可容忍的失败节点数。基于这一原则,任何Range副本都必须位于超过半数的正常集群节点中,才能保证正常运作。然而,在实际部署中,当集群中Range的数量较多,且单个base-kv节点可能承载多个不同Range的副本时,多个base-kv节点的同时故障可能导致n < 2f+1 的情况发生,这被称为Lost Majority。在Lost Majority的情况下,受影响的Range将无法正常工作。
为应对这种情况,RecoveryBalancer提供了一项关键能力。即允许节点检测自身是否处于Lost Majority的状态,并在必要时主动缩减副本列表配置,以确保至少有半数节点处于存活状态,从而使Range能够继续正常运作。然而,需要特别注意的是,在使用RecoveryBalancer自动恢复处于Lost Majority状态的Range时,如果之前失败的节点未经人工干预重新加入集群,可能会引起数据丢失和不一致等问题。这种情况下,用户需要慎重考虑并结合实际运维策略来决定是否启用RecoveryBalancer功能。
▲RecoveryBalancer功能
▌应用场景
BifroMQ有状态子服务有三种:MQTT订阅路由、离线消息队列、Retain消息。分别由对应模块:dist-worker、inbox-store和retain-store来实现。每一模块部署后均构成一个独立的base-kv集群。
为适应不同使用场景和负载需求,BifroMQ允许各集群根据其特定情况选择并启动相应的Balancer策略,用以使各集群都能在保持高可用性的同时,实现最优数据处理吞吐性能。
dist-worker
在BifroMQ架构中,dist-worker模块承担着管理订阅信息(Sub)和消息分发(Pub)的职责。在正常使用场景下,这通常是一个以读操作为主、写操作较少的场景。
考虑到这一使用场景和base-kv的负载处理能力,dist-worker采用了以下默认Balancer策略:
启用ReplicaCntBalancer:这一策略确保KVRange副本数与集群节点数保持一致,最大化查询吞吐效率。
控制Voter副本数:在保证高可用性的同时,将Raft Voter的数量限制在最多3个,其他副本则作为Learner角色,以此降低写操作的响应延迟。
在大多数常见的使用场景中,一条Publish消息匹配到的订阅者数量通常并不多,且匹配过程也较为迅速。结合标准的操作环境中,当前设置已能满足大部分性能要求,dist-worker模块在其默认配置下并没有激活Range拆分策略。
然而,当面对大规模Fanout场景,特别是那些对低延迟有更高需求的情况时,单个Range查询效率可能会变成制约整体性能的瓶颈。为了应对这类挑战,未来BifroMQ中将增强这一方面的处理能力,以优化大规模Fanout场景下的性能表现。
retain-store
Retain消息的工作负载与订阅信息和消息发送的处理相似,正常使用下主要表现为读操作多于写操作。基于这一特点,retain-store的默认Balancer策略被设置为与dist-worker模块一致,在此不做重复说明。
inbox-store
在BifroMQ中,inbox-store模块承担着管理每个cleanSession=false连接离线消息的角色。对于这类连接,inbox-store会创建一个专属持久化离线消息队列。归属于这些连接的Publish消息首先被写入这些队列中。当连接恢复在线状态时,消息将从队列中取出进行推送并随即删除。构成一个典型的高频读写场景,其中工作负载主要集中在Leader Range副本上,且KV存储操作的IO时延对整个系统的消息处理能力有着不可忽视的影响。
考虑到这种特定使用场景和base-kv的负载处理特性,inbox-store采取以下默认的Balancer策略:
默认限制Voter为1:由于副本数量越多可能导致写入响应时延增长,因此默认设置中将Range副本数限制为1,以优先保障消息的快速处理,但会有一定可靠性损失,用户可以根据情况增加副本数量。
启用RangeSplitBalancer和RangeLeaderBalancer:这一策略使得inbox-store能够随着工作负载的增长动态进行分片扩容,最终实现更加均衡的负载分布,提升系统整体性能。
StandardCluster的部署策略与灵活性评估
BifroMQ StandardCluster采纳集所有功能模块于单一进程的部署策略。这种策略使得配置和部署过程相对简化,类似于业界普遍采用的"SharedNothing"集群架构。尽管如此,多个模块共享同一进程内的系统资源,难免限制基于实际业务需求的动态配置能力。所有模块需统一进行扩缩容,这在某些情况下可能导致灵活性不足。这一点在云业务场景中尤为明显,因为不同类别的负载波动与时间相关,需要更加细致和灵活的资源管理。
单个模块独立进程的实施与过渡
BifroMQ的独特架构设计使其能够轻松实现"单个模块独立进程"的部署模式,我们称之为IndependentWorkload Cluster(未来版本中推出)。这种模式不仅提供更高的灵活性和精细的资源管理能力,还能够帮助用户根据业务的发展逐步从StandardCluster模式过渡到IndependentWorkload Cluster模式。这种渐进式的部署变化可在保持业务连续性的同时,优化资源配置和应对业务需求的波动。
▲IndependentWorkload Cluster 过渡
总结
以上内容为BifroMQ利用多种机制确保集群整体高可用性的全面介绍。本专题下期文章,我们将从BifroMQ Topic订阅匹配方案的设计和实现思路出发,为您带来全新视角深入解读。
本次系列文章围绕BifroMQ在MQTT上的设计实现做全面解读,欢迎对MQTT技术感兴趣的同学关注。也期待大家更多的参与(BifroMQ社区,详见:https://github.com/baidu/bifromq ),提供宝贵的深入使用反馈意见,共同推动这一技术的成熟。