ZooKeeper初探

 以下文字大部分翻译自ZooKeeper官方文档,然后进行了知识归类,如有错误,欢迎指正

一、ZooKeeper部署

1、集群数目

如果你想构建可靠的集群系统,至少需要3zookeeper服务节点。我们推荐在线生产环境使用5台服务器;这样你可以停掉一台服务器(例如升级),并且在剩下的服务器中某一台由于未知原因宕机后仍然可以提供服务。

增加zookeeper服务器的数目,会造成写性能下降,以及读性能的大幅上升。写性能下降的原因是每个写操作都需要至少半数的节点投票确认。

为解决上述问题,zookeeper有一类Observer节点。Observer是非投票节点,它只是听从投票的结果。除了这个简单的差异,ObserverFollower的功能是一样的:客户端可以连接ObserverFollower,并向他们发送读和写的请求。Observers会像Followers一样把请求转发给Leader,但是他们只是简单的等待投票的结果。因此,我们可以随意的增加Observers的数量,而不用担心会影响投票的性能。

2、配置参数详解

以下为常用参数,配置在conf/zoo.cfg中,高级参数请参考zookeeper管理员手册http://zookeeper.apache.org/doc/trunk/zookeeperAdmin.html

示例:

tickTime=2000

initLimit=10

syncLimit=5

dataDir=H:/ZooKeeper/zookepper_data/node1

clientPort=2181

server.1=localhost:2883:3883

server.2=localhost:2884:3884

server.3=localhost:2885:3885

server.4=localhost:2886:3886

server.5=localhost:2887:3887

tickTime

以毫秒为单位的基本时间单位。用来测试心跳;最小的会话超时时间是tickTime的两倍。

dataDir

内存数据库快照的存放位置以及事务日志的默认存放位置(除非指定了其他位置)。

clientPort

监听客户端连接的端口

initLimit 

集群中的Server连接到Leader的超时时间。

syncLimit 

集群中的Server可以脱离Leader的最长时间。

3、单节点部署注意事项

不一致的服务器列表

客户端使用的ZooKeeper服务器列表必须和每个ZooKeeper服务器配置的列表一致。如果客户端的列表是真实列表的子集,可能还可以正常工作;但是如果客户端列表是不同ZooKeeper集群的服务器时,就会有很多奇怪的现象。并且,集群中每个服务器的配置文件中的服务器列表也应该彼此相同。

不正确的事务日志存放

ZooKeeper影响性能最大的地方是事务日志。ZooKeeper在返回响应之前需要同步事务到磁盘中。一个专用的事务日志存放设备是获取高性能必不可少的。

不正确的Java堆大小

设置Java最大堆内存时要特别小心。应该避免出现ZooKeeper交换至磁盘的情况。一切都是有序的,所以如果某个正在处理的请求被交换至磁盘,那么队列中的所有请求可能都要这么多。所以DON’T SWAP

估算的时候应该持保守态度:4G RAM,不要设置Java最大堆内存到6G甚至4G。举例来说,对于一个4G机器你可以使用3G的堆内存,因为操作系统和缓存也需要内存。估算堆大小最好也是推荐的方式是进行测试。

4、Zookeeper以及调度管理中心升级

全部停掉升级或轮流停止升级。

Zookeeper集群(3台)在停掉两台机器后,是无法提供服务的,只允许停掉一台机器;调度管理中心只要保证一台机器正常,即可提供调度服务。

二、集成zookeeper二次开发

1、调度管理中心的选举算法

目的:选择调度管理中心的Leader,使用zookeeper提供的接口。

角色:调度管理中心作为zookeeper集群的客户端。

避免:羊群效应;事件发生后,唤醒了大量在其上监听的客户端,而实际上只有部分客户端能够处理该事件(盲目唤醒了不必要的客户端);避免方法,不是所有节点都监视leader是否死亡,而是监视前一个比sequence自己小的客户端。

2、节点故障处理机制

    2.1 连接丢失

CONNECTION_LOSS:表示客户端和服务器之间的连接断开。它不能代表请求失败了(例如,create请求到达server之后,server成功创建节点,但是在返回响应前连接断开)。zookeeper无法区分请求没有发出还是响应没有收到的情况,因此它统一返回CONNECTION_LOSS应用程序需要负责检测这种情况。

处理方法:

①请求管理:程序应该判断请求是成功还是需要重试,通常通过应用程序相关的方式。例如查看要创建的节点是否已经存在或者检查要修改的节点值。

②连接管理:当客户端的连接状态变为CONNECTION_LOSS后,会查找指定的服务器列表(构造zookeeper对象时传入的connectString)进行重连。在会话超时之前重连成功,该客户端的连接状态变为CONNECTED;在会话超时之后重连成功,状态变为SESSION_EXPIRED

结论:只有在收到会话超时的通知后,才去创建一个新的连接。

    2.2 会话超时

定义及现象:会话超时发生在集群在指定的会话超时周期内没有收到客户端的回复(即无心跳)。会话超时后,集群删除该会话的所有临时节点,并且立即通知所有在这些znodes上监听的已经连接的客户端。此时会话超时的客户端和集群仍然是断开的,它不会被通知会话超时,除非它能够和集群重新建立连接(可见这个消息已经不是实时的了)。

SESSION_EXPIRED:会使zookeeper自动关闭连接。一个正确操作的集群,永远不应该看到SESSION_EXPIRED错误。他表示客户端离开zookeeper超过会话超时时间,并且zookeeper确定它已经死亡。由于zookeeper服务是稳定的,这个时候客户端应该认为自己已经死了,并且准备恢复。

处理方法:如果客户端只是从zookeeper读取状态,那恢复只是意味着重新连接;但是在复杂的应用程序中,恢复可能还意味着重新创建临时节点,争夺领导角色,重新构造发布状态等。

    2.3 Server关闭

考虑下面的场景:客户端以5秒的会话超时时间连接到ZK,管理员为了升级关闭了整个ZK集群,集群停机几分钟后重启。

在这种情况下,客户端可以重新连接并刷新它的会话。因为会话超时是由Leader跟踪的,当集群重启后,会话会重新进行倒计时。因此,只要客户端在Leader选举后的第一个5秒内连接就不会超时,并且它之前拥有的临时节点也不会消失。

对于Leader挂掉,重新选举Leader的情况是一样的,会话超时计时器会重置。

    2.4 zookeeper.close

一旦zookeeper对象关闭或者接收到一个致命事件(SESSION_EXPIREDAUTH_FAILED),zookeeper对象变成不可用。此时,客户端的IO线程和Event线程关闭,后续的请求是未定义行为并且会被忽略(any further access on zookeeper handle is undefined behavior and should be avoided)。

3、一致性保证

不保证:zookeeper并不保证在任何时候,两个不同的客户端看到的zookeeper数据是一样的。由于某些因素如网络延迟,一个客户端更新数据可能比另外一个客户端得到

变更通知要早。考虑两个客户端AB的情况,如果客户端A更改znode /a0变到1;然后客户端B读取/a,那么客户端B可能读取到的是旧值0,取决于B所连接的server。如果客户端A和客户端B看到的数据一致非常重要,客户端B应该在调用read方法之前,调用同步方法sync

也就是说,zookeeper本身不保证任何时候,任何server的数据是一致的,但是提供了有用的客户端同步机制。

4、认证

认证方式是可扩展的,实现AuthenticationProvider 可提供自己的认证方式。

zookeeper内置两种认证插件:ip and digest.

三、性能

在读操作比写操作频繁的应用中,zookeeper性能更佳(写操作涉及到所有server的同步)。分布式应用中,通常情况下读操作比写操作频繁。

测试条件:

Zookeeper版本: ZooKeeper release 3.2 

主机:双核2Ghz Xeon处理器

磁盘:SATA 15K RPM

部署方式:其中一个磁盘作为zookeeper事务日志专用设备,快照写入到操作系统磁盘。

测试方式:写操作和读操作数据大小1K;大约30台服务器模拟客户端;zookeeper集群设置为领导者不接受客户端的连接。

 

四、可靠性

常规配置:同上

集群规模:7台主机组成

写操作占所有操作的30%(请求里面有30%是要求写数据的)

测试步骤:

      Failure and recovery of a follower 重启一个follower

      Failure and recovery of a different follower 重启另外一个不同的follower

      Failure of the leader 挂掉Leader

      Failure and recovery of two followers 同时重启两个follower

      Failure of another leader 挂掉另外一个Leader(重新选举出来的那个)

 

 

从图中可以得出几个重要的点:

1、如果跟随者宕机并很快恢复,zookeeper能够维持一个很高的吞吐量。

2Leader选举算法可以保证系统快速恢复以阻止吞吐量的大幅下降。上图中zookeeper花费不到200ms选举一个新的Leader

3、一旦跟随者开始处理请求,zookeeper能够重新提高吞吐量。

五、客户端API

1public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher)

会话建立的过程是异步的。该构造函数初始化到server的连接,并且立即返回(通常比会话完全建立要早)。

参数Watcher指定当发生状态变化时被通知的watcher。该通知可能在任何时候到达(构造函数返回之前或之后都有可能)。

主机选择是随机从connectString挑选的。

 2public String create(final String path, byte data[], List<ACL> acl, CreateMode createMode)

   CreateMode.PERSISTENT:不会被zookeeper自动移除

   CreateMode.EPHEMERAL:当创建节点关联的会话失效时,临时节点会被zookeeper自动移除

    节点数据最大是1MB,超过会抛出KeeperException

   触发监控:exists() of current node and getChildren() of parent node

 3public void delete(final String path, int version)

   触发监控:exists() of current node and getChildren() of parent node

 4public synchronized void close()

    触发监控:The watches left on those nodes (and on their parents) will be triggered.

5public Stat setData(final String path, byte data[], int version)

    触发监控:getData() of current node.

六、Watcher丢失

只有一种情况会出现watch丢失:监控一个还没有创建的znode是否存在的watch,如果在断开连接的过程中该znode被创建并且又被删除了,这时重新连接后该watch会丢失。

正常情况下,重新连接后会话没有超时,此时会从Leader进行同步,watch不会丢失。

当然,如果重新连接过久导致会话超时,这时需要新建一个zookeeper,属于另外一个连接,原来连接上面的watch需要重新注册。

七、重新选举Leader的延迟

第四章-可靠性章节中显示,在有七台主机组成的集群中,一次Leader选举只花费不到200ms的时间。

如果对此仍然需要考虑的话,那么和第二章-2节节点故障处理机制中列出的4点故障一起考虑,有两种方式处理:

对于发送不成功的请求进行缓存和重试一定次数之后,就将请求丢弃。

八、常用功能

1、配置管理:

数据存放在某个节点,所有其他节点到该节点读取数据getData(true),并监听;数据变更之后,其他节点收到变更通知,调用getData(true)获取新数据并进行下次变更监听。

 

2、生产者、消费者模式/队列模式:

创建一个目录节点DIR;生产者往该目录中添加节点(节点也是带数据的);消费者调用getChildren(true)DIR节点上面监听(wait),有新节点(数据)添加到DIR节点后,消费者被唤醒。(如果消费者按照创建节点的顺序消费,那就是FIFO队列模式了)

 

3、栅栏模式:

栅栏前面站多少匹马是定好的(即SIZE定好)

创建一个目录节点DIR,每个客户端往目录中添加节点,并调用getChildren(true)DIR节点上面监听,DIR下面的节点变化后会触发各个客户端的监听事件,客户端判断DIR目录下面的节点数目是否达到预定的SIZE

 

4、独享锁模式/选举机制:

创建一个目录节点DIR,需要某个锁的时候,到目录下面创建一个自己的节点;调用getChildren()获取所有节点,如果自己的节点号是最小的,那么就获取到锁了,处理完之后将自己的节点删除;如果自己的节点号不是最小的,那么在比他次小的节点上调  exist(true),等待比他次小的节点消失(释放锁)。

选举机制类似,客户端成功启动后,都去目录中创建一个自己的临时序列递增节点;调用getChildren(),如果自己的节点号是最小的,那么就成为leader;否则在比自己次小的节点调用exist(true)监控此节点;当有一个服务器挂掉后,它所创建的临时节点会被删除,并激活在这个节点上面监听的其他节点;其他节点激活后,再次调用getChildren判断自己是不是最小的节点,是的话就成为leader.

 

5、分段提交/分布式事务:

分段提交协议指的是让分布式系统中的所有客户端都同意或者回滚一个事务。

协调器创建一个事务节点/app/Tx以及每个参与者的节点 /app/Tx/s_i(无数据);每个节点都在所有子节点上面监听;参与者处理完后,向自己的节点/app/Tx/s_i写入“commit”或者“abort”;等所有节点写入完毕,就可以决定是提交还是回滚了;如果其他节点写入了abort,某个节点可以提前回滚。

缺点:一共触发了o(n2)次的事件;

也可以子节点由子节点去创建自己的子节点(子节点处理完成之后),协调器getChildren(/app/Tx)做决定后,统一要求节点提交或者回滚(所有的事件都经过协调器)

九、自测相关

1、如何构造session_expired异常?

每创建一个zookeeper对象(成功),都会为此连接生成一个sessionid以及sessionpassword;这两个值是用来在客户端重连时恢复会话的。

我们可以利用这两个值构造另外一个拥有同样sessionid以及sessionpasswordzookeeper对象,然后把这个新对象close,则原来的zookeeper对象就会超时。

public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher,

            long sessionId, byte[] sessionPasswd)

十、维护

1、日志清理

自动净化快照和对象的事务日志可以通过配置自动完成。

 

2NIO选择

Zookeeper 3.4之前的版本,直接使用Java NIO通信。3.4版本之后(包括3.4),zookeeper提供了一个替换NIO的选项,但是NIO仍然是默认方式;不过,通过设置环境变量zookeeper.serverCnxnFactoryorg.apache.zookeeper.server.NettyServerCnxnFactory我们可以替换NIO为基于Netty的通信方式。该选项在服务端和客户端都可以设置,而且通常情况下应该在两端同时设置(客户端和服务端使用的方式不同对功能无影响)

3、日志目录的选择

      一个专用的事务日志存放设备必不可少。

猜你喜欢

转载自sweepingmonkgo.iteye.com/blog/2097555
今日推荐