分布式系统之Zookeeper分析

1.Zookeeper介绍


  • 配置文件
tickTime :2000   #发送心跳的时间间隔,单位毫秒
initLimit=10   #从leader服务器上同步数据的最大时间限制
syncLimit=5    #检测是否机器已经下线
dataDir=/tmp/zookeeper  #存储快照文件,默认情况日志也会存储在这,可以配置dataLogDir
clientPort: =2181  #客户端连接服务端的接口

--------------------------------------------------
dataDir=/zookeeper/data_1
dataLogDir=/zookeeper/logs_1
server.1=localhost:2887:3887 
server.2=localhost:2888:3888
server.3=localhost:2889:3889
#server.x中的x和myid中的一致,第一个端口用户Leader和Learner之间的同步,第二个端口用于选举过程中的投票通信
  • 启动集群
> zkServer.sh start {zookeeperUrl}/zookeeper-3.4.12/conf/zoo1.cfg 
> zkServer.sh start {zookeeperUrl}/zookeeper-3.4.12/conf/zoo2.cfg 
> zkServer.sh start {zookeeperUrl}/zookeeper-3.4.12/conf/zoo3.cfg
  • 查看服务状态
> zkServer.sh status {zookeeperUrl}zookeeper-3.4.12/conf/zoo1.cfg 
> zkServer.sh status {zookeeperUrl}zookeeper-3.4.12/conf/zoo2.cfg 
> zkServer.sh status {zookeeperUrl}zookeeper-3.4.12/conf/zoo3.cfg
  • 客户端连上服务器
> zkCli.sh -server localhost:2181
> zkCli.sh -server localhost:2182
> zkCli.sh -server localhost:2183
  • 观察者节点
节点配置中加上:peerType=observer
server列表加上:server.4=localhost:2890:3890:observer
  • 集群的角色术语
领导者(Leader) :负责进行投票的发起和决议,最终更新状态。

跟随者(Follower) : Follower用于接收客户请求并返回客户结果。参与Leader发起的投票。

观察者(observer) : Oberserver可以接收客户端连接,将写请求转发给leader节点。
但是Observer不参加投票过程,只是同步leader的状态。

学习者( Learner ) :和leader进行状态同步的server统称Learner,Follower和Observer都是Learner.
  • Zookeeper中的CAP理论
Zookeeper至少满足了CP,牺牲了可用性,
如果挂掉的是Leader或Follower,都要重新进行选举,在选举过程中,集群是不可用的。
如果挂掉的是Observer,那么对于集群来说并没有影响,集群还是可以用的。

2.Zookeeper详细功能介绍


  • 节点类型
  • 持久节点:节点创建就一直存在,会进行持久化;
  • 临时节点:和客户端的生命周期绑定,不会持久化;
  • 持久顺序节点:有序
  • 临时顺序节点:有序
  • watch监听器
  • 一个Watch事件,当被设置了Watch的数据发生了改变的时候,
  • 服务器将这个改变发送给设置了Watch的客户端(监听器),来进行通知。
  • ACL权限控制

ACL 权限控制,使用:scheme:id:perm 来标识

scheme:

  • world:默认方式,相当于全部都能访问;
  • auth:代表已经认证通过的用户(cli中可以通过addauth digest user:pwd 来添加当前上下文中的授权用户)
  • digest:即用户名:密码这种方式认证,这也是业务系统中最常用的。
  • ip:使用客户端的主机IP作为ACL ID 。

id:

  • anyone、user:pwd、ip地址。

perm:

  • CREATE、READ、WRITE、DELETE、ADMIN 也就是 增、删、改、查、管理权限,这5种权限简写为crwda。
  • 常用命令
创建持久节点    create /path data
 

创建临时节点    create -e /path data

获取节点数据    get /path

设置节点数据    set /path /data

检查节点状态    stat /FirstNode

递归删除节点    rmr /FirstNode、

查看ACL           getAcl /parent

设置ACL           设置setAcl /parent world:anyone:wa

增加ACL的设置 增加授权用户addauth digest zhangsan:12345 ,授予权限setAcl /parent auth:zhangsan:123456:rdwca

  • 节点的状态

cZxid:创建znode的事务ID。

mZxid:最后修改znode的事务ID。

pZxid:最后修改添加或删除子节点的事务ID。

ctime:表示znode创建时间,时间戳。

mtime:表示znode最近修改时间,时间戳。

dataVersion:表示对该znode的数据所做的更改次数。

cversion:这表示对此znode的子节点进行的更改次数。

aclVersion:表示对此znode的ACL进行更改的次数。

ephemeralOwner:如果znode是ephemeral类型节 点,则这是znode所有者的session ID。如果znode不 是ephemeral节点,则该字段设置为零。

dataLength:这是znode数据字段的长度。

numChildren:这表示znode的子节点的数量。

3.客户端和服务端交互


  • NIO

channle、buffer、selector符合反应者设计模式,channle注册到selector中等待事件的发生,

数据的传输依靠channle并且是双向通道Input/Output,数据的读取和写入只能依靠buffer缓冲区来实现

使用了直接内存/堆外内存减少了一次核心态到用户态的拷贝效率高。

  • 只读模式

当服务器集群一半以上的节点挂掉之后,不能进行写操作,但是可以做读操作,这时可以开启只读模式

1.服务器开启只读模式: -Dreadonlymode .enabled=true;

2.客户端调用开启只读模式: zkServer.sh start -r或者new ZooKee(... canBeReadOnly=true);

3.以上两步都开启了只读模式才会生效;

ps:每一个写操作,主服务器都需要获得一半从服务器的确认,才可以进行同步数据;

  • closeSession

创建的临时节点,与客户端的生命周期绑定,进行quit的时候,调用zk.close();

客户端发送一个op.closesession操作给服务端;

服务端,先持久化事务,在deleteNode删除该临时节点;

  • 客户端启动类

ZookeeperMain类初始化Zookeeper,初始化ClientCnxn,初始化SendThread 和 EventThread,初始化socket连接;

命令行解析为request,提交请求request并且包装成packet,加入outgoingqueue队列;

SendThread:连接socket;

发送数据:

从outgoingqueue队列取出数据并发送ConnectRequest给客户端;

将需要等待的结果ConnectResponse放入penddingQueue;

读取数据:

将pendingQueue中的Packet读取出来并且使用服务端返回的结果进行装配

接收watcher事件通知,并且把通知加入到waitingEvents队列中去

如果是同步请求则唤醒线程

如果是异步请求则将Packet加入到waitingEvents队列中

EventThread:从waitingEvents队列中获取数据并且remove掉(只能触发一次);

如果是watcher事件通知,触发绑定的watcher逻辑(自定义的watcher逻辑);

如果异步请求,则调用对应的异步回调函数;

  • 服务端启动类

QuorumPeerMain解析配置进行设置属性,判断进入单机模式 或者 集群模式(根据servers的数量)

单机模式下

ZooKeeperServerMain,从snap快照文件里的内容加载到内存,设置请求处理器RequestProcessor;

ps:如果事务和快照数据不一致,会重新执行事务加载到内存。

创建socket,取出packet解析出request;

请求器处理链会从queue中取出request进行处理(责任链模式);

PrepRequestProcessor:获取客户端的请求,生成txn事务,并且修改节点属性,调用下一个处理器;

SyncRequestProcessor:获取请求,将txn同步到磁盘,并进行快照snapslop,调用下一个处理器;

FinalRequestProcessor:获取请求,触发watcher发送事件(NIOServerCnxn#process),构造response返回给客户端;

集群模式下

QuorumPeer#start,从snap快照文件里的内容加载到内存

ZAB协议

4.领导者选取


  • 建立连接

为了防止机器之间重复建立连接,因此只允许sid大的服务器进行连接。

  • 1,2,3会有组合(2,1)、(3,2)、(3,1)而不允许类似(1,2)这样的出现。
  • 投票的数据结构

每个投票中包含了两个最基本的信息,所推举服务器的SID和ZXID,投票(Vote) :

  • id:被推举的Leader的SID。
  • zxid: 被推举的Leader事务ID。
  • electionEpoch: 逻辑时钟,用来判断多个投票是否在同一轮选举周期中,该值在服务端是一一个自增序列,每次进入新一轮的投票后,都会对该值进行加1操作。
  • peerEpoch: 被推举的Leader的epoch(周期)。
  • state: 当前服务器的状态。

选票管理QuorumCnxManager

  • sendqueue:选票发送队列,用于保存待发送的选票。
  • recvqueue:选票接收队列,用于保存接收到的外部投票。
  • WorkerReceiver:选票接收器。其会不断地从QuorumCnxManager中获取其他服务器发来的选举消息,并将其转换成一个选票,然后保存到recvqueue中, 在选票接收过程中,如果发现该外部选票的选举轮次小于当前服务器的,那么忽略该外部投票,同时立即发送自己的内部投票。
  • WorkerSender:选票发送器,不断地从sendqueue中获取待发送的选票,并将其传递到QuorumCnxManager中。

一旦连接建立,就会根据远程服务器的SID来创建相应的消息发送器SendWorker和消息接收器RecvWorker,并启动。

  • 服务器状态

服务器具有四种状态,分别是LOOKING、FOLLOWING、LEADING、OBSERVING。

  • LOOKING:寻找Leader状态。当服务器处于该状态时,它会认为当前集群中没有Leader,需要进入Leader选举状态。
  • FOLLOWING:跟随者状态。表明当前服务器角色是Follower。
  • LEADING:领导者状态。表明当前服务器角色是Leader。
  • OBSERVING:观察者状态。表明当前服务器角色是Observer。
  • 启动时的领导者选举
  • 最开始自己先投给自己,然后告知其他服务器;
  • 各自服务器,首先判断选举的轮数,在依次比较zxid事务号、myid服务器号;
  • 统计投票,不断的判断是否有服务器可以成为leader;
  • 选举成功改变各自服务器状态即可;

ps:主要是依靠zxid和myid进行选举;

  • 运行时的领导者选举

当follow挂掉后

  • follow和leader会有心跳检测,leader会检测当票数少于一半就会进行shutdown,
  • 然后设置自己为looking状态,其他的follow也会为looking状态,然后重新进行选举;

当leader挂掉后,一定会重新进行选举;

ps:其他服务器会将自己的状态改变为looking,并且开启只读状态;

新增一台服务器后,以当前leader为准;

  • 选举流程图

5.基本应用


  • 分布式锁思路

利用顺序临时节点和watch机制来进行实现;

当前节点只能监听前一个节点,当前一个节点不存在就可以加锁,否则会一直监听;

  • 分布式配置中心

利用watch注册监听器,当增加、更新、删除事件发生可以及时更新缓存,保证配置可以及时更新;

发布了129 篇原创文章 · 获赞 29 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/taka_is_beauty/article/details/104434103