由于我们公司主要使用的C语言客户端,并且由于业务需要和稳定性需要,对于zk服务增加了基于taas(内部认证系统)的认证和quota管理,所以代码修改了握手时候的协议,不过大体还是和原先相同的。
主要数据结构
有zhandle,adaptor_thread和completion_list_t,分别代表zk的一些全局共享信息,线程控制信息和回调watcher信息。各个struct的主要内容如下:
线程模型:
zk c客户端每一个zhandle创建两个线程,IO线程(do_io)负责连接zk server以及收发包,competion线程(do_competion)处理异步回调函数和watcher调用。
IO线程发送to_send队列数据,接收服务器返回数据至to_process,并根据收到的回复把sent_request队列中的回调和watcher挪到completion_to_process队列。
用户线程通过客户端接口把请求Request插入to_send队列,把回调和watcher写入sent_request
competion线程调用completion_to_process队列中的回调和watcher。
状态转换:
①若从连接状态发送to_send失败返回connect loss错误。若2/3个timeout没有收到任何包,则返回connect timeout。
这两种情况都会执行sent_request(pengding request)的同步和异步调用,并设置error code为相应值。
主要流程:
用户在操作zk znode之前,需要调用zookeeper_init初始化一个zhandle,之后在每个方法调用中传入这个handle。
以get为例说明主要流程:
1.调用zoo_get方法把回调和watcher写入send_request队列。如果是同步则写入一个标记SYNCHRONOUS_MARKER。(add_data_completion)
2.序列化request,加入到to_send队列。
3.通过往pipe的fd写操作唤醒IO线程发送数据。
4.如果是异步则结束,如果是同步等待处理结束。
IO线程步骤:
1.挑选一个ip连接zk server并发送ConnectRequest(握手包,用于创建session),如果已经连接,则在1/3超时时发送ping。(zookeeper_interest)
2.发送to_send队列,接收服务器消息到to_process队列,更新最后收发包时间。(check_events)
3.如果是异步操作和watcher,则把sent_request队列中的回调和watcher移到completion_to_process队列。结束。(zookeeper_process)
4.如果是同步操作根据to_process队列操作唤醒用户线程。
Completion线程
1.若completion_to_process队列为空则等待。
2.调用队列中的回调函数和watcher。
zookeeper c客户端bug修复
1.fd泄露的bug
zookeeper.cc的zookeeper_process方法在收到乱序回应时,断掉连接重连时,没有减少zhandle的引用计数,这时zookeeper_close只会停止线程而不会释放zhandle的资源。
只有当引用计数为0时,才回destroy整个handle,造成了fd泄露的问题。
2.watcher_object的内存泄露 (zookeeper-c-client-3.4.6 中已修复)
对于同一路径上注册watcher超过两个时,后面的watcher全部内存泄露。
因为activateWatcher时在堆上创建了wacher,而后面add_to_list调用clone了该watcher object,在watcher调用完成时释放的是clone的,所以原始的就泄露了。
3.Log_evn的getpwuid_r方法返回0不一定代表了pw结构体被找到,pw还可能是NULL,core在这里
主要缺点:
1.每初始化一个zhandle都会启动两个线程,线程不能共享。
2.c代码难以维护
3.回调串行处理,某个回调时间长容易堵住线程,可以采用线程池。
4.session expire必须由客户端重连到服务端才能发现。
5.重连时不sleep,因此实际应用中发生过DDOS
original link:http://blog.csdn.net/shaolinshu/article/details/7922963