目录
Zookeeper简介
ZooKeeper安装
Curator常用API及其原理
Zookeeper在PtBalancer中的应用
演讲文档
文档下载
遗留问题
1、ZK选举过程
当leader崩溃或者leader失去大多数的follower,这时候zk进入恢复模式,恢复模式需要重新选举出一个新的leader,让所有的Server都恢复到一个正确的状态。Zk的选举算法有两种:一种是基于basic paxos实现的,另外一种是基于fast paxos算法实现的。系统默认的选举算法为fast paxos。先介绍basic paxos流程:
1 .选举线程由当前Server发起选举的线程担任,其主要功能是对投票结果进行统计,并选出推荐的Server;
2 .选举线程首先向所有Server发起一次询问(包括自己);
3 .选举线程收到回复后,验证是否是自己发起的询问(验证zxid是否一致),然后获取对方的id(myid),并存储到当前询问对象列表中,最后获取对方提议的leader相关信息(id,zxid),并将这些信息存储到当次选举的投票记录表中;
4. 收到所有Server回复以后,就计算出zxid最大的那个Server,并将这个Server相关信息设置成下一次要投票的Server;
5. 线程将当前zxid最大的Server设置为当前Server要推荐的Leader,如果此时获胜的Server获得n/2 + 1的Server票数, 设置当前推荐的leader为获胜的Server,将根据获胜的Server相关信息设置自己的状态,否则,继续这个过程,直到leader被选举出来。
通过流程分析我们可以得出:要使Leader获得多数Server的支持,则Server总数必须是奇数2n+1,且存活的Server的数目不得少于n+1
2、ZK在PtBalancer中的应用
由于昨天后面时间太长,导致ZK在PtBalancer中的应用未讲,这部分后面会在讲解PtBalancer系统时补充
zookeeper常见问题
1. 客户端对ServerList的轮询机制是什么
随机,客户端在初始化( new ZooKeeper(String connectString, int sessionTimeout, Watcher watcher) )的过程中,将所有Server保存在一个List中,然后随机打散,形成一个环。之后从0号位开始一个一个使用。
两个注意点:1. Server地址能够重复配置,这样能够弥补客户端无法设置Server权重的缺陷,但是也会加大风险。(比如: 192.168.1.1:2181,192.168.1.1:2181,192.168.1.2:2181). 2. 如果客户端在进行Server切换过程中耗时过长,那么将会收到SESSION_EXPIRED. 这也是上面第1点中的加大风险之处。更多关于客户端地址列表相关的,
2.客户端如何正确处理CONNECTIONLOSS(连接断开) 和 SESSIONEXPIRED(Session 过期)两类连接异常
在ZooKeeper中,服务器和客户端之间维持的是一个长连接,在 SESSION_TIMEOUT 时间内,服务器会确定客户端是否正常连接(客户端会定时向服务器发送heart_beat),服务器重置下次SESSION_TIMEOUT时间。因此,在正常情况下,Session一直有效,并且zk集群所有机器上都保存这个Session信息。在出现问题情况下,客户端与服务器之间连接断了(客户端所连接的那台zk机器挂了,或是其它原因的网络闪断),这个时候客户端会主动在地址列表(初始化的时候传入构造方法的那个参数connectString)中选择新的地址进行连接。
好了,上面基本就是服务器与客户端之间维持长连接的过程了。在这个过程中,用户可能会看到两类异常CONNECTIONLOSS(连接断开) 和SESSIONEXPIRED(Session 过期)。
CONNECTIONLOSS发生在上面红色文字部分,应用在进行操作A时,发生了CONNECTIONLOSS,此时用户不需要关心我的会话是否可用,应用所要做的就是等待客户端帮我们自动连接上新的zk机器,一旦成功连接上新的zk机器后,确认刚刚的操作A是否执行成功了。
SESSIONEXPIRED发生在上面蓝色文字部分,这个通常是zk客户端与服务器的连接断了,试图连接上新的zk机器,这个过程如果耗时过长,超过 SESSION_TIMEOUT 后还没有成功连接上服务器,那么服务器认为这个session已经结束了(服务器无法确认是因为其它异常原因还是客户端主动结束会话),开始清除和这个会话有关的信息,包括这个会话创建的临时节点和注册的Watcher。在这之后,客户端重新连接上了服务器在,但是很不幸,服务器会告诉客户端SESSIONEXPIRED。此时客户端要做的事情就看应用的复杂情况了,总之,要重新实例zookeeper对象,重新操作所有临时数据(包括临时节点和注册Watcher)。
3. 不同的客户端对同一个节点是否能获取相同的数据
4. 一个客户端修改了某个节点的数据,其它客户端能够马上获取到这个最新数据吗
ZooKeeper不能确保任何客户端能够获取(即Read Request)到一样的数据,除非客户端自己要求:方法是客户端在获取数据之前调用org.apache.zookeeper.AsyncCallback.VoidCallback, java.lang.Object) sync.
通常情况下(这里所说的通常情况满足:1. 对获取的数据是否是最新版本不敏感,2. 一个客户端修改了数据,其它客户端需要不需要立即能够获取最新),可以不关心这点。
在其它情况下,最清晰的场景是这样:ZK客户端A对 /my_test 的内容从 v1->v2, 但是ZK客户端B对 /my_test 的内容获取,依然得到的是 v1. 请注意,这个是实际存在的现象,当然延时很短。解决的方法是客户端B先调用 sync(), 再调用 getData().
5. ZK为什么不提供一个永久性的Watcher注册机制
不支持用持久Watcher的原因很简单,ZK无法保证性能。
6. 使用watch需要注意的几点
a. Watches通知是一次性的,必须重复注册.
b. 发生CONNECTIONLOSS之后,只要在session_timeout之内再次连接上(即不发生SESSIONEXPIRED),那么这个连接注册的watches依然在。
c. 节点数据的版本变化会触发NodeDataChanged,注意,这里特意说明了是版本变化。存在这样的情况,只要成功执行了setData()方法,无论内容是否和之前一致,都会触发NodeDataChanged。
d. 对某个节点注册了watch,但是节点被删除了,那么注册在这个节点上的watches都会被移除。
e. 同一个zk客户端对某一个节点注册相同的watch,只会收到一次通知。即
for( int i = 0; i < 3; i++ ){
zk.getData( path, true, null );
zk.getChildren( path, true );
}
f. Watcher对象只会保存在客户端,不会传递到服务端。
7.我能否收到每次节点变化的通知
如果节点数据的更新频率很高的话,不能。
原因在于:当一次数据修改,通知客户端,客户端再次注册watch,在这个过程中,可能数据已经发生了许多次数据修改,因此,千万不要做这样的测试:”数据被修改了n次,一定会收到n次通知”来测试server是否正常工作。(我曾经就做过这样的傻事,发现Server一直工作不正常?其实不是)。即使你使用了GitHub上这个客户端也一样。
8.能为临时节点创建子节点吗
不能。
9. 是否可以拒绝单个IP对ZK的访问,操作
ZK本身不提供这样的功能,它仅仅提供了对单个IP的连接数的限制。你可以通过修改iptables来实现对单个ip的限制,当然,你也可以通过这样的方式来解决。https://issues.apache.org/jira/browse/ZOOKEEPER-1320
10. 在getChildren(String path, boolean watch)是注册了对节点子节点的变化,那么子节点的子节点变化能通知吗
不能
11.创建的临时节点什么时候会被删除,是连接一断就删除吗?延时是多少?
连接断了之后,ZK不会马上移除临时数据,只有当SESSIONEXPIRED之后,才会把这个会话建立的临时数据移除。因此,用户需要谨慎设置Session_TimeOut
12. zookeeper是否支持动态进行机器扩容?如果目前不支持,那么要如何扩容呢?
截止3.4.3版本的zookeeper,还不支持这个功能,在3.5.0版本开始,支持动态加机器了,期待下吧: https://issues.apache.org/jira/browse/ZOOKEEPER-107
13. ZooKeeper集群中个服务器之间是怎样通信的?
Leader服务器会和每一个Follower/Observer服务器都建立TCP连接,同时为每个F/O都创建一个叫做LearnerHandler的实体。LearnerHandler主要负责Leader和F/O之间的网络通讯,包括数据同步,请求转发和Proposal提议的投票等。Leader服务器保存了所有F/O的LearnerHandler。
14.zookeeper是否会自动进行日志清理?如果进行日志清理?
zk自己不会进行日志清理,需要运维人员进行日志清理,
15.ZooKeeper客户端地址列表的随机原理
客户端使用ZooKeeper的时候,首先会建立与ZooKeeper的连接,方法是通过调用下面这个构造方法来实现的。
public ZooKeeper(String connectString, //
int sessionTimeout, //
Watcher watcher,//
boolean canBeReadOnly )
throws IOException
在这个构造方法中,首先要配置的是ZK服务器的地址列表,即connectString 这个参数,这个参数通常是这样一个格式的字符串:
192.168.1.1:2181,192.168.1.2:2181,192.168.1.3:2181
很明显,我们可以看到,ZK客户端允许我们将ZK服务器的所有地址都配置在这里,于是一个问题就来了,ZK在连接服务器过程中,是如何选择服务器的呢?下面首先来看看ZK客户端是如何处理这个connectString的:
new ZooKeeper(“192.168.1.1:2181,192.168.1.2:2181,192.168.1.3:2181”,...)
实例一个ZooKeeper对象的时候,会要求传入一个地址列表的字符串,这个字符串就是ZK服务器的地址列表,用英文状态“,“隔开。
ConnectStringParser connectStringParser =
new ConnectStringParser(“192.168.1.1:2181,192.168.1.2:2181,192.168.1.3:2181”);
之后,这个地址列表会被封装到一个ConnectStringParser 对象中去,这个类主要就是解析传入地址列表字符串,将其它保存在一个ArrayList中。这个对象基本结构如下,这里我们主要关注serverAddresses这个成员。
public final class ConnectStringParser {
private final String chrootPath;
private final ArrayList
}
接下去,这个地址列表会被进一步封装成StaticHostProvider对象,并且在运行过程中,一直是这个对象来维护整个地址列表。关于这个对象,我们主要关注两点:地址列表的随机和地址获取这两个过程。首先来看地址列表的随机:
public StaticHostProvider(Collection
throws UnknownHostException {
……
……
Collections.shuffle(this.serverAddresses);
}
这里可以看到,对于传入地址列表,ZK使用java.util.Collections.shuffle(List list) 来对地址列表随机打乱顺序,注意,这个随机过程是一次性的,也就是说,之后使用过程中一直是按照这样的顺序。再来看看地址列表被随机打乱后,又是怎么使用地址的:
public InetSocketAddress next(long spinDelay) {
++currentIndex;
if (currentIndex == serverAddresses.size()) {
currentIndex = 0;
}
……
……
return serverAddresses.get(currentIndex);
}
看一下StaticHostProvider.next(long spinDelay) 方法就明白了。next方法的实现, 没错,就是“Round Robin”。简单的说,ZK客户端将所有Server保存在一个List中,然后随机打乱,并且形成一个环,具体使用的时候,从0号位开始一个一个使用。
另外两个注意点:
1.通过代码,可以发现ZK本质上是通过一个List来维护地址列表的,因此,Server地址能够重复配置,这样能够弥补客户端无法设置Server权重的缺陷,但是也会加大风险。 比如: 192.168.1.1:2181,192.168.1.1:2181,192.168.1.2:2181
2.如果客户端在进行Server切换过程中耗时过长,那么将会收到SESSION_EXPIRED. 这也是上面第1点中的加大风险之处。
参考:淘宝中间团队博客