http://blog.youkuaiyun.com/yangzhen92/article/details/53248294
Zookeeper C API
客户端使用C语言开发,zookeeper提供了两个库,zookeeper_st(单线程库)以及zookeeper_mt(多线程库)。
zookeeper_st提供了异步API和集成在应用程序用来实现事件循环的回调函数,该库是为了支持pthread库不支持或是不稳定的系统而存在。使用过程中需要通过zoo_interest以及zoo_process实现事件处理以及通知机制。
一般情况下使用zookeeper_mt多线程库,多线程库分为三个线程:主线程、io线程以及completion线程。主线程就是调用API的线程,io线程负责网络通信,而对于异步请求以及watcher的响应,io线程会发送给completion线程完成处理。
回调函数
Zookeeper C API中的各种回调函数原型如下:
监视函数(watcher funciton)原型
- 1
- 1
监视函数参数
- zh:zookeeper句柄
- type:事件类型(event type),*_EVENT常量之一(后面会提到)
- state:连接状态(connection state),*_STATE常量之一(后面会提到)
- path:触发监视事件zonode节点的路径,如果为NULL,则事件类型为ZOO_SESSION_EVENT
- watcherCtx:监视器上下文
其他回调函数原型
Zookeeper 中还有几种在异步 API(一般以 zoo_a*开头的函数) 中使用的回调函数,根据回调函数处理异步函数返回值类型的不同分为以下几类:
- 处理返回 void 类型的回调函数
- 处理返回 Stat 结构的回调函数
- 处理返回字符串的回调函数
- 处理返回数据的回调函数
- 处理返回字符串列表(a list of string)的回调函数
- 同时处理返回字符串列表(a list of string)和 Stat 结构的回调函数
- 处理返回 ACL 信息的回调函数
具体如下所示:
- 1
- 2
- 1
- 2
其中rc是异步返回的错误码,data是传入回调函数的自定义参数(下同)。
- 1
- 2
- 1
- 2
其中stat指向与znode相关的Stat信息(下同)。
- 1
- 2
- 1
- 2
其中value返回的字符串。
- 1
- 2
- 1
- 2
其中value表示返回的字节数组,value_len是字节长度。
- 1
- 2
- 3
- 1
- 2
- 3
其中strings指向了某个znode节点的所有子节点名称列表结构(下同)。
- 1
- 2
- 1
- 2
- 1
- 2
- 1
- 2
其中acl指向包含某个节点ACL信息的指针。
常用API函数
初始化、销毁Zookeeper句柄
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
参数:
- host:逗号隔开的 host:port 对, 每个代表一个 zk server, 例如: “127.0.0.1:3000,127.0.0.1:3001”
- fn:全局的监视器回调函数,当发生事件通知时,该函数会被调用
- clientid:客户端尝试重连的先前会话的ID,如果不需要重连先前的会话,则设置为0。客户端可以通过调用 zoo_client_id来访问一个已经连接上的并且有效的会话ID,如果clientid对应的会话超时,或者由于某种原因 clientid变为无效了,那么zookeeper_init将返回一个非法的 zhandle_t,通过 zhandle_t 的状态可以获知 zookeeper_init 调用失败的原因(通常为 ZOO_EXPIRED_SESSION_STATE)
- context:与zhandle_t实例相关联的“上下文对象”(可以通过该参数为 zhandle_t 传入自定义类型的数据),应用程序可以通过 zoo_get_context 访问它(例如在监视器回调函数中),当然 zookeeper 内部没有用到该参数,所以 context 可以设置为 NULL。
flags:一般设置为0
- 1
- 1
辅助函数
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 1
- 2
返回向zookeeper中注册的事件列表
参数:
- fd:文件描述列表
- interest:事件类型,ZOOKEEPER_WRITE、ZOOKEEPER_READ标志
- select或是poll超时时间
- 1
- 1
通知zookeeper注册的事件已经发生
参数:
- events:ZOOKEEPER_WRITE、ZOOKEEPER_READ标志
- 1
- 1
获取zookeeper连接的状态信息
同步接口
创建、删除节点
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
参数:
- zh:zookeeper_init返回的zookeeper句柄
- path:节点路径
- value:该节点保存的数据
- valuelen:该节点保存数据的大小。如果 value 被设置为 NULL(该 znode 节点不包含数据),则 valuelen 应该设置为 -1
- acl:该节点初始 ACL,ACL 不能为null 或空
- flags:该参数可以设置为 0,或者创建标识符 ZOO_EPHEMERAL, ZOO_SEQUENCE 的组合或(OR)
- path_buffer:用于保存返回节点新路径(因为设置了 ZOO_SEQUENCE 后 zoo_create 所创建的节点名称与参数 path 提供的名称不同,新的节点名称后面填充了序号),path 字符串以 NULL 结束。path_buffer 可以设置为 NULL,此时 path_buffer_len 等于 0
- path_buffer_len:path_buffer的长度,如果新节点名称的长度大于path_buffer_len,则节点名称将会被截断,而服务器端该节点的名称不会截断
- 1
- 1
参数:
- version:节点的版本号,如果该 znode 节点的实际版本号与该参数提供的版本号不一值,则删除节点失败,如果 version 为 -1,则不做版本检查
检查节点状态
两者的区别就是后者可以指定单独的watcher_fn(监视器回调函数),前者只能用使用zookeeper_init设置的全局监视器回调函数,下同。
- 1
- 2
- 1
- 2
参数(下同):
- watch:如果非 0,则在服务器端设置监视,当节点发生变化时客户端会得到通知,即使当前指定的节点不存在也会设置监视,这样该节点被创建时,客户端也可以得到通知
- stat:返回的 Stat 信息
- 1
- 2
- 3
- 1
- 2
- 3
参数(下同):
- watcher:如果不为 NULL 则会在服务器端设置监视,当节点发生变化时客户端会得到通知,即使当前指定的节点不存在也会设置监视,这样该节点被创建时,客户端也可以得到通知
- watchCtx:用户指定的数据,将被传入到监视器回调函数中,与由 zookeeper_init() 设置的全局监视器上下文不同,该函数设置的监视器上下文只与当前的监视器相关联
获取节点数据
- 1
- 2
- 1
- 2
参数:
- buffer:用于保存从 zookeeper 服务器获取的节点数据
- buffer_len:buffer 大小,一旦成功返回该值将会被设置为节点数据的实际大小,如果节点的数据为空,则数据大小为 -1,buffer_len 也为 -1
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
获取子节点列表
- 1
- 2
- 1
- 2
参数(下同):
- strings:返回的各个子节点路径。这里strings在调用api时会通过malloc分配内存空间,将子节点所有的目录存放在data字段中,需要客户端调用deallocate_String_vector(strings)做释放处理。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
异步接口
初学Zookeeper到这里肯定会有疑问,Watcher和AsyncCallBack之间的区别是什么?在介绍异步接口之前先来回顾一下Watcher和AsyncCallBack之间的区别和实现。
Watcher是用于监听节点、session状态的,比如get方法对节点设置了watcher之后,当节点的数据发生了改变之后,服务器会主动发送notification给客户端,然后进行watcher的回调。
AsyncCallBack是以异步的方式调用API,主动向服务器发送请求,然后将请求放入到pending队列中,等待服务器的响应。收到服务器对应的响应后,进行回调。
Zookeeper客户端中Watcher和AsyncCallback都是异步回调的方式,但它们回调的时机是不一样的,前者是由服务器发送事件触发客户端回调,后者是在执行了请求后得到响应后客户端主动触发的。它们的共同点在于都需要在获取了服务器响应之后,由io线程将事件注册到completion线程的事件队列中,然后由completion线程从队列中逐个取出并处理。
创建、删除节点
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
其中参数 string_completion_t completion 即返回字符串的回调函数,那么当 zoo_acreate 调用结束时将会触发 completion 回调函数的调用,同时传递给 completion 的 rc 参数为: ZOK 操作完成;ZNONODE 父节点不存在;ZNODEEXISTS 节点已存在;ZNOAUTH 客户端没有权限创建节点。ZNOCHILDRENFOREPHEMERALS 临时节点不能创建子节点。而 string_completion_t completion 中 const char *value 参数即新节点的路径名(注:如果 zoo_acreate 设置了ZOO_SEQUENCE ,则创建节点成功后,节点名称并不是 zoo_acreate 中 path 参数所指定的名称,而是类似与 /xyz0000000001,/xyz0000000002… 的名称)。另外,string_completion_t completion 中 const void *data 参数即为 zoo_acreate 中的 const void *data。
参数(下同):
- zh:zookeeper_init() 返回的 zookeeper 句柄
- path:节点路径
- value:该节点保存的数据
- valuelen:该节点保存数据的大小
- acl:该节点初始 ACL,ACL 不能为null 或空
- flags:该参数可以设置为 0,或者创建标识符 ZOO_EPHEMERAL, ZOO_SEQUENCE 的组合或(OR)
- completion:当创建节点请求完成时会调用该函数
- data:completion 函数被调用时,传递给 completion 的数据
- 1
- 2
- 1
- 2
参数(下同):
- version:期望的节点版本号,如果真实的版本号与期望的版本号不同则 zoo_delete() 调用失败,-1 表示不不检查版本号。
- 1
- 2
- 1
- 2
参数:
- watch:如果非 0,则在服务器端设置监视,当节点发生变化时客户端会得到通知,即使当前指定的节点不存在也会设置监视,这样该节点被创建时,客户端也可以得到通知
- 1
- 2
- 3
- 1
- 2
- 3
参数(下同):
- watcher:如果非 0,则在服务器端设置监视,当节点发生变化时客户端会得到通知,即使当前指定的节点不存在也会设置监视,这样该节点被创建时,客户端也可以得到通知
- watcherCtx:用户指定的数据,将被传入到监视器回调函数中,与由 zookeeper_init() 设置的全局监视器上下文不同,该函数设置的监视器上下文只与当前的监视器相关联
- 1
- 2
- 1
- 2
参数(下同):
- 如果非 0,则在服务器端设置监视,当节点发生变化时客户端会得到通知。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
节点状态信息
使用get命令获取指定节点数据时,也会返回节点的状态信息Stat,该结构包含如下字段:
- czxid:节点创建时的zxid
- mzxid:节点最新一次更新发生时的zxid
- ctime:节点创建时的时间戳
- time:节点最新一次更新发生时的时间戳
- dataVersion:节点数据的更新次数
- cversion:其子节点的更新次数
- aclVersion:节点ACL(授权信息)的更新次数
- ephemeralOwner:如果该节点为ephemeral节点, ephemeralOwner值表示与该节点绑定的session id. 如果该节点不是ephemeral节点, ephemeralOwner值为0
- dataLength:节点数据的字节数
- numChildren:子节点个数
常见错误码
错误码 | 说明 |
---|---|
ZOK | 正常返回 |
ZSYSTEMERROR | 系统或服务器端错误(System and server-side errors),服务器不会抛出该错误,该错误也只是用来标识错误范围的,即大于该错误值,且小于 ZAPIERROR 都是系统错误 |
ZRUNTIMEINCONSISTENCY | 运行时非一致性错误 |
ZDATAINCONSISTENCY | 数据非一致性错误 |
ZCONNECTIONLOSS | Zookeeper 客户端与服务器端失去连接 |
ZMARSHALLINGERROR | 在 marshalling 和 unmarshalling 数据时出现错误(Error while marshalling or unmarshalling data) |
ZUNIMPLEMENTED | 该操作未实现(Operation is unimplemented) |
ZOPERATIONTIMEOUT | 该操作超时(Operation timeout) |
ZBADARGUMENTS | 非法参数错误(Invalid arguments) |
ZINVALIDSTATE | 非法句柄状态(Invliad zhandle state) |
ZAPIERROR | API 错误(API errors),服务器不会抛出该错误,该错误也只是用来标识错误范围的,错误值大于该值的标识 API 错误,而小于该值的标识 ZSYSTEMERROR |
ZNONODE | 节点不存在(Node does not exist) |
ZNOAUTH | 没有经过授权(Not authenticated) |
ZBADVERSION | 版本冲突(Version conflict) |
ZNOCHILDRENFOREPHEMERALS | 临时节点不能拥有子节点(Ephemeral nodes may not have children) |
ZNODEEXISTS | 节点已经存在(The node already exists) |
ZNOTEMPTY | 该节点具有自身的子节点(The node has children) |
ZSESSIONEXPIRED | 会话过期(The session has been expired by the server) |
ZINVALIDCALLBACK | 非法的回调函数(Invalid callback specified) |
ZINVALIDACL | 非法的ACL(Invalid ACL specified) |
ZAUTHFAILED | 客户端授权失败(Client authentication failed) |
ZCLOSING | Zookeeper 连接关闭(ZooKeeper is closing) |
ZNOTHING | 并非错误,客户端不需要处理服务器的响应(not error, no server responses to process) |
ZSESSIONMOVED | 会话转移至其他服务器,所以操作被忽略(session moved to another server, so operation is ignored) |
Watcher通知的状态类型和事件类型
状态类型(state)
状态码 | 说明 |
---|---|
-112 | 会话超时(ZOO_EXPIRED_SESSION_STATE) |
-113 | 认证失败(ZOO_AUTH_FAILED_STATE) |
1 | 连接建立中(ZOO_CONNECTING_STATE) |
2 | 连接建立中(ZOO_ASSOCIATING_STATE) |
3 | 连接已建立(ZOO_CONNECTED_STATE) |
999 | 无连接状态 |
事件类型(type)
事件码 | 说明 |
---|---|
1 | 创建节点事件(ZOO_CREATED_EVENT) |
2 | 删除节点事件(ZOO_DELETED_EVENT) |
3 | 更改节点事件(ZOO_CHANGED_EVENT) |
4 | 子节点列表变化事件(ZOO_CHILD_EVENT) |
-1 | 会话session事件(ZOO_SESSION_EVENT) |
-2 | 监视被移除事件(ZOO_NOTWATCHING_EVENT) |
获取Watcher
Watcher的设置和获取在开发中很常见,不同的操作会收到不同的watcher信息。对父节点的变更以及孙节点的变更都不会触发watcher,而对watcher本身节点以及子节点的变更会触发watcher。
exists、getdata以及getchildren方法获取watcher可参考如下:
操作 | 方法 | 触发watcher | watcher state | watcher type | watcher path |
---|---|---|---|---|---|
Create当前节点 | getdata | × | × | × | × |
getchildren | √ | 3 | 4 | √ | |
exists | × | × | × | × | |
set当前节点 | getdata | √ | 3 | 3 | √ |
getchildren | × | × | × | × | |
exists | √ | 3 | 3 | √ | |
delete当前节点 | getdata | √ | 3 | 2 | √ |
getchildren | √ | 3 | 2 | √ | |
exists | √ | 3 | 2 | √ | |
create子节点 | getdata | × | × | × | × |
getchildren | √ | 3 | 4 | √ | |
exists | × | × | × | × | |
set子节点 | getdata | × | × | × | × |
getchildren | × | × | × | × | |
exists | × | × | × | × | |
delete子节点 | getdata | × | × | × | × |
getchildren | √ | 3 | 4 | √ | |
exists | × | × | × | × | |
恢复连接 | getdata | √ | 1 | -1 | × |
getchildren | √ | 1 | -1 | × | |
exists | √ | 1 | -1 | × | |
恢复连接session未超时 | getdata | √ | -112 | -1 | × |
getchildren | √ | -112 | -1 | × | |
exists | √ | -112 | -1 | × | |
恢复连接session超时 | getdata | √ | 3 | -1 | × |
getchildren | √ | 3 | -1 | × | |
exists | √ | 3 | -1 | × |
单线程库使用示例
主函数
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
全局监视器回调函数
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
程序执行分析
单线程程序链接需要libzookeeper_st库
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
在程序执行前首先建立/xyz节点,服务器首次返回的事件类型为SESSION_EVENT,显示客户端连接已成功。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
创建节点/xyz/abc,服务器返回的事件类型是CHILD_EVENT,表示的是子节点事件,同时返回子节点列表。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
设置节点数据/xyz,服务器返回的事件类型是CHANGED_EVENT,表示的节点数据改变事件,
多线程库使用示例
多线程库api使用相对单线程就比较简单了
主函数
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
全局监视器回调函数
和上面单线程类似
程序执行分析
多线程程序需要链接libzookeeper_mt以及pthread两个库
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
这里可以看到创建session成功的时候,服务器返回状态码state=3,事件码type=-1。
而改变/xyz节点数据的时候,服务器返回状态码state=3,事件码type=3(由于这里设置了watcher,因此当节点改变的时候会通知一次客户端)。
最后成功删除节点/xyz。