1. 前言 上篇《TDBank:腾讯万亿级实时数据接入系统》总体介绍了TDBank的整体架构,本篇重点介绍处于TDBank核心的消息存储中心——Tube的主要设计原则以及Tube系统在实现上采用的一些方案和策略。 2. Tube系统概述 2.1 什么是Tube
如上图,Tube作为TDBank平台中的消息存储中心,是TDBank的核心组件,但Tube可作为单独的消息中间件被使用。 Tube由腾讯数据平台部自行研发,其系统架构思想源于Apache Kafka,在实现上,则完全采取了自己的方式,并且使用了更加优化的分区管理和分配机制和全新节点通讯流程,同时还基于Netty和Google Protobuf自主开发了高性能的底层RPC通讯模块;这些实现使得Tube在保证实时性和一致性的前提下,具有了更高的吞吐能力,更适合作为处理海量数据的消息中间件; 2.2 功能与特性 Tube实现JMS规范中的发布/订阅模型,且发布端、订阅端的可使用集群模式,同时支持负载均衡发布端和消费端;消费端以Group分组,同一分组下的消费者消费同一份数据,且可以重复消费多次;不同分组之间相互独立、互不影响。通常情况下,Tube可以做到消息的“零误差”,即不丢失、不重复、不损坏;极端场景下,也仅会丢失或重复少量的消息。 在性能方面,单个Tube集群可稳定承载5w以上的客户端(生产者/消费者)数量,单台broker并发写入量可达10w TPS,使用1k大小的消息测试(机器配置:12核2.1GHz CPU带超线程、64G内存,Raid 5级磁盘阵列)时,可跑满千兆网卡带宽;Tube在绝大多数场景下可以将消息的延迟限制在毫秒级。 2.3 架构设计 Tube系统主要有三部分组成:客户端(producer&consumer)、服务端(broker)和中心节点;其中,客户端包括Producer和Consumer API,主要用来生产和消费消息数据;服务端则是用来接受Topic的发布和订阅,并存储消息数据;而中心节点主要为Master节点,用来收集和监控整个集群的状态。 另外,目前在生产环境部署的Tube集群还使用了Zookeeper,目前主要用来保存Consumer的消费位置和Master HA的选举,不过Zookeeper的存在主要是历史遗留,全新的Tube系统设计是可以摆脱对Zookeeper依赖的。 当前Tube系统的整体架构如下图所示:
其中,Broker负责数据的存储,为集群部署模式,Producer为生产者,它将消息发送到broker集群,Consumer为消费者,从Broker读取消息到自己的本地处理;Producer和Consumer可以是单机模式,也可以是集群模式;Master为中控节点,负责收集整个集群的状态信息,并控制消费者的分区消费平衡。 Tube采用了Kafka的分区设计思想,其各个节点主要交互流程如下: a. 消息按照内容分为多类,每一类消息有一个Topic,每个Topic可以包含多个分区,分布在若干Broker上;Producer可以生产某一个或多个Topic数据,此后Consumer可以指定这些Topic的数据进行消费; b. Broker向Master汇报自身信息,包括自身id、状态以及提供哪些Topic的发布和订阅服务,每个Topic下包含多少分区; c. Producer向Master通报要发布的Topic名称,Master给Producer返回哪些Broker可以接收这些Topic的数据;此后Producer可直接向这些Broker生产数据; d. Consumer向Master通报要订阅的Topic名称,Master给Consumer返回可以从哪些Broker获取数据;此后Consumer则直接向这些Broker拉取数据;不同业务Consumer通过Group来区分,同一个Group下的Consumer消费同一份数据,每个Consumer会负责消费其中的一部分分区,Consumer之间消费的分区互不重叠,Consumer和分区的消费对应关系由Master统一分配;不同Group下的Consumer消费互不影响,既可以消费相同Topic的数据,也可以消费不同Topic的数据; e. 集群节点间通过心跳和Master保持状态同步,当状态发生变化时,Master会生成相应时间并负责通知相关节点; f. 为了避免中心节点的单点存在,Master采用主备模式,并通过Zookeeper来进行选举。 2.4 应用情况 目前Tube主要应用于腾讯大数据平台数据接入服务,为后端的离线、实时等计算平台以及相关业务系统提供数据存储和分发服务; 当前生产环境已部署Tube集群4个,待升级集群3个,服务于腾讯七大事业群数百个业务,每天处理的消息量达8000亿,预计今年底将达到1.5万亿。 3. 经典消息模型 JMS规范中定义了两种消息系统的模型,一个是点对点(Point to Point,简称P2P)模型,另一种是发布/订阅(Publish/Subscribe,简称P/S)模型,下面分别进行简单介绍。 3.1 点对点模型 在典型的点对点模型中,主要包含三个要素,消息的发送者(Sender),消息的接收者(Receiver)和存储消息的队列(Message Queen);发送者首先将消息发送到队列,然后接收者从队列读取消息,其流程如下所示:
P2P是个简单的“一对一”消息模型,Sender发送消息到指定队列,Receiver从指定队列读取消息,虽然Receiver从物理上讲也可以有多个,但是逻辑上还是一个单一的整体,即一条消息只能被其中的一个Receiver处理。 3.2 发布订阅模型 发布/订阅模型同样是由三部分组成,即消息的生产者(Producer)、消息的消费者(Consumer)以及消息主题(Topic);Producer通过发布(Publish)操作,将消息发布到指定的Topic下,而Consumer则通过订阅(Subscribe)操作来消费Topic下的消息;其关系如下图所示:
与P2P模型不同的是,P/S模型是一对多或者是多对多的关系,即Producer和Consumer都可以有很多,二者通过Topic联系到一起; P2P模型和P/S模型之间另外一个很重要的区别就是,对于前者,Sender每次都是“有目的”的发送消息,因此Receiver需要对接收道德消息进行确认,以便告诉Sender“我成功的处理了消息,你也可以做相应处理了”,如果Reveiver某个时段并不在线,那么Sender发送的消息就会一直保留在队列中,以便后续Receiver上线后能够继续读取这些消息,直到超时发生;从这个角度来看,P2P模型的处理流程中其实是隐含了一些事务的特征, Sender和Receiver的耦合性也更强; 而在P/S模型中,Producer将消息发送到Topic之后,就完全不关心之后发生的事情了;无论是哪些Consumer消费了这个消息,消费到什么位置了,都与Producer无关;而同样Consumer也不关系自己消费的消息到底是哪个Producer发送的,它只关心消息的内容。 除了以上这些差异外,两种模型其实还有很多其他细节差异,这里不再一一赘述。 3.3 Push vs Pull 在消息系统的设计中,另一个需要权衡的就是Push方式和Pull方式的选择。 熟悉微博或社区产品的同学可能都比较熟悉这两个概念;比如在微博中,一个大V可能有成百上千万的粉丝,一个“无聊”的粉丝也可能关注几百上千个大V或朋友,那么这就带来一个问题,当一个会员发一条微博消息时,应该如何将该消息的内容传递到他的粉丝那里?同样,当一个用户刷新微博页面的时候,应该如何获取他所关注的人的信息? 由于体量的原因,这里的信息传递即会存在一个Push和Pull的选择问题。 所谓Push模型,即是当Producer发出的消息到达后,服务端马上将这条消息投递给Consumer;而Pull则是服务端收到这条消息后什么也不做,只是等着Consumer主动到自己这里来读,即Consumer这里有一个“拉取”的动作。 选择推(Push)方式还是拉(Pull)方式是所有大型的消息系统中必须要面对的问题,因为这直接关乎到整个系统的使用场景,吞吐量、实时性以及性能问题;这里边主要有如下几个问题需要探讨。 存在多个(组)Consumer 在P/S模式下的消息系统中,一份消息往往会被多个(组)消费者消费,这是一个很常见的场景。采用Push模型的情况下,服务端需要记录每个Consumer对Topic的订阅关系,以便当Topic下有消息到达时,能够及时将其推送给对应的Consumer,这个绝大多数时候不是问题,但是一旦当Consumer的数量很大同时消息的生产又比较快时,推送操作便会成为服务器的一个沉重负担,如果要接连不断的同时向成千上万个Consumer推送消息,服务器甚至可能崩溃掉,这种场景下Pull模式显然更具优势。 Consumer同时订阅的多个Topic 如果一个Consumer同时订阅了多个Topic,在采用Pull模式时,一旦有Consumer来拉取数据,服务端都需要查询每个Topic下是否新的消息,以决定是否给Consumer返回数据,而如果Consumer订阅的Topic非常多,服务器就需要耗费大量的精力来做遍历查询,即使其中的90%的Topic下都没有新的数据产生;这种情况下,采用Push模式显然比单纯的Pull更合适。 部分或全部Consumer不在线; 在消息系统中,Producer和Consumer是完全解耦的,Producer发送消息时,并不要求Consumer一定要在线,对于Consumer也是同样的道理,这也是消息通信区别于RPC通信的主要特点;但是对于Consumer不在线的情况,却有很多值得讨论的场景; 首先,在Consumer偶然宕机或下线的情况下,Producer的生产是可以不受影响的,当Consumer上线后,可以继续之前的消费,此时消息数据不会丢失;但是如果Consumer长期宕机或是由于机器故障无法再次启动时,就会出现问题,即服务端需不需要为Consumer保留数据,以及保留多久的数据等等; 在采用Push方式时,因为无法预知Consumer的宕机或下线是短暂的还是持久的,如果一直为该Consumer保留自宕机开始的所有历史消息,那么即便其他所有的Consumer都已经消费完成,数据也无法清理掉,随着时间的积累,队列的长度会越来越大,此时无论消息是暂存于内容还是持久化到磁盘上(采用Push模型的系统,一般都是将消息队列维护于内存中,以保证推送的性能和实时性,这一点会在后边详细讨论),都将对服务端造成巨大压力,甚至可能影响到其他Consumer的正常消费,尤其当消息的生产速率非常快时更是如此;但是如果不保留数据,那么等该Consumer再次起来时,则要面对丢失数据的问题; 折中的方案貌似是给数据设定一个超时时间,当Consumer宕机时间超过这个阈值时,则清理数据;但这个时间阈值也并太容易确定; 在采用Pull模型时,情况会有所改善;服务端不再关心Consumer的状态,而是采取“你来了我才服务”的方式,Consumer是否能够及时消费数据,服务端不会做任何保证。 Producer的速率大于Consumer的速率; 对于Producer速率大于Consumer速率的情况,有两种可能性需要讨论,第一种是Producer本身的效率就要比Consumer高(比如说,Consumer端处理消息的业务逻辑可能很复杂,或者涉及到磁盘、网络等I/O操作);另一种则是Consumer出现故障,导致短暂时间内无法消费或消费不畅。 Push方式由于无法得知当前Consumer的状态,所以只要有数据产生,便会不断地进行推送,在以上两种情况下时,可能会导致Consumer的负载进一步加重,甚至是崩溃,除非Consumer有合适的反馈机制能够让服务端知道自己的状况。而采取Pull的方式问题就简单了许多,由于Consumer是主动到服务端拉取数据,此时只需要降低自己访问取频率就好了。 消息的实时性 采用Push的方式时,一旦消息达到,服务端即可马上将其推送给服务端,这种方式的实时性显然是非常好的;而采用Pull方式时,为了不给服务端造成压力(尤其是当数据量不足时,不停的轮询显得毫无意义),需要控制好自己轮询的间隔时间,这必然会给实时性带来一定的影响。 3.4 Tube的选择 就消息模型而言,互联网应用场景中, P/S模型显得更有用武之地,Tube系统实现的也是该模型,所以我们后边的讨论所涉及到的术语也主要是和P/S模型相关;但是在实际的系统设计中,我们不会拘泥于规范的定义和细节,模型怎样规定我们就怎样做;消息系统要服务于业务需求,业务需要什么样的特性,我们就增加什么样的特性,不会太关心我们的系统到底是属于P2P模型还是属于P/S模型。 而对于Push和Pull方式的选择,根据前面的讨论我们不难得出结果,像Tube这种旨在服务于大数据平台,需要进行海量数据分发的消息系统,Pull方式无疑是更好的选择,它使得我们可以在牺牲一定实时性的前提下,可以获得更高的吞吐和更强的消息堆积能力。 4. 关于消息一致性的保证 在消息的一致性模型中,主要包含三个方面的内容,分别是消息的可靠性,消息的顺序和消息重复,下面分别讨论。 4.1 消息的可靠性 首先可靠性必须是第一位的,一个消息系统如果无法提供一个可靠的数据服务,那它几乎没有存在的价值。但是需要注意的是,在有些业务中,数据必须绝对的可靠,比如交易、银行、证券等领域;但是在有些场景中,只需要做到基本可靠即可; 在互联网大数据场景下,由于体量非常巨大,而数据的使用又偏向统计和分析,所以其对数据的可靠性要求并不像交易、金融等业务系统那么高,可以在一定程度上容忍少量的数据错误或丢失,而这一点也是Tube实现高吞吐量的一个前提。 目前Tube系统主要在两个地方可能会有数据丢失,第一是Tube采取了Consumer信任模型,即数据一旦被Consumer拉取到本地,就默认会消费成功,如果Consumer在实际消费的过程中出现错误(Tube可以保证消息数据的正确性,所以这种错误一般是由业务方消费逻辑或代码问题导致),则Tube并不负责恢复,所以这里存在一个丢失数据的风险;再一个就是由于操作系统Pagecache的利用,服务器断电或宕机而可能带来的数据丢失。 4.2 顺序的消息性 由于Tube沿用了Kafka的分区设计思想,而分区的数据消费之间是没有先后顺序关系的,而且Tube支持消息的异步方式发送,在这种方式下,网络并不能保证先发送的消息就一定会先到达服务端,所以Tube一般不提供顺序性的保证; 目前系统结构下,只有使用单一分区并且采用同步发送接口的情况下,才可以实现消息的顺序写入和读取;但是这样的使用方式,其性能和吞吐则会大打折扣,完全背离了Tube服务于海量消息传输的初衷。 4.3 消息的重复 前面我们提到过,在Tube集群中,Consumer的消费位置信息由Broker端进行管理,所以在某些异常情况下,Broker可能无准确获得Consumer的实际消费情况而导致数据重复;另外就是出于性能考虑, Consumer的消费位置信息在每次变化时,并不会实时更新到持久化存储中,而是暂存于内存,周期性更新,如果此时broker宕机即会导致少量的数据重复。所以在使用Tube时,业务本身对数据的处理要具有幂等性或者是能够容忍少量的数据重复;而在大数据的使用场景中,后者一般不是问题。 5. 实时性与吞吐 关于实时性和吞吐,我们在前边Push和Pull方式选择中已经有过一些讨论;一般来说,在分布式系统中,实时性和吞吐是相悖的,通常情况下,二者只能偏于其一。 一个消息系统能达到什么样的吞吐量、性能和实时性,主要取决于三个部分:客户端消费模型、服务端存储模型以及底层通信模型。其中,客户端消费模型主要是指前面的Push和Pull模型,服务端存储模型和底层通信模型我们将在接下来的章节进行详细介绍。 6. 通信框架 无论是消息系统还是其他的分布式系统,其高效的数据通信都要依赖于底层的通讯框架。在Tube这种性能为王的消息系统中,一个是高效的RPC框架更是必不可少的;而高性能的RPC框架设计,主要体现在两个方面:高效的I/O模型和优秀的序列化/反序列化框架;下面我们就分别介绍。 6.1 I/O模型 有人将网络编程的经验总结为异步、队列和多线程;虽然只有三个词,却基本道出了网络通信框架开发时的核心要点。当然,虽然我们都熟悉网络通信的原理并且也有着不少的实践经验,但是没有必要重复造轮子,自己去开发一个通信框架,当前各种语言的通信框架比比皆是,而且性能不俗,只要从其中找一款合适自己的稍加改造来用就好了。 当前的通信框架的实现多采用I/O多路复用的方式,经典的设计模型有Reactor和Proactor两种;对于上层应用来讲,这两种方式效果其实差不多,主要是根据底层操作系统是否支持异步机制而决定采用前者还是后者。 Netty是基于Java NIO实现的一个经典Reactor模型的框架,其性能和稳定性都得到了充分地验证,也是当前Java领域很流行的一款网络开发框架。其Boss/Worker的线程模型可以实现对大量连接请求和数据请求的高效管理,同时还提供了简单的编程接口,使得我们的开发效率提升了不少。 Tube的RPC实现便采用Netty作为其I/O通信模型部分,不过在Tube RPC的接口设计中,完全是抽象化的通用的,并不会和某种特定的通信框架绑定,完全可以替换成其他的诸如Mina或Grizzly等NIO框架,以充分利用他们各自的特性和优势。 6.2 序列化/反序列化 不管我们是在设计还是在优化一个RPC框架时,始终都应该朝着一个“终极”目标,那就是尽量跑满网卡。随着硬件的不断换代升级,在普通的PC Server中,速度的瓶颈其实往往是在网卡;即便是经常被大家诟病的机械磁盘,按照当前主流SATA盘的转速,其顺序读写也达到了170MB/s ~ 200MB/s甚至更高,而在我们大部分的PC Server上,配置的还都是千兆以太网卡。 既然网卡带宽已经到顶了,那我们只能在数据压缩上下功夫了。这里的数据压缩包含两个部分,一个是对象的序列化,另一个则是真正含义的数据压缩;对于一个分布式系统来讲,对象的序列化是必须要有的,但是序列化后的数据是否还需要再次压缩,则会根据具体情况来定,而且对象序列化本身也是一个压缩的过程,其中会针对不同的数据类型使用一些经典或独有的压缩算法;所以,下面我们主要介绍数据的序列化这块内容。 一般来说,我们对一个序列化框架的关注主要有以下几点: a、 序列化速度;即对一个普通对象,将其从内存对象转换为字节数组需要多长时间;这个当然是越快越好; b、 对象压缩比;即序列化后生成对象的与原内存对象的体积比; c、 支持的数据类型范围;序列化框架都支持什么样的数据结构;对于大部分的序列化框架来说,都会支持普通的对象类型,但是对于复杂对象(比如说多继承关系、交叉引用、集合类等)可能不支持或支持的不够好; d、 易用性;一个好的序列化框架必须也是使用方便的,不需要用户做太多的依赖或者额外配置; 对于一个序列化框架来说,以上几个特性很难都做到很出色,这是一个鱼和熊掌不可兼得的东西,但是终归各有各的优势和长处,需要使用者根据实际场景仔细考量。 Tube在开发时使用了Google的Protobuf作为序列化框架,主要是看中它在资源消耗、压缩比上的综合优势,同时Protobuf也在业界其他公司所广泛使用,在易用性和稳定性方面经受了考验,而且有丰富的文档支持。 7. 存储模型 存储的选择是消息系统中非常重要的一环,它直接关系着整个系统所能提供的功能和性能。Tube在存储层上实现了接口化设计,具备根据实际情况更换存储引擎的能力,从而可以利用不同存储引擎的特性,来为消息系统增加更多丰富的功能;这一点有点类似于MySQL数据库。 7.1 文件存储 消息的接收和发送通常是一个典型的顺序队列读写场景,而顺序读写则是磁盘的强项。Tube目前也主要使用文件作为其存储引擎。但是,对于P/S模型的消息系统,Topic的数量显然不止一个,有可能会有很多。那么这里就会有一个问题,就是所有的消息都存放在一个队列(文件)中,还是多个队列(文件)中? 在这一点上,不同的消息系统有不同的实现选择;在单队列的模式下,所有Topic的数据混合保存,各自以Topic名称作为区分;但是此时会有一个问题,就是当拉取数据时,每个Topic下的Consumer都要逐一扫面所有磁盘数据,直到找到需要的消息为止,期间它也不得不扫描大量根本不属于它的消息,显然这种情况带来的磁盘I/O和随机读写都是难以接受的;所以通常在采用单一的磁盘物理队列存储之外,还会在磁盘或内存中维护各个Topic自己的逻辑队列,但在逻辑队列中一般不会保存消息数据,而是只存储消息在物理队列中实际位置的索引值。 在多磁盘队列的情况下,每个Topic下的Consumer会扫描各自的队列文件,避免了无效的文件扫描,所以消息的读取效率会有提高,但是在Topic数量很大的情况下,仍然会有较多的磁盘随机读写。 可以看到,在以上两种存储方式中,都无法完全避免磁盘的随机读写,所以,通常的做法是使用大内存的服务器,通过OS的Pagecache来缓存最新部分的消息,让数据所在的page被替换出内存前就完成消费;经过实践证明,这种措施非常有效。 鉴于大内存的昂贵性,另一种替代方案是使用SSD作为“缓存”介质,而磁盘仍作为持物理存储介质,这样可以得到更多的空间来存放新数据,尽量降低磁盘I/O,但是这样又会提升存储管理的复杂性。 Tube在实践中采用了每个Topic单独队列的存储方式。另外,在数据安全性方面,Tube目前只使用了磁盘的Raid 10来保证数据的容错,这在实际使用中存在一定的风险。但在我们的使用场景中,由于系统能够提供很高的吞吐和性能,一般很少存在数据积压的情况,所以这种风险并不会带来太大的影响;同时,我们也在考虑更为安全的磁盘存储方案。 7.2 关系型数据库 关系型数据是大家都很熟悉的存储方式,它能够提供良好的信息条件检索能力,这在消息系统的很多使用场景下都是很有帮助的;如果使用文件的存储方式,在满足条件查询需求时,往往需要构建大量的内存队列和索引结构,这个成本是非常高昂的,其复杂性也对开发人员有很高的要求。 但是关系型数据库最大的弱点就是容量和性能太差,难以满足大数据场景下的数据规模和惊人的吞吐量。 7.3 NoSQL存储 NoSQL存储则是介于文件和关系型数据库之间的一个选择,它们基本都支持一级索引,可以实时点查询,同时具备很好的线性扩容能力,使得数据的增长不再是问题,此外,大多数NoSQL存储都具备数据容灾的能力;所以将数据的存储置于NoSQL上,对于消息系统来讲,是一个很值得考虑的选择; 目前,Tube正在尝试将HBase纳入其默认支持的存储引擎行列,以其使系统能有更丰富的功能和更多的适用场景。 8. 关于事务支持 消息系统中的事务主要分为两类,一类是系统内部协调的发送端或接受端的本地事务,这类事务的实现相对简单;另一种则是多系统间协调的分布式事务,如在交易下单的场景中,经常需要写入数据库同时发送一条交易通知消息,甚至还要同步更新缓存等,此时事务的协调需要在多系统间进行,除了各个系统需要分别实现事务接口外,还需要一个独立的事务管理系统来参与协调,此时的实现就会复杂很多。 由于Tube服务于海量数据传输的定位,在实际的应用场景中,很少需要提供事务级别的一致性保证,同时事务的支持还会吞吐和性能上的影响以及复杂性的提升,所以Tube目前尚未支持事务,未来也暂无相应的计划。 9. Scale Out与容灾 9.1 线性扩容 一个分布式系统集训部署到生产环境后,随着使用量和数据量的不断增大,可能很快就会到达其性能瓶颈,所以其线性扩容能力是非常关键的一点。 在Tube集群中,各个节点的状态共享和分区平衡的管理是由Master完成的,节点之间无需共享任何数据,所以其线性扩容的关键点在于Master节点。 根据压力测试和当前现网的资源使用情况,Master节点最大可支撑5w以上的客户端和上百台的Broker数量,并能够保持稳定运行,随着后续的不断优化,这个数据还可能更高;可以预见,在未来的一段时期内Master都将不会达到性能瓶颈。 9.2 跨机房部署 随着业务量的不断增大,机器数量越来越多,集群跨机房部署已经是一个很常见的场景。 一般来讲,跨机房部署主要会带来两个方面的好处;第一点就是容灾,当一个机房的网络出现问题时,我们能有另外一个机房可以提供服务;另外一点好处就是可以就近提供服务。对于Tube来讲,无论是Producer还是Consumer端,都在业务方的系统当中,这种情况下就很难要求业务方将集群和我们在同一机房中部署,这就需要Tube提供跨机房的支持。 跨机房部署需要解决的两大问题就是容灾和延时。当前Tube还不具备跨机房容灾的能力,但是对于Producer/Consumer端的跨机房,目前已经有在生产环境部署使用并且运行稳定,但是因为网络时延的存在,在性能和吞吐上有一定的下降。 9.3 Master HA 从前面的介绍中能够看出,Master负责整个集群的状态的收集和消费者的分区平衡,并且是一个单点存在,所以我们通过Master HA的方式来实现Failover,以增强系统的稳定性。 当前部署集群的HA主要是通过Zookeeper的选举来实现,同时,Master还可以通过主备心跳的方式来实现,以摆脱对Zookeeper的依赖; 另一方面,Master所存储的数据全部来自于Client端(Producer、Consumer、Broker)的心跳汇报,并且全部保存于内存,除了下发分区平衡指令外,Master不再和client共享其他任何信息;所以可以看到,这里即便不适用HA,在单机的情况下Master发生宕机,那么也只是集群无法再进行Client的加入和退出操作,即整个集群处于“冻结”的状态,但是原有的发布订阅关系不会受到影响,仍然可以正常的发送或消费数据。 同时,对于主备HA可能出现的“脑裂”问题,我们在Client端增加了check机制,当发现双机都处在Active状态时,则拒绝接受Master下发的分区平衡命令,使集群处于“冻结”状态并发出告警。
|
|