您的位置:首页 > 其它

Zookeeper实例创建和会话建立的异步特性

2014-08-18 17:22 525 查看

一、 关于zookeeper_init函数的使用

问题描述:

开发人员在调用zookeeper_init函数时,若返回一个非空句柄zhandle_t *zh,则认为初始化成功,这样可能会导致后续操作失败。

问题分析:

zhandle_t *zookeeper_init(const char *host, watcher_fn fn, int recv_timeout,const clientid_t *clientid, void *context, int flags) 函 数 返回一个zookeeper客户端与服务器通信的句柄,通常我们仅仅根据返回句柄情况来判断zookeeper 客户端与zookeeper服务器是否 建立连接。如果句柄为空则认为是失败,非空则成功。其实不然,zookeeper_init创建与ZooKeeper服务端通信的句柄以及对应于此句柄的会话,而会话的创建是一个异步的过程,仅当会话建立成功,zookeeper_init才返回一个可用句柄。

问题解决:

如何正确判断zookeepr_init初始化成功,可通过以下三种方式

1、判断句柄的state是否为ZOO_CONNECTED_STATE状态,通过zoo_state(zh)判断状态值是否为ZOO_CONNECTED_STATE。

void ensureConnected()

{

pthread_mutex_lock(&lock);

while (zoo_state(zh)!=ZOO_CONNECTED_STATE)

{

pthread_cond_wait(&cond,&lock);

}

pthread_mutex_unlock(&lock);

}
2、 在zookeeper_init中设置watcher,当zookeeper client与server会话建立后,触发watcher,当 watcher 的state = 3 (ZOO_CONNECTED_STATE), type = -1(ZOO_SESSION_EVENT)时,确认 会话成功建立,此时zookeeper client 初始化成功,可进行后续操作。

3、业务上可以做保证,调用zookeeper_init返回句柄zh,通过该句柄尝试做zoo_exists()或zoo_get_data()等操作,根据操作结果来判断是否初始化成功。

二、 如何解决session失效问题

问题描述:

session失效,导致注册的watcher全部丢失。

问题分析:

如果zookeeper client与server在协商的超时时间内仍没有建立连接,当client与server再次建立连接时,由于session失效了,所有watcher已经被服务器端删除,从而导致所有的watcher需要重新注册。

session 失效,zookeeper client与server重连后所有watcher都会收到两次触发,第一次 wathetr state = 1,type = -1(state = 1表示正在连接中,type = -1 表示session事件);第二次 watcher state = -112,type = -1(state = -112表示session失效)。

问题解决:

可以通过以下两种方法解决session失效问题

1、获取触发session失效watcher后,业务重新注册所有的watcher。

2、不能根本解决,但是可以减小session失效的概率。通过zookeeper client 与server设置更长的session超时时间。(参考下一问题)

三、 为什么zookeeper_init设置recv_timeout较长却没有效果

问题描述:

zookeeper_init设置recv_timeout 100000ms,但客户端与服务端断开连接30s就session失效了。

问题分析:

关于session超时时间的确定:zookeeper_init中设置的超时时间并非真正的session超时时间,session超时时间需要 server与client协商,业务通过zoo_recv_timeout(zhandle_t* zh)获取server与client协商后的超时时间。服务端: minSessionTimeout (默认值为:tickTime * 2) , maxSessionTimeout(默认值为:tickTime * 20), ticktime的默认值为2000ms。所以session范围为4s ~ 40s 。客户端:
sessionTimeout, 无默认值,创建实例时设置recv_timeout 值。经常会认为创建zookeeper客户端时设置了sessionTimeout为100s,而没有改变server端的配置,默认值是 不会生效的。 原因: 客户端的zookeeper实例在创建连接时,将sessionTimeout参数发送给了服务端,服务端会根据对应的 minSession/maxSession Timeout的设置,强制修改sessionTimeout参数,也就是修改为4s~40s 返回的参数。所以服务端不一定会以客户端的sessionTImeout做为session expire管理的时间。

问题解决:

增加zookeeper_init recv_timeout大小的同时,需要配置tickTime的值。

tickTime设置是在 conf/zoo.cfg 文件中

# The number of milliseconds of each

ticktickTime=2000 (默认)

注: tickTime 心跳基本时间单位毫秒,ZK基本上所有的时间都是这个时间的整数倍。
import org.apache.zookeeper.*;

import java.io.IOException;

import java.util.concurrent.CountDownLatch;

import java.util.concurrent.ThreadLocalRandom;

public class ZKApplication implements Watcher {

private static final int SESSION_TIMEOUT = 3000;

private volatile static boolean shutdown;

private ZooKeeper zk;

private CountDownLatch connectedSignal = new CountDownLatch(1);

public void connect(String hosts) throws IOException, InterruptedException {

try {

System.out.println("Start to create the Zookeeper instance");

zk = new ZooKeeper(hosts, SESSION_TIMEOUT, this);

new Thread(new Runnable() {

int count = 0;

@Override

public void run() {

while (count++ <= 1000) {

System.out.println(zk.getState());

try {

Thread.currentThread().sleep(20);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}).start();

} catch(IOException e) {

e.printStackTrace();

throw e;

}

connectedSignal.await();

//Wait for the SyncConnected event occurs and process has been called, which will stop the waiting here.

}

}

@Override

public void process(WatchedEvent event) { // Watcher interface

if (event.getState() == Event.KeeperState.SyncConnected) {

connectedSignal.countDown(); //connectedSignal.await() will no longer wait

}

}

public void create(String groupName) throwsKeeperException,

InterruptedException {

String path = "/" + groupName;

String createdPath = zk.create(path, null/*data*/, ZooDefs.Ids.OPEN_ACL_UNSAFE,

CreateMode.EPHEMERAL); //The znode will be deleted upon the session is closed.

System.out.println("Created " + createdPath);

}

public void close() throws InterruptedException {

zk.close();

}

public static void main(String[] args) throws Exception {

final ZKApplication createGroup = new ZKApplication();

String groupName = "zoo" + ThreadLocalRandom.current().nextInt();

createGroup.connect(Host.HOST);

createGroup.create(groupName);

createGroup.close();

}

}

上面的代码在Zookeeper没有启动的情况下,控制台输出1000次CONNECTING,也不退出,原因是已经发生了死锁问题。 引发死锁问题的是同步闭锁connectedSignal的使用,代码中的含义是客户端在connect方法中发起链接,然后connect一直等待直到 Watcher的process被回调将闭锁计数置零,通常这个没有问题,可是当Zookeeper压根没有启动的时候,这个代码会陷入死锁:

死锁发生在客户端阻塞等待于connectedSignal的await方法上,发生阻塞说明connectedSignal的计数没有置零,没有置零说明process没有调用,因为Zookeeper没有启动,所以说process没有被调用是合理的。代 码死锁于connectSignal的await方法上,那么意思是说,Zookeeper对象构造和会话建立过程应该是异步的,Zookeeper构造
方法返回后,另外一个会话创建线程会尝试建立会话,从代码的输出可以看出,会话状态一直是Connecting状态(试图创建会话,但尚未创建成功的状 态),Zookeeper的Javadoc清楚的说到Zookeeper实例和会话建立是异步的过程
: Session establishment is asynchronous. This constructor will initiate connection to
the server and return immediately - potentially (usually) before the session is fully established. The watcher argument specifies the watcher that will be notified of any changes in state. This notification can come at any point before or after the constructor
call has returned.
会 话建立的失败策略,比较直观的做法时当建立会话不成功时,第一时间通知客户,给客户以失败快速响应的机制。。。不过Zookeeper似乎采用另外一种策 略,连接不成功不告诉你,只有当调用Zookeeper的API时,才会真正的把连接未建立的异常抛给客户端,这么做的好处是在Zookeeper实例创
建但是由于连接不成功而处于Connecting状态时,如果在用户调用Zookeeper的API做具体的事情之前,Zookeeper恢复了,那么客 户端再调用Zookeeper的API进行操作时,跟Zookeeper一直处于健康的状态一样,这也体现了Zookeeper的高可用性。
会话的建立和会话的状态管理是在单独的线程中

了解了Zookeeper对象实例化和会话建立的异步性,一方面可以了解Zookeeper的设计策略,另一方面,也可以避免一些代码和同步策略导致的陷阱,比如代码中那样。

虽然代码中主线程已经阻塞于connect方法,可是当Zookeeper服务器启动后,Client会建立连接,然后回调Watcher的process方法,此时就会解锁,从这个角度上来说,这里的死锁应该不叫事,应该启动这个程序目的就是要有Zookeeper在运行才好,如果Zookeeper之后断了,connectionsignal这个闭锁已经不再起作用。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: