1. ZAB协议
如果说到Zookeeper
的FLE
选举算法和广播通信,那就绕不开Zookeeper
的底层通信协议ZAB
。
ZAB
协议全称为Zookeeper Atomic BroadCast
,即Zookeeper
原子广播,Zookeeper
则是通过ZAB
协议来保证分布式事务的最终一致性。这里需要注意,ZK
只能保证最终一致性,而不是强一致性,即在某一时刻读取任意两个机器的数据,可能是不一样的。
ZAB
协议使用的是主备系统架构模型,只有Leader
会进行真正的写事务处理,Follower
和Observer
则负责提供读操作和与Leader
进行数据同步。(Follower
和Observer
实际差别挺大的,后面再仔细分析)
ZAB
协议共分成了三个阶段:
1、 发现:指的就是实际的选举流程,此流程会在集群机器中选举出Leader
、Follower
和Observer
,且Leader
会维护一份可用的集群客户端通信对;
2、 同步:在集群中选举出Leader
后,Leader
将本身的数据同步给集群内的其它机器,实现集群多副本,保证可用性;
3、 广播:在集群完成选举和数据同步后,集群就可以正式对客户端提供功能了,此时客户端对集群的写请求都会经过Leader
,Leader
再对集群广播Proposal
事务请求,完成集群对客户端的请求同步(实际上还有ack
和commit
等流程,但不是本篇重点,所以忽略);
ZAB
协议也可以分为两个模式:
1、 恢复模式:ZAB
协议的发现和同步阶段,在代码中可以称为LOOKING
状态;
2、 广播模式:ZAB
协议的广播阶段,在代码中表现为LEADING
、FOLLOWING
或OBSERVING
状态;
ZAB
协议的内容很多,如果要一篇文章就全部搞定,估计看完都得2-3个小时,更别说要一次性消化完了。因此本篇就只分析一下发现阶段,并主要着重于选举算法的实现和一些细节,并思考解答一下该流程所涉及的一些常见问题。
2. 集群配置
在开始选举前先把关键的集群配置说明一下:
# 选择leader选举算法
electionAlg=3
# 集群类型
peerType=participant
# 和参与者一样,观察者现在默认将事务日志以及数据快照写到磁盘上
syncEnabled=true
# 配置的集群通信机器信息
server.sid1=host:port:electionPort:type
server.sid2=host:port:electionPort
参数说明:
- electionAlg:选择Leader的选举算法:0对应于原始的基于UDP的版本;1对应于快速Leader选举基于UDP的无身份验证的版本;2对应于快速Leader选举有基于UDP的身份验证的版本;3对应于快速Leader选举基于TCP的版本,简称FLE。默认为3,FLE也是最常用的算法;
- peerType:集群类型,observer或者participant;
- syncEnabled:和参与者一样,观察者现在默认将事务日志以及数据快照写到磁盘上,这将减少观察者在服务器重启时的恢复时间。将其值设置为false可以禁用该特性。默认值是true;
- server:配置的server信息,需要把自身也配进去,有三种配置方式,sid1则是本机器的编号,一般配置为整数,port为配置该server和集群中的Leader交换信息所使用的的端口,electionPort为配置选举leader时所使用的端口,type的类型是observer或participant。
从server
的配置方式我们可以暂时推断出,集群内新加入的机器是一定要有目标集群的所有机器地址+端口信息的,否则新加入的机器无法向集群的其它机器发送消息。需要注意的是,如果server
不配置electionPort
将无法参与集群的选举通信,type
不配置默认为participant
类型。
participant
和observer
数量也有限制,当participant
的数量小于2
,observer
数量只要大于0
启动就会报错,participant
的数量不是奇数日志会进行提示。
3. 选举发现
发现阶段ZK
做了很多有意思的小设计,但这些小设计如果没注意就很容易踩坑,接下来在介绍选举流程时会一一把小设计引出来。
假设该阶段的选举算法使用了默认的FLE
算法,FLE
算法的全称为Fast Leader Election
,即快速选择Leader
算法,顾名思义,该算法的核心便是快速选择Leader
,那么该算法是如何保证可以快速确定Leader
的?接下来一起慢慢解开该算法的面纱。
3.1 选举的三个条件参数
在开始正式的选举流程分析前,先需要知道哪些参数是选举流程中最为重要的,三个参数说明如下:
1、 epoch
:类似于古代的朝代,每更替一个朝代epoch
就会+1,在ZK
中每进行一轮新的选举epoch
也会+1,初始值为0;
2、 zxid
:在ZK
集群中zxid
是全局唯一的,每来一个请求zxid
就会+1,初始值为0;
3、 myid
:机器最基本的标识,如果myid
配成1
,server
也必须得配置一个server.1
,选举时等同于本机器的sid;
3.2 选举核心规则
优先级从上到下:
1、 epoch
谁大谁当选:正常的ZK
集群机器运行时所有的机器epoch
都是一样的当某个机器收到epoch
要大的投票信息说明本机器因为某种意外新的选举结果未收到,因此本机器收到新的朝代信息直接变成Follower
加入;
2、 zxid
谁大谁当选:如果epoch
相同,选举时谁的zxid
要大说明谁接收的信息最多,应该让其成为Leader
以便最大限度的减少请求的丢失;
3、 myid
谁大谁当选:在初始阶段epoch
和zxid
都是一样的,因此需要有一个最基本的标准来衡量谁来当Leader
,这个最基本的标准便是每台机器都拥有的myid
在选举时也会以SID
的别名来称呼;
换成通俗一点的话来讲就是:
1、 epoch
谁大谁当选:周星驰的《九品芝麻官》里有句话是“你竟然用前朝的剑来斩本朝的官?”这句话的隐藏意思就是前朝的权利在本朝等于无效,应当以本朝的权利优先,不管是前朝的尚方宝剑还是前朝的皇帝;在ZK
中也是如此,不管上一个epoch
值机器是Follower
或Leader
,只要机器的epoch
比集群最新的epoch
要小,就说明需要无条件服从新的epoch
;
2、 zxid
谁大谁当选:当以这个条件为判定标准,就说明参与选举的机器都是同一个epoch
值,当集群处理一个客户端请求时zxid
便会+1,因此zxid
可以看成是处理事务的能力,zxid
的值越大,就说明该机器接触过的事情多,能力大,在同一个选举朝代中,能力越大肯定会选举该机器当选Leader
;从本质上来看,这里的能力越大就是现实中的网络更好或机器算力更强;
3、 myid
谁大谁当选:如一开始所说,初始阶段epoch
和zxid
肯定所有机器都是一样的,此时来选举谁当Leader
,就一定有个预先设定的值来做判断就如一个部门刚刚成立,所有人互不相识,需要一个Leader
,但投票总要有根据的,比如每个人手上都有其他人的简历,根据简历再去投票谁更适合当Leader
;而在ZK
集群中,myid
的大小就代表着简历内容的优劣,作为兜底的判断条件来选举Leader
;
因此从这些选举的核心规则我们就能轻易得知:
- 集群的Leader一般而言算力和性能会比其它的机器要更好;
- 在初始化集群时,给算力和网络最好的机器设置最大的myid可以有效提高集群的处理能力;
3.3 建立通信对
3.3.1 流程分析
在开始集群选举Leader
流程前有一个必要的流程:确定集群内有哪些机器参与选举并和这些机器建立通信对。ZK
在建立通信对这上面花了点小设计,假设现在参与投票的三台机器还是下面的server
配置:
server.1=host:port:electionPort
server.3=host:port:electionPort
server.5=host:port:electionPort
配置启动后集群内的机器会向server
列表的所有机器都发送投票给自己的消息,这个流程分两个阶段,第一阶段通信图如下:
其中每台机器都会先给自己投一票,然后再和其它的机器建立Socket
通信去发送自己的投票信息,从图中可以看到,SID
大的机器向SID
小的机器建立通信Socket
都成功了,但SID
小的机器向SID
大的机器建立通信Socket
都被关闭了。
经过第一阶段,每台机器的通信对集合情况如下:
可以看到第一阶段后,SID
最小的机器没有大SID
机器的通信对,小SID
机器的通信对建立会在第二阶段完成,通信图如下:
在第一阶段SID
大的机器主动把SID
小的机器通信Socket
关闭了,但由于在第一阶段SID
大的机器向SID
小的机器发送过建立通信对的操作,所以第二阶段中SID
小的机器将会由于被连接而和SID
大的机器建立通信对。经过第二阶段通信对集合情况如下:
经过了这两个阶段后集群内的所有机器才算完整的建立了通信网络。
注:上面表示的都是participant
之间的建立通信对流程,如果是observer
与observer
之间是不会互相建立通信对的。
3.3.2 思考与总结
看完上述流程是不是觉得很困惑?为什么ZK
实现的ZAB
协议在发现阶段建立通信对时一定要SID
大的机器来主动连接建立通信对,SID
小的机器为什么对SID
大的机器毫无主动权?
个人觉得如果要解答这个问题,关键有两个点:
1、 SID
唯一性:在ZK
的机器上,如果配置了两个SID
一样的server
配置,那么有一个一定会覆盖另外一个,并且由于文件会转成Properties
对象,因此key
的顺序也是没办法控制的基于这个原因,新的SID
要避免覆盖的问题,值最好一直往上增加,如果使用数字间隔的方式来预留位置,过于复杂也不便于后续维护;基于这个原因ZK
为SID
大的机器赋予了更多的主动性,目的在于鼓励开发者按SID
递增的方式去配置;
2、 集群新增机器:这是解答上面这些问题最核心的点,简单来说就是原集群只包含了A-B-C
三台机器,现在需要新增第四台机器D
,由于A-B-C
集群不知道D
机器的信息,因为一开始的配置信息是不会把D
配置进去的,只有D
知道A-B-C
集群,此时如果D
的SID
最大,即使A-B-C
不知道D
机器,D
建立通信对时也可以掌握主动权,和A-B-C
集群的三台机器各自建立通信对;如果D
的SID
小,将无法顺利和A-B-C
集群建立通信对,这个流程后续会分析;
从上述这两点可以得出以下结论:
1、 ZK
是推荐开发者配置SID
时递增,不推荐使用数字间隔的配置方式;
2、 为了让开发者遵循第一点,ZK
为SID
大的机器赋予了更多的主动权,特别是新增机器去融入原有集群的情况,新增的机器只能配置更大的SID
才行,配置更小的SID
将无法融入原集群;
3.4 选举流程
假设现在有三台机器,三台机器的server
配置如下:
server.1=host:port:electionPort
server.3=host:port:electionPort
server.5=host:port:electionPort
3.4.1 流程分析
在选举时,机器A
先回复集群通知,随后机器B
回复集群通知。
其大致选举通信流程图如下:
流程分析如下:
1、 开始Leader
选举时,每台机器都会发送给集群中的其它机器告诉它们选举自己当选Leader
,并把自己的myid
、zxid
和epoch
等值信息放到通知中,以便其它机器收到后作比对;
2、 机器A
、B
和C
都接收到了各自的选举通知,并使用前面所说的选举规则进行过判断这里假设A
先收到B
的通知,先把选举投给了B
,但是后面又收到了C
的通知,最后把投票改投给了C
,如果是先收到C
的通知,那么B
的通知将会被忽略,最终A
都会把票投给C
机器;
3、 B
机器会收到A
和C
的通知,但是A
的通知会被忽略,因为根据FLE
算法的规则A
的权重比B
的小,但是C
的权重比B
要大,因此B
最终会把投票投给机器C
;
4、 此时集群内的三台机器已经收到了全部通知并把票已经给投了,最终算上C
自己的投票,就算有其中一台机器没有回复,C
的投票是肯定超过总数的半数的于是C
将自己的状态改成Leader
,而A
和B
则把自己的状态改成Follower
,修改的依据便是判断超过半数的机器信息myid
是否和本机器相同,相同则是Leader
,否则是Follower
;
3.4.2 思考与总结
单看上述流程可能会像看流水账一样,都看得懂,但感觉都比较死,都是一些约定俗成的流程。但其实如果站在现实角度看,这个选举算法也是很有意思的,比如现实中有以下场景:
现在公司有个新项目需要攻坚,新拉了一个攻坚组,组内只有三个人A、B和C
原来三个人都没当过组长,现在需要这三个人自行协商谁来当组长
可以采取类似FLE
的方式来进行推选:
1、 每个人向其他的人分发一下自己的工作简历,这个动作就类似于流程中的把当选Leader
的投票投给自己;
2、 每个人再根据工作简历判断自己相比其他的人合不合适当选组长,判断的条件有自身技能(类似于SID
)、接手过的项目(类似于zxid
)又或者工作经验(类似于epoch
)等;
3、 如果A
对比完自己和B
、C
后,觉得C
综合来看是三个人中最合适的,A
就会把票投给C
;而B
先对比完A
后觉得自己比A
更有竞争力,于是便忽略A
,随后再与C
对比,最后发现C
更适合,再推举C
当组长,C
同理;
4、 经过上面三个投票对比流程后,最后C
的票数稳定过半,自然当选了组长;
以上面这种方式可能更容易理解到FLE
算法为什么叫快速选举Leader
算法,个人认为其核心思想在于判断条件直观明显,对于不符合当选Leader
的机器交互尽可能少。这样才能在短时间内不需要很多算力性能便可以选举出集群Leader
。
4. 发现选举阶段相关问题
或许看完了上面的发现阶段会觉得自己懂了但好像又没有完全懂,考验对部分知识是否掌握的唯一标准就是答疑解惑。比如我在学完发现阶段后产生了些许疑惑,网上也有选举相关的面试问题,接下来尝试一一解答这些问题。
4.1 新增Follower融入集群
学习ZK
或面试的时候经常会碰到以下这个问题:
如果集群选举完毕后,中途要加入Follower机器参与集群工作,此时如何通信的?
接下来便来解答一下这个问题,还是按上面的流程,将其分为三个部分:
1、 配置说明;
2、 建立通信对;
3、 选举流程;
4.1.1 建立通信对
ZK
集群可支持新增Follower
和Observer
机器角色,新增这两种角色前有个绕不开的流程:和集群机器建立通信对。前面说过只能由SID
大的机器主动向SID
小的机器有个小设计,这个小设计在新增机器的场景就尤为重要。
4.1.1.1 流程分析
假设还是原来的A-B-C
集群,该集群已经选举出了Leader
并正常运行,每个机器的server
配置都如下:
server.2=host:port:electionPort
server.4=host:port:electionPort
server.6=host:port:electionPort
现在需要对集群进行扩容新增两台Follower
机器,两个机器的server
主要配置如下:
# 机器D
server.1=host:port:electionPort
# 机器E
server.7=host:port:electionPort
# 原集群配置
server.2=host:port:electionPort
server.4=host:port:electionPort
server.6=host:port:electionPort
此时启动两台机器,第一阶段连接情况如下图:
SID
最小的D
机器,连接A-B-C
集群的任意一台机器都失败了,而SID
最大的E
机器连接任意一台机器都成功了,通信对建立情况如下:
接下来便是二阶段的触发连接,触发连接情况如下图:
有没有觉得很奇怪,为什么D
机器没有触发建立通信对的情况?再来看下通信对图:
这一看明显不对劲了,只有机器E
成功和现有集群A-B-C
建立起了通信对,而D
则只和机器E
成功建立了通信对,这一现象明显和之前说的流程不一致,按之前的流程来说应该五台机器都会建立彼此的通信对。那么为什么会产生这种现象呢?
4.1.1.2 思考与总结
其核心问题就在于前面提到过的ZK
对SID
大的机器赋予了更多的主动权,相反,SID
小的机器就会很被动。解释如下:
- 机器E:由于E的SID比现有集群机器都要大,因此连接和触发操作都是可以正常完成的。A-B-C集群的server配置没有机器E的信息,但机器E的server配置有A-B-C集群的机器信息,当机器E向A-B-C集群的三台机器发送建立通信对的操作时,由于SID大,因此机器E可以掌握主动权,第一阶段先与server配置的机器建立通信对,随后SID小的机器在第二阶段触发与机器E建立通信对;
- 机器D:由于D的SID在所有的机器中是最小的,因此D的主动权是最差的,无法向其它的机器主动建立通信对。A-B-C机器的server配置中没有D的机器信息,所以A-B-C三台机器无法主动向D发起建立通信对的操作,因此D也无法触发与A-B-C三台机器建立通信对的操作;但E的server配置是有D机器的,因此D可以顺利触发与E建立通信对。
在新增集群机器的这种场景下,该案例更容易证明前面对于集群建立通信对流程的思考总结,在这种逻辑下ZK
集群的SID
一定是递增的,如果把新机器的SID
弄成最小的或者不是最大的,新的机器将无法和现有集群的所有机器建立通信对。假设建立通信对的机器没有超过server数量/2
,那么该机器就永远处于LOOKING
阶段,永远不会正式承担集群的工作。
4.1.2 新增机器选举流程
4.1.2.1 流程分析
既然说明了机器要参与选举,那么机器的角色就一定是Follower
,因为集群如果正常运行,是不会突然变更Leader
的,添加的机器只能是Follower
了。假设现在添加的机器就是前面提到过的D
和E
机器,通信对建立流程按照前面的分析已经完成,现在已经进入了投票流程,其投票流程图如下:
1、 在FLE
算法中,第一步的操作永远都是向server
配置的所有机器发送把票投给自己的消息,无论是同时启动还是后续新增的机器启动;但前提是本机器和其它的机器已经建立了通信对,由于D
只和E
建立了通信对,因此D
只会向E
发送自己的投票消息,而E
则会向其它的四台机器发送自己的投票消息;此时也会发送机器本身的epoch
、zxid
和myid
,但这三个参数在该场景下不会被使用到;
2、 当集群A-B-C
的任意机器接收到E
的投票消息后,由于E
不在A-B-C
的server
配置中,因此A-B-C
集群所有机器将会直接向E
发送本机器已产生的投票信息,而不会做任何逻辑判断,在这一步机器E
将会得知集群Leader
的信息,当先后收到的消息达到或大于server数量/2
时,选举成功,state
成为Follower
;但机器D
由于无法和A-B-C
集群进行通信,因此暂时无法得知集群Leader
的信息,但前面收到了E
的投票信息,D
会暂时选举E
当选Leader
;
3、 由于机器D
除了最开始收到过E
的投票信息后,再也没有收到其它的投票信息,因此D
会再次向server
的所有机器发送投票给E
的消息,实际上只能发给E
,因为只有E
的通信对;当E
收到D
的消息后,将会直接把本机器的Leader
投票发送给D
,D
收到Leader
投票后,会把之前投给E
的票改为投给新的Leader
,但由于只会收到E
的投票信息,投票数量永远不会超过server数量/2=2
,因此机器D
会一直处于LOOKING
状态,重复第三步;
4.1.2.2 思考与总结
可以从上面的流程看出如果把SID
的值设置的比要参与集群的大部分SID
都要小的话就会导致机器完全融入不进去,会一直处于LOOKING
状态,循环往复。更可怕的是D
机器会一直向有通信对的E
发送选举消息,E
机器选举结束后通信对不会销毁,因此会一直接收消息,当消息堆积到100
时就会把前一个消息移除再接收新的,但机器E
还是会一直接收D
的不必要消息。
因此如果集群要加入新的Follower
机器,SID
最好要比现有集群的所有机器都大,这样才能顺利和集群的所有机器建立起通信对,并融入集群分担集群工作压力。
看网上有些文章说当新加入集群的机器成功选举工作后,需要把原集群机器也重启,但实际这样的操作是没必要的,在ZK
集群的配置文件中配置server
信息,目的是为了选举时能让机器和server
中的配置建立通信对,如果新加入的机器严格按照SID
递增的方式配置,那么所有机器是肯定可以顺利全部建立各自的通信对,所以完全无需再修改原集群机器再去重启。当然,把zoo.cfg
配置文件修改成一样的,这个操作是推荐做的,保持机器配置的统一性,方便后续维护。
4.2 新增Observer融入集群
网上有文章会说Observer
不会参与选举,但Observer
机器不参与选举是怎么融入集群的?又是怎么从LOOKING
状态转变为OBSERVING
的呢?“Observer
不参与选举”这种说法是不严谨的,Observer
肯定是通过选举才能融入集群,只是Observer
不具备选举权,“不能参与选举”和“不具备选举权”差别很大。
说到选举核心规则就避不开这三个参数:epoch
、zxid
和myid
,在选举时比对的就是这三个值的大小,当某个机器被集群超过半数的机器选举为Leader
时,那么该机器将会成为Leader
。Observer
不具备选举权就体现在这两个方面:
1、 Observer
在参与选举流程时这三个参数都是Integer.MIN_VALUE
整数的最小值,所以先接收了哪个机器通知就选举那个机器当选Leader
,随后碰到更大的值,再更换投票;
2、 Observer
向其它参与选举的机器发送消息时,由于不具备选举权,其它的机器会直接把自身的投票信息返回给Observer
,Observer
收到后,再更新自身的投票信息;
如果Observer
要融入集群,这种情况相对于Follower
而言算是比较简单的,只需要两步便可以让Observer
机器从LOOKING
状态变成OBSERVING
。假设还是原来的A-B-C
集群,该集群已经选举出了Leader
并正常运行,每个机器的server
配置都如下:
server.2=host:port:electionPort
server.4=host:port:electionPort
server.6=host:port:electionPort
现在需要对集群进行扩容新增1台Observer
机器,两个机器的server
主要配置如下:
# 机器E
server.7=host:port:electionPort:observer
# 原集群配置
server.2=host:port:electionPort
server.4=host:port:electionPort
server.6=host:port:electionPort
配置完上面的配置后启动机器E,流程图如下:
流程是十分简单的,由于E
机器的sid
大,因此是可以准确和A-B-C
集群进行通信的。共两步:
1、 启动选举流程后的通用流程,会向参与选举的机器发送投票给自己的消息;
2、 集群内参与选举的机器判断E
不是participant
,直接向E
回复自身的投票信息;E
机器接收到超过半数的投票信息后,便成功选举出Leader
,同时自身的状态修改为OBSERVING
;
集群正常运行再去新增Observer
的流程是十分简单的,其实就算是Observer
和participant
同时启动,对于Observer
而言,流程还是非常简单,依然只会接收其它机器的投票信息,只是Observer
接收到不同的投票信息后会比较并更改自身的投票信息。
4.3 机器宕机重新选举相关
其实如果前面的那些流程都了解了,相关机器宕机再启动无非就是再走一遍之前的逻辑,机器宕机最大的影响还是在于某个机器宕机后对于整个集群而言,吞吐量和可用性是否有什么变化。接下来分别聊下这三种情况。
4.3.1 Leader宕机
最严重的情况,整个集群在新的Leader
被选举出来前都无法提供服务,也就是整个集群会短暂的不可用。Leader
宕机的选举流程和前面说的发现阶段选举流程基本差不多,只是原来的通信对会销毁再重新创建一次。
只要集群的机器数量是奇数,这种情况是不会发生脑裂的。假设原集群有5
台participant
,然后Leader
宕机了,此时只剩下4
台机器选举,剩下的机器想要当选Leader
需要得票>=3
,所以4
台机器各占2
票这种情况会被下一轮投票给纠正,最后只能有一台新的Leader
机器诞生。
4.3.2 Follower宕机
集群可以正常提供服务,只是集群的吞吐量会受到影响,比如同步数据时,会一直少一个机器进行ack
,变相的减少了吞吐量。假设还是5
台机器集群,最多允许宕机2
台机器,如果集群没有3
台正常进行ack
的机器,那么集群将不可用。即奇数数量的集群最大可允许宕机数为n/2
;
集群宕机数量示例:
1、 总共3
台机器,最多1
台机器宕机;
2、 总共5
台机器,最多2
台机器宕机;
3、 总共7
台机器,最多3
台机器宕机;
4、 总共9
台机器,最多4
台机器宕机;
5、 …;
4.3.3 Observer宕机
仅会影响读操作的吞吐量,对于集群的写操作无影响,甚至从某一种角度来看会提升集群的写操作吞吐量,为何这样说?
Observer
接收写操作大致流程:Observer
接收到写操作后会把写操作发给Leader
,Leader
再和其它的Follower
进行ack
半数确认,最后commit
并同步给集群所有的Follower
机器,并发送INFORM
给Observer
,Observer
再同步本机数据。
所以无论写操作是Observer
接收还是Follower
接收,都会经过Leader
,对于Leader
而言没有任何变化,有Observer
时,Leader
还要向Observer
发送INFORM
消息去同步消息,如果没有Observer
,发送INFORM
消息这一步对于Leader
而言则可以省略,Leader
就能有更多的算力去处理和Follower
的交互及客户端的操作。
所以Observer
不是任何情况都可以无脑新增的,需要对读写操作的比例有个大致的判断才能决定新增Follower
还是Observer
。
但回归现实来说,写操作肯定是占少数的,提升了读操作效率,从整体而言确实就是提升了集群的吞吐量。且新增Observer
是最简单、对集群影响最小的操作,如果要新增Follower
机器,一次性需要新增两台以保证集群数量为奇数,但Observer
可以新增任意台,宕机后对集群的影响也是最小的。