zookeeper杂记

      最近在看关于ZK的博客,有一些感觉很不错的内容。这篇博客是我认为比较重要的内容我把它们收集起来,便于以后看:

zookeeper的一些处理原则

zookeeper的一些处理原则

1.可靠 delivery

	如果消息m被一台服务器delivered,它会被所有服务器delivered

2.完全有序

	如果消息a在一台服务器上先于消息b被delivered,在所有服务器上都保持这个顺序

3.因果顺序(causual order)

	消息的发送顺序决定了消息的顺序



-- zookeepker使用TCP,下列几个特性依赖TCP的特性

(与同类系统容忍丢包和乱序不同,zookeepker假定所有servers建立在 p2p的FIFO通道上)3


4.有序delivery(Ordered delivery)

5.FIFO channel一旦关闭,就不能发送任何信息

6.发送消息包括两个阶段

  * Leader activation :本阶段会推选一个leader,并建立系统状态和次序,准备好发送proposal

  * Active messaging :本阶段leader会接收message,并协调系统中message的传送

7.Leader activation

  包括Leader election,其有两个算法,LeaderElection和FastLeaderElection(AuthFastLeaderElection是其变种,是一种简单的授权以防止IP欺骗的election方式)	

  但ZooKeeper messaging不关注election算法,而

  * leader获取到所有followers中的最大zxid (必要条件)

  * 一定量的servers已经follow这个leader 

  在election过程中或完成后,有较多服务器断开,则会重新进行election

   所有follower会连接到leader

8.Active Messaging 

   * leader以FIFO方式给所有Followers发送message,Followers按顺序存储和执行

   * 当Followers收到message后,要返回一个ack给leader

   * 当leader收到quorum Followers的 ack后,会发送一个COMMIT


zxid      ZooKeeper Transaction id,所有proposal都有唯一标识其和其顺序的zxid

	  目前为64位表示一个zxid(epoch, count).,高32位为epoch,低32位为counter

	  epoch与一个leader关联,新leader有新epoch号

packet  由FIFO channel发送的字节数据

proposal   一种约定(在所有zookeepker服务器之间),一般带有message(NEW_LEADER类型的不带message)
message   原子广播包,需放入 proposal或agreed才能发送

quorum   法定人数,zookeeper 中指有些操作必须有一定数量的followers,才能执行

 

 

我们说性能,可以从两个方面去考虑:写入的性能与读取的性能。

   由于ZooKeeper的写入首先需要通过Leader,然后这个写入的消息需要传播到半数
以上的Fellower通过才能完成整个写入。所以整个集群写入的性能无法通过增加
服务器的数量达到目的,相反,整个集群中Fellower数量越多,整个集群写入的
性能越差。

   ZooKeeper集群中的每一台服务器都可以提供数据的读取服务,所以整个集群中服
务器的数量越多,读取的性能就越好。但是Fellower增加又会降低整个集群的写
入性能。为了避免这个问题,可以将ZooKeeper集群中部分服务器指
定为Observer。

 

 

***********************************************

 

   详细一点的ZK的java的api说明实例:

// 创建一个Zookeeper实例,第一个参数为目标服务器地址和端口,第二个参数为Session超时时间,第三个为节点变化时的回调方法
ZooKeeper zk  =   new  ZooKeeper( " 127.0.0.1:2181 " ,  500000 , new  Watcher()  {
            //  监控所有被触发的事件
              public   void  process(WatchedEvent event)  {
            // dosomething
           }
      } );
// 创建一个节点root,数据是mydata,不进行ACL权限控制,节点为永久性的(即客户端shutdown了也不会消失)
zk.create( " /root " ,  " mydata " .getBytes(),Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);

// 在root下面创建一个childone znode,数据为childone,不进行ACL权限控制,节点为永久性的
zk.create( " /root/childone " , " childone " .getBytes(), Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);

// 取得/root节点下的子节点名称,返回List<String>
zk.getChildren( " /root " , true );

// 取得/root/childone节点下的数据,返回byte[]
zk.getData( " /root/childone " ,  true ,  null );

// 修改节点/root/childone下的数据,第三个参数为版本,如果是-1,那会无视被修改的数据版本,直接改掉
zk.setData( " /root/childone " , " childonemodify " .getBytes(),  - 1 );

// 删除/root/childone这个节点,第二个参数为版本,-1的话直接删除,无视版本
zk.delete( " /root/childone " ,  - 1 );
      
// 关闭session
zk.close();

 

***********************************************

Zookeeper 的主流应用场景实现思路(除去官方示例)

(1) 配置管理
集中式的配置管理在应用集群中是非常常见的,一般商业公司内部都会实现一套集中的配置管理中心,应对不同的应用集群对于共享各自配置的需求,并且在配置变更时能够通知到集群中的每一个机器。

Zookeeper
很容易实现这种集中式的配置管理,比如将 APP1 的所有配置配置到 /APP1 znode 下, APP1 所有机器一启动就对 /APP1 这个节点进行监控 (zk.exist( "/APP1" ,true)), 并且实现回调方法 Watcher ,那么在 zookeeper /APP1 znode 节点下数据发生变化的时候,每个机器都会收到通知, Watcher 方法将会被执行,那么应用再取下数据即可 (zk.getData( "/APP1",false,null ));

以上这个例子只是简单的粗颗粒度配置监控,细颗粒度的数据可以进行分层级监控,这一切都是可以设计和控制的。
    
(2) 集群管理
应用集群中,我们常常需要让每一个机器知道集群中(或依赖的其他某一个集群)哪些机器是活着的,并且在集群机器因为宕机,网络断链等原因能够不在人工介入的情况下迅速通知到每一个机器。

Zookeeper
同样很容易实现这个功能,比如我在 zookeeper 服务器端有一个 znode /APP1SERVERS, 那么集群中每一个机器启动的时候都去这个节点下创建一个 EPHEMERAL 类型的节点,比如 server1 创建 /APP1SERVERS/SERVER1( 可以使用 ip, 保证不重复 ) server2 创建 /APP1SERVERS/SERVER2 ,然后 SERVER1 SERVER2 watch /APP1SERVERS 这个父节点,那么也就是这个父节点下数据或者子节点变化都会通知对该节点进行 watch 的客户端。因为 EPHEMERAL 类型节点有一个很重要的特性,就是客户端和服务器端连接断掉或者 session 过期就会使节点消失,那么在某一个机器挂掉或者断链的时候,其对应的节点就会消失,然后集群中所有对 /APP1SERVERS 进行 watch 的客户端都会收到通知,然后取得最新列表即可。

另外有一个应用场景就是集群选
master, 一旦 master 挂掉能够马上能从 slave 中选出一个 master, 实现步骤和前者一样,只是机器在启动的时候在 APP1SERVERS 创建的节点类型变为 EPHEMERAL_SEQUENTIAL 类型,这样每个节点会自动被编号,例如
          

zk.create( " /testRootPath/testChildPath1 " , " 1 " .getBytes(), Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL_SEQUENTIAL);
        
zk.create(
" /testRootPath/testChildPath2 " , " 2 " .getBytes(), Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL_SEQUENTIAL);
        
zk.create(
" /testRootPath/testChildPath3 " , " 3 " .getBytes(), Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL_SEQUENTIAL);
        
//  创建一个子目录节点
zk.create( " /testRootPath/testChildPath4 " , " 4 " .getBytes(), Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL_SEQUENTIAL);

System.out.println(zk.getChildren(
" /testRootPath " false ));

 打印结果: [testChildPath10000000000, testChildPath20000000001, testChildPath40000000003, testChildPath30000000002]

zk.create( " /testRootPath " " testRootData " .getBytes(),Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);

//  创建一个子目录节点
zk.create( " /testRootPath/testChildPath1 " , " 1 " .getBytes(), Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL);
        
zk.create(
" /testRootPath/testChildPath2 " , " 2 " .getBytes(), Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL);
        
zk.create(
" /testRootPath/testChildPath3 " , " 3 " .getBytes(), Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL);
        
//  创建一个子目录节点
zk.create( " /testRootPath/testChildPath4 " , " 4 " .getBytes(), Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL);

System.out.println(zk.getChildren(
" /testRootPath " false ));
打印结果: [testChildPath2, testChildPath1, testChildPath4, testChildPath3]

我们默认规定编号最小的为
master, 所以当我们对 /APP1SERVERS 节点做监控的时候,得到服务器列表,只要所有集群机器逻辑认为最小编号节点为 master ,那么 master 就被选出,而这个 master 宕机的时候,相应的 znode 会消失,然后新的服务器列表就被推送到客户端,然后每个节点逻辑认为最小编号节点为 master ,这样就做到动态 master 选举。
 

***********************************************

ZookeeperServer工作流程

3.1.1 主线程的工作:

1. 刚开始时各个Server处于一个平等的状态peer

2. 主线程加载配置后启动。

3. 主线程启动QuorumPeer线程,该线程负责管理多数协议(Quorum),并根据表决结果进行角色的状态转换。

4. 然后主线程等待QuorumPeer线程。

3.1.2  QuorumPeer线程

1. 首先会从磁盘恢复zkdatabase(内存数据库),并进行快照回复。

2. 然后启动server的通信线程,准备接收client的请求。

3. 紧接着该线程进行选举leader准备,选择选举算法,启动response线程(根据自身状态)向其他server回复推荐的leaer。

4. 刚开始的时候server都处于looking状态,进行选举根据选举结果设置自己的状态和角色。

3.1.3 quorumPeer有几种状态

1. Looking: 寻找状态,这个状态不知道谁是leader,会发起leader选举

2. Observing: 观察状态,这时候observer会观察leader是否有改变,然后同步leader的状态

3. Following:  跟随状态,接收leader的proposal ,进行投票。并和leader进行状态同步

4. Leading:    领导状态,对Follower的投票进行决议,将状态和follower进行同步

当一个Server发现选举的结果自己是Leader把自己的状态改成Leading,如果Server推荐了其他人为Server它将自己的状 态改成Following。做Leader的server如果发现拥有的follower少于半数时,它重新进入looking状态,重新进行 leader选举过程。(Observing状态是根据配置设置的)。

3.2 Leader的工作流程:

3.2.1 Leader主线程:

1.首先leader开始恢复数据和清除session

启动zk实例,建立请求处理链(Leader的请求处理 链):PrepRequestProcessor->ProposalRequestProcessor->CommitProcessor->Leader.ToBeAppliedRequestProcessor ->FinalRequestProcessor

2.得到一个新的epoch,标识一个新的leader , 并获得最大zxid(方便进行数据同步)

3.建立一个学习者接受线程(来接受新的followers的连接,follower连接后确定followers的zxvid号,来确定是需要对follower进行什么同步措施,比如是差异同步(diff),还是截断(truncate)同步,还是快照同步)

4. 向follower建立一个握手过程leader->follower NEWLEADER消息,并等待直到多数server发送了ack

5. Leader不断的查看已经同步了的follower数量,如果同步数量少于半数,则回到looking状态重新进行leaderElection过程,否则继续step5.

3.2.2 LearnerCnxAcceptor线程

1.该线程监听Learner的连接

2.接受Learner请求,并为每个Learner创建一个LearnerHandler来服务

3.2.3 LearnerHandler线程的服务流程

1.检查server来的第一个包是否为follower.info或者observer.info,如果不是则无法建立握手。

2. 得到Learner的zxvid,对比自身的zxvid,确定同步点

3.和Learner建立第二次握手,向Learner发送NEWLEADER消息

4.与server进行数据同步。

5.同步结束,知会server同步已经ok,可以接收client的请求。

6. 不断读取follower消息判断消息类型

i.           如果是LEADER.ACK,记录follower的ack消息,超过半数ack,将proposal提交(Commit)

ii.         如果是LEADER.PING,则维持session(延长session失效时间)

iii.        如果是LEADER.REQEST,则将request放入请求链进行处理–Leader写请求发起proposal,然后根据follower回复的结 果来确定是否commit的。最后由FinallRequestProcessor来实际进行持久化,并回复信息给相应的response给server

3.3 Follower的工作流程:

1.启动zk实例,建立请求处理链:FollowerRequestProcessor->CommitProcessor->FinalProcessor

2.follower首先会连接leader,并将zxid和id发给leader

3.接收NEWLEADER消息,完成握手过程。

4.同leader进行状态同步

5.完成同步后,follower可以接收client的连接

5.接收到client的请求,根据请求类型

l         对于写操作, FollowerRequestProcessor会将该操作作为LEADER.REQEST发给LEADER由LEADER发起投票。

l         对于读操作,则通过请求处理链的最后一环FinalProcessor将结果返回给客户端

对于observer的流程不再赘述,observer流程和Follower的唯一不同的地方就是observer不会参加leader发起的投票。

***********************************************

Zookeeper server工作原理

Zookeeper的核心是原子广播,这个机制保证了各个server之间的同步。实现这个机制的协议叫做Zab协议。Zab协议有两种模式,它 们分别是恢复模式和广播模式。当服务启动或者在领导者崩溃后,Zab就进入了恢复模式,当领导者被选举出来,且大多数server的完成了和leader 的状态同步以后,恢复模式就结束了。状态同步保证了leader和server具有相同的系统状态。

一旦leader已经和多数的follower进行了状态同步后,他就可以开始广播消息了,即进入广播状态。这时候当一个server加入 zookeeper服务中,它会在恢复模式下启动,发现leader,并和leader进行状态同步。待到同步结束,它也参与消息广播。 Zookeeper服务一直维持在Broadcast状态,直到leader崩溃了或者leader失去了大部分的followers支持。

Broadcast模式极其类似于分布式事务中的2pc(two-phrase commit 两阶段提交):即leader提起一个决议,由followers进行投票,leader对投票结果进行计算决定是否通过该决议,如果通过执行该决议(事务),否则什么也不做。

广播模式需要保证proposal被按顺序处理,因此zk采用了递增的事务id号(zxid)来保证。所有的提议(proposal)都在被提出 的时候加上了zxid。实现中zxid是一个64为的数字,它高32位是epoch用来标识leader关系是否改变,每次一个leader被选出来,它 都会有一个新的epoch。低32位是个递增计数。

当leader崩溃或者leader失去大多数的follower,这时候zk进入恢复模式,恢复模式需要重新选举出一个新的leader,让所有的server都恢复到一个正确的状态。

首先看一下选举的过程,zk的实现中用了基于paxos算法(主要是fastpaxos)的实现。具体如下:

1.每个Server启动以后都询问其它的Server它要投票给谁。

2.对于其他server的询问,server每次根据自己的状态都回复自己推荐的leader的id和上一次处理事务的zxid(系统启动时每个server都会推荐自己)

3.收到所有Server回复以后,就计算出zxid最大的哪个Server,并将这个Server相关信息设置成下一次要投票的Server。

4.计算这过程中获得票数最多的的sever为获胜者,如果获胜者的票数超过半数,则改server被选为leader。否则,继续这个过程,直到leader被选举出来。

此外恢复模式下,如果是重新刚从崩溃状态恢复的或者刚启动的的server还会从磁盘快照中恢复数据和会话信息。(zk会记录事务日志并定期进行快照,方便在恢复时进行状态恢复)

选完leader以后,zk就进入状态同步过程。

1.leader就会开始等待server连接

2.Follower连接leader,将最大的zxid发送给leader

3.Leader根据follower的zxid确定同步点

4.完成同步后通知follower 已经成为uptodate状态

5.Follower收到uptodate消息后,又可以重新接受client的请求进行服务了。

***********************************************

猜你喜欢

转载自wjy320.iteye.com/blog/2084203