上一篇 咱们提到了基于zookeeper下的分布式锁的简单实现,咱们分别经过节点不可重名+watch机制(不推荐),取号 + 最小号取lock + watch的原理来各实现了一把分布式锁,第二种相似于去银行办理业务的先领号,等叫号的一种形式。java
咱们如今已经知道,zookeeper可以帮助咱们解决的是分布式下应用进程间的协同问题,它简单,有序,可复制且快速(基础篇一 的2 - ④ 小节),核心有数据模型,会话机制和watch监听机制(基础篇一 的3 - ① ~ ④ 小节),zookeeper还有其对应的丰富的第三方客户端方便咱们进行开发,在至此zookeeper入门的前两篇就告一段落了。linux
从零开始的高并发(一)--- zookeeper的基础概念算法
从零开始的高并发(二)--- Zookeeper实现分布式锁数据库
本来应该和leader选举分P说的,可是由于集群搭建过程相对不复杂,主要的内容其实仍是paxos算法的那块,因此咱们就合成1P来讲吧,内容看起来会较多,实际上是截图比较多而已,但愿你们可以耐心安全
刚刚咱们所提到的,zookeeper的可复制性就是zookeeper集群的特色,为了提供可靠的zookeeper服务,咱们须要集群的支持,这个集群还有个特色,就是这个集群中只要有大多数的节点准备好了,就可使用这项服务。容错集群设置至少须要3台服务器,强烈建议使用奇数个服务器(为啥推荐奇数看到后面就知道啦),而后建议每一个服务都运行在单独的机器上。服务器
这里咱们演示的是伪集群的方式,不过一台机器作伪集群和几台机器分别作集群须要注意的细节确定是更多的。能够说伪集群若是能配置稳当的话,到了真集群下配置就会更加简单明了。网络
须要注意的配置
1.initLimit:
集群中的小弟follower服务器,和领导leader服务器之间完成初始化同步链接时的能接受的最大心跳数,
此时若是集群环境很是大,同步数据的时间较长,这个参数咱们须要进行适当调整.
注意,在zookeeper中,任什么时候间的设置都是以ticktime的倍数来进行定义,若是咱们设置initLimit=2.那咱们能接受的最大时间就是ticktime*2
2.syncLimit:
follower和leader之间请求和应答能接受的最大心跳数
集群节点的配置
server.id = host:port:port
id:经过在各自的dataDir目录下建立一个myId的文件来为每台机器赋予一个服务器id,这个id咱们通常用基数数字表示
两个port:第一个follower用来链接到leader,第二个用来选举leader
复制代码
这里咱们打开zookeeper根目录下的conf文件夹,找到zoo.cfg文件,并发
zookeeper官方建议咱们添加上DataLogDir来存放事务日志。若是只有dataDir目录而没有dataLogDir目录的话,它会把运行日志和事务日志都放在dataDir的那个目录上面去,事务日志和运行日志有什么区别?这个事务日志就至关于咱们zookeeper的数据库,须要使用到读取和恢复等功能的时候,它就须要这么一个数据库来恢复。框架
咱们的zookeeper集群开启的话会有3个端口号,第一个端口号是客户端去链接它的端口号。clientPort是指咱们的java客户端去链接它所使用的端口号,maven
第二个是小弟follower和leader去同步数据所用到的端口号, 第三个就是咱们去进行leader选举时所用到的端口号。在末尾加上咱们的节点配置,192.168.1.104使用的就是本机ip了,这里下方1001就是小弟和leader同步数据所用的端口号,那万一咱们的leader崩溃,挂掉了,咱们就须要从新去选举,此时咱们就须要用到第三个端口,也就是2001端口号
此时我copy了两份zookeeper出来作伪集群
打开 zookeeper-3.5.2-alpha 02/conf/setting.xml,也就是第二个zookeeper的setting.xml
同理打开zookeeper-3.5.2-alpha 03/conf/setting.xml,也就是第三个zookeeper的setting.xml
在开启3个集群以前,咱们还须要去进行一个操做,看到咱们的dataDir了吗,咱们要去到这个指定目录下建立一个myid文件,前面也提到了myid是一个数字,咱们就用1,2,3做为咱们zookeeper01,02,03的id便可。
注意,myid是一行只包含机器id的文本,id在集群中必须是惟一的,其值应该在1~255之间,咱们这里用的词为应该,而不是限制,若是真有一天超过了限制数集群超过了255,这个zookeeper集群就过大了,响应会很是慢。还有就是文件内不要有空格或者其余字符,只须要打上1,2,3···,便可。
这里会致使的报错可能有两个
由于zookeeper运行报错的话会闪退,咱们能够找到zkServer.cmd,右键编辑,在末尾加上pause来查看报错信息
一个是myid没放对目录,须要放在咱们的dataDir的路径下
二是myid里面除了数字还有其余的字符,这里我刻意在1前面敲了一个空格
这些错误都须要尽可能去避免,不要由于本身操做太快没注意一些小的细节。
以个人zookeeper01为例,把myid文件放在data文件夹下便可,这个目录结构最好仍是手动建立
不只仅是启动集群,后面咱们将要说到的选举和节点间的通讯都是要使用到myid的
注意,在多节点未启动成功,好比仅仅只启动了一个节点时,咱们启动可能会报错,好比如下这个Connection Exception:Connection refused:connect,这是由于咱们和02,03之间未能创建通讯致使的,因此此时咱们只须要把其余的节点都正常开启便可
集群中的全部节点均可以提供服务,客户端链接时,链接串中能够指定多个或者所有集群节点的链接地址,当一个节点链接不通时,客户端将自动切换另外一个节点。指定地址的时候咱们使用英文输入下的逗号,来进行分割
JMX(Java Management Extensions) --- Java管理扩展,是一个为应用程序,设备,系统等1植入管理功能的框架
在咱们的jdk的bin目录下运行jconsole.exe
打开jconsole以后,咱们看到了咱们正在运行的几个服务,好比下图中我正在跑的服务有jconsole自己,idea的maven,还有3个咱们正在开启的zookeeper服务
咱们随便选择一个点开,选择使用不安全的链接便可,此时咱们切换到Mbean视图
这里ReplicatedServer可复制的服务中,id1表明咱们设置的myid的值为1
属性中咱们看到一个数值QuorumSize表明的是集群中有多少台服务
此时它是做为小弟follower,咱们也能够尝试打开另外的节点,好比个人第二个节点就是做为leader节点
工具大概有一个了解就差很少了,咱们终于开始今天咱们的主题,集群的leader选举
每台服务器均可能成为leader,那到底leader是如何被选举出来的呢?
这里尝试去翻译了一下
P1a:提议者选择一个提案编号n,给大多数接收者发送一个带有编号n的提案预请求
P1b:若是接收者收到了编号n的预请求,n大于前面已经响应过的提案预请求编号,这时接收者作出响应,
承诺再也不接收编号比n小的提案预请求,而且在响应中会带上本身曾接收过的最高编号提案
P2a:若是提议者接收到了大多数接收者对于n的提案响应,这时提议者会给这些接收者的每个服务发送接受请求,
此接受请求的内容为编号n的提案并带上一个value,这个value是如何取值的呢,
是取接收者响应中最高编号所对应的值,若是响应中根本不存在提议值,则能够任意取值
P2b:若是接收者接收到一个编号为n的接收请求,它接收该提案,但若是它将对大于编号n的预请求作出响应,则不接受n的提案
复制代码
ps:拜占庭将军问题不存在于zookeeper的集群中
首先咱们必须清楚paxos算法的两个约束
1.最终只有一个提议会被选择,只有被选择的提议值才会被learner去记录
2.最终会有一个提议生效,paxos协议可以让proposer发送的提议朝着能被大多数Acceptor接受的那个提议靠拢,
保证了最后结果的生成(少数服从多数原则)
复制代码
咱们如今开始进行流程分析
提议者会发送的两种消息:prepare是预请求,accept是接收请求
提议者的提议请求构成一个(n,v)结构,n为序号,v为提议值
简单说明一下,这里提议者A向接收者C,D,E发送了本身的一个预请求prepare request(n=2,v=5),C,D正常接收,可是E断线,没有第一时间接收到
提议者B向C,D,E发送本身的预请求prepare request(n=4,v=8),此时E已经恢复通信。
这里咱们如何理解?首先咱们第一阶段中C,D都是正常接收到了提议者A的预请求的,此时它们在先前没有接收过任何的预请求(no previous),因此它们会对提议者A的请求做出应答(prepare response),这里对应的概念就是P2b:若是接收者接收到一个编号为n的接收请求,它接收该提案
因此此时它们对提议者A做出它们的应答,就是设置它们当前收到的提议就是(n=2,v=5),并向A做出承诺,再也不接收序号小于2的提议请求,这里对应的就是P1b:若是接收者收到了编号n的预请求,n大于前面已经响应过的提案预请求编号,这里由于先前根本就没人请求过啊,因此前面响应过的预请求编号就是0,知足条件后,这时接收者C,D作出响应,承诺再也不接收编号比n小的提案预请求,因此而且在响应中会带上本身曾接收过的最高编号提案,也就是它们接收到的(n=2,v=5)
此时接收者E是接收到了提议者B的提案,也是对应P1b和P2b,承诺再也不接收比4小的编号提案,并把本身的曾接收的最高编号提案设置为(n=4,v=8)
随后咱们的C,D接收者也会接收到提议者B的提议请求,先前C,D的提案为(n=2,v=5),此时因为B提议(n=4,v=8)了,4>2,因此它们会给提议者B也发送一个提议响应,表示它们曾经提案过(n=2,v=8),以后它们本身进行修改,把提案修改成(n=4,v=5),而且再承诺再也不接收n小于4的提案请求。这里v为何值为5以后的阶段会解释
接收者E由于是先接收了提议者B的提案(n=4,v=8),因此以后再接收提议者A的提案(n=2,v=5)时,4>2,因此直接无视掉A的提案,也不对A做出回复
此时proposerB收到了超过半数Acceptor所发的提案响应,便会对全部的接收者都发送一个接收请求
提议者A在预请求第二阶段收到两个提议响应以后会给C,D发送它的提案接受请求(n=2,v=5),可如今C,D明显已经再也不是它的人了,因此C,D会无情抛弃它发送的接受请求
提议者B收到C,D的响应(n=2,v=5)的时候,会发送一个接收请求(n=4,v=5),为何会是本身的编号4可是又取5做为提案值呢,由于这里的5是众望所归,是C,D它们响应回来的数字中最大的,请看下面咱们接收第一阶段的图,要明白,决定最终提案值的并非接收者接收到的你的提议值,而是做为提议者自身所接收到的最大编号对应值,请注意P2a中对于paxos算法的描述,取接收者响应中最高编号所对应的值
此时learner才真正地参与进来,在通过接受第二阶段后,proposerB会把它得出来的(n=4,v=5)发送给全部的它能联系到的接收者,每一个人都接收完成以后,此时就算A再发起投票,由于它能接收回来的响应中,确定编号最大对应的v值为5,因此5就是这个算法的最终结果,没法再更改了。此时学习者就会去进行同步
对选举leader的要求:
选出的leader节点上要持有最高zxid
过半数节点赞成
复制代码
内置实现的选举算法
LeaderElection
FastLeaderElection(默认的)
AuthFastLeaderElection
复制代码
服务器id---myid
事务id---服务器中存放的最大zxid
逻辑时钟---发起的投票轮数计数
选举状态:
LOOKING:竞选状态
FOLLOWING:跟随状态,同步leader状态,参与投票
OBSERVING:管擦状态,同步leader状态,不参与投票
LEADING:领导者状态
复制代码
选举算法的步骤
1.每一个服务实例均发起选举本身为leader的投票
2.其余服务实例收到投票邀请时,比较发起者的数据事务id是否比本身最新的事务ID大,大则给它投一票,小则不投票,相等则比较发起者的服务器ID,大则投票给它
3.发起者收到你们的投票反馈后,看投票数(包括本身的票数)是否大于集群的半数,大于则成为leader,未超过半数且leader未选出,则再次发起投票
复制代码
如今咱们来说一下,刚刚咱们搭建好的集群是如何选举出节点2为leader的
首先咱们节点一先被运行起来,由于没人和它联系,因此当第二个节点启动的时候,第二个节点也给本身发起一个投票,此时第一个节点把本身的信息广播出去给其余的节点,可是此时第三个节点并无启动起来,因此它只能广播给第二台,而后第二个节点的广播也发送给第一个节点,此时它们各自都持有节点1,2的信息,此时它们会先比较事务id,这时,可能初期这两个节点的事务id都为0,那都为0的状况下是怎样的呢,就再比较服务器id,这时咱们的节点1的myid为1,节点2的myid为2,因此节点1就会投票给节点2,此时节点2就是两票,节点1是本身的一票
此时咱们的节点3的服务也起来了,此时它会接收到节点1和节点2的广播消息,它本身也会给节点1和节点2发送本身想要当leader的广播,但是,它会发现,此时节点2已经有了两票了,因此它就只能接受节点2为leader的结果。
稍微往下到节点2启动的状态
最后渠道节点3启动的状态
也是扯了很多东西,简单再回顾一下
集群搭建必定要注意myid的存放位置和编辑时候别手快带上了一些没法识别的字符,其他也没什么好说的
paxos算法比较晦涩难懂,也是不敢说本身有多深刻的研究,若是讲的不稳当或者有另外想法的,你们能够给我留言,会进行改进
zookeeper的选举其实不算很难理解,若是要测试的话,只须要在搭建集群那里多加几个集群,再经过jconsole或者运行日志来查看就能测试不一样的结果,在网络波动大的时候也会有其余的改变
下一篇:从零开始的高并发(四)--- Zookeeper的经典应用场景