初识ZooKeeper,做一些记录。
ZooKeeper提供一个集中式服务,包括配置维护、服务命名、分布式同步、组管理。子服务常用于分布式应用。
ZooKeeper体系结构
ZooKeeper是开源的用于分布式应用的分布式协调服务。它公开了一组接口,分布式应用可在其基础上实现配置维护、数据同步、服务命名、组管理等上层服务。它采用了类似文件系统的目录树型结构的数据模型。协调服务难于处理,特别容易出错,比如条件竞争和死锁。ZooKeeper的动机是为了减轻分布式应用实现协调服务的负担。
zookeeper允许分布式应用通过共享的层次化名字空间进行相互协调。zookeeper在内存中维护数据,访问上具备高吞吐、低延迟的特点。zookeeper重视高性能、高可用、严格有序存取,因此,性能方面能够胜任大型分布式系统,可靠方面可屏蔽单点故障,严格有序存取确保客户端能够实现复杂的同步原语。
数据模型、层次化名字空间、节点
zookeeper名字空间由节点znode构成,其组织方式类似文件系统,其中各个节点相当于目录和文件,通过路径作为唯一标识。与文件系统不同的是,每个节点具有与之对应的数据内容,同时也可以具有子节点。zookeeper用于存储协调数据,如状态、配置、位置等信息,每个节点存储的数据量很小,KB级别(我理解是0~1024KB)。节点维护一个stat结构(包括数据变化的版本号、ACL变化、时间戳),以允许缓存验证与协调更新。每当节点数据内容改变,版本号会增长。客户端获取数据的同时也会获取数据版本号。节点的数据内容以原子方式读写,读操作读取全部内容,写操作替换全部内容。节点具有一个访问控制列表(Access Control List - ACL)来约束哪些人能执行哪些操作。zookeeper有一种节点叫做临时节点,临时节点仅在创建该节点的会话的存存活期间存在,会话结束,临时节点自动删除。
ZooKeeper组件
如下图所示,同一个zookeeper服务下的server有两种,一种是leader server,另一种是follower server。leader特殊之处在于它有决定权,具有Request Processor,在zookeeper整个服务下的每台server将复制各个组件。Replicated Database是包含了所有数据的内存数据库。读请求利用本地副本数据库进行处理。改变zookeeper服务状态的请求和写请求将按照一个协同协议执行。所有写请求(from client)将传递到leader,然后leader向所有follower发起提案,follower接收提案并表态,提案通过后执行。消息传输层负责在提案失败时更换leader和followers与leader之间的同步。
关于ZooKeeper Watches
zookeeper所有读操作(getData(),getChildren(),exists())具有设置watch的选项。
zookeeper watch的定义如下:watch事件是一次性触发器,当watch监视的数据发生变化时,通知设置了该watch的client,即watcher。
需要注意三点:
1.一次性触发器
client在一个节点上设置watch,随后节点内容改变,client将获取事件。当节点内容再次改变,client不会获取这个事件,除非它又执行了一次读操作并设置watch
2.发送至client,watch事件延迟
watch事件异步发送至观察者。比如说client执行一次写操作,节点数据内容发生变化,操作返回后,而watch事件可能还在发往client的路上。这种情况下,zookeeper提供有序保证:client不会得知数据变化,直到它获取watch事件。网络延迟或其他因素可能导致不同client在不同时刻获取watch事件和操作返回值。
3.设置watch的数据内容
涉及到节点改变的不同方式。比方说zookeeper维护两个watch列表:节点的数据watch和子节点watch。getData()和exists()设置了内容watch,getChildren()设置了子节点watch,操作返回的数据类型不同,前者是节点的内容,后者是节点的子节点列表。setData()触发内容watch,create()触发当前节点的"内容watch"和其父节点的"子节点watch",delete()同时触发"内容watch"和"子节点watch"(其子节点被全部删除),以及其父节点的"子节点watch"。说白了,对当前节点的操作,要考虑到对其父节点与子节点的影响。
watch在客户端所连接的服务端本地维护。watch的设置、维护、分发操作都很轻量级。当客户端连接到新的服务端,watch将被任一会话事件触发。与服务端断开连接时,不能获取watch事件。客户端重连后,之前注册的watch将被重新注册并在需要时触发。通常这一切透明地发生,用户不会察觉到。有一种情况watch可能丢失:之前对一个尚未建立的节点的设置了exists watch,如果断开期间该节点被建立或删除,那么此watch将丢失。
对于watch,zookeeper提供以下保证:
1.watch对于其他事件、watch、异步响应是有序的。zookeeper client library保证有序分发
2.客户端监视一个节点,总是先获取watch事件,再发现节点的数据变化。
3.watch事件的顺序对应于zookeeper服务所见的数据更新的顺序。
关于watch要记住的是:
1.watch是一次性触发的,如果获取一个watch事件并希望得到新变化的通知,需要重新设置watch
2.watch是一次性触发的并且在获取watch事件和设置新watch事件之间有延迟,所以不能可靠的观察到节点的每一次变化。要认识到这一点。
3.watch object只触发一次,比如,一个watch object被注册到同一个节点的getData()和exists(),节点被删除,仅对应于exists()的watch ojbect被调用
4.若与服务端断开连接,直到重连后才能获取watch事件。
ZooKeeper访问控制
zookeeper使用ACL(Access Control List)控制对节点的访问。ACL与Unix文件访问权限类似,采用权限位的形式控制节点的可操作类型,zookeeper没有拥有者和用户组的概念,使用ACL指定每个用户的访问权限。一个ACL只用于一个节点,注意不能用于该节点的子节点,即每个节点的访问权限由其自身的ACL决定。
每一个客户端连接在zookeeper内部有唯一的id,zookeeper将连接和id关联起来,客户端试图访问节点时,用id与节点ACL进行比对,以确定客户端的访问权限。
ACL由键值对构成,格式是(scheme:expression, perms)。expression内容格式特定于scheme。
ACL权限
ZooKeeper客户端开发
关于C开发,zookeeper提供了两个库:zookeeper_st(单线程库)与zookeeper_mt(多线程库)。zookeeper_st放弃了事件循环,可在事件驱动的应用程序中使用。而zookeeper_mt更加易用,与Java API类似,创建一个网络IO线程和一个事件分发线程,用来维护连接和执行回调。
在具体使用上,zookeeper_st仅提供了异步API与回调,用以集成至应用程序的事件循环。它只是为了支持pthread库不可用或不稳定的平台而存在,例如FreeBSD 4.x。除此以外的其他情况,应使用提供同步与异步两种API的zookeeper_mt。
采用zookeeper_mt库的zookeeper客户端使用了3个线程:
线程1是业务逻辑层,负责与用户直接交互,主要是zookeeper库提供的API
线程2是网络IO层,负责与zookeeper服务端的网络通信,包括发送业务逻辑层的API调用生成的请求数据、服务端的响应数据、服务端的watch事件数据
线程3是事件处理层,负责执行watch回调
Watch事件类型:
ZOO_CREATED_EVENT:节点创建事件,需要watch一个不存在的节点,当节点被创建时触发,此watch通过zoo_exists()设置
ZOO_DELETED_EVENT:节点删除事件,此watch通过zoo_exists()或zoo_get()设置
ZOO_CHANGED_EVENT:节点数据改变事件,此watch通过zoo_exists()或zoo_get()设置
ZOO_CHILD_EVENT:子节点列表改变事件,此watch通过zoo_get_children()或zoo_get_children2()设置
ZOO_SESSION_EVENT:会话失效事件,客户端与服务端断开或重连时触发
ZOO_NOTWATCHING_EVENT:watch移除事件,服务端出于某些原因不再为客户端watch节点时触发
watch事件与zookeeper读操作的对应关系图:
API:
typedef void (*watcher_fn)(zhandle_t *zh, int type, int state, const char *path,void *watcherCtx); watch回调函数,两种watch事件通知方式: 1.legacy:预先实现watch回调函数并将函数指针传入zookeeper_init,然后使用其他api设置watch 2.watcher object:一个函数指针和一个watcher上下文指针。watch触发时,两者结合调用。使用此类型,需使用'w'前缀api,如zoo_awexists、zoo_wget等 zhandle_t *zookeeper_init(const char *host, watcher_fn fn, int recv_timeout, const clientid_t *clientid, void *context, int flags); 创建与ZooKeeper服务端通信的句柄和对应于此句柄的会话。会话的创建过程是异步的,收到ZOO_CONNECTED_STATE状态事件后,确认会话成功建立。
int zookeeper_close(zhandle_t *zh); 关闭句柄,释放资源。调用函数后,会话将不可用,函数返回前会将未发送完毕的请求发送完,所以可能会引起阻塞。 对一个句柄来说,这个方法只许调用一次,调用多次将产生不确定的结果。对于调用过此方法的句柄,其他句柄操作也将产生不确定的结果。 const clientid_t *zoo_client_id(zhandle_t *zh); 返回客户端会话id,仅在与服务端连接正常时有效 int zoo_recv_timeout(zhandle_t *zh); 返回会话超时时间,仅在于服务端连接正常时有效,该值在与服务器重连后可能改变 const void *zoo_get_context(zhandle_t *zh); 返回句柄上下文 void zoo_set_context(zhandle_t *zh, void *context); 设置句柄上下文 watcher_fn zoo_set_watcher(zhandle_t *zh,watcher_fn newFn); 设置watch回调,返回之前的watch回调 struct sockaddr* zookeeper_get_connected_host(zhandle_t *zh, struct sockaddr *addr, socklen_t *addr_len); 返回服务端的网络地址(sockaddr结构),仅在与服务端连接正常是有效 int zookeeper_interest(zhandle_t *zh, int *fd, int *interest, struct timeval *tv); 暂时不太理解,可能是返回zookeeper在监听某个fd的读或者写 int zookeeper_process(zhandle_t *zh, int events); 暂时不太理解,通知zookeeper监听的事件发生了 typedef void (*void_completion_t)(int rc, const void *data); 函数类型定义,异步调用或连接断开或连接超时执行的回调类型 typedef void (*stat_completion_t)(int rc, const struct Stat *stat, const void *data); 同上,有返回值 typedef void (*data_completion_t)(int rc, const char *value, int value_len, const struct Stat *stat, const void *data); 同上,返回详细数据 typedef void (*strings_completion_t)(int rc, const struct String_vector *strings, const void *data); 同上 typedef void (*strings_stat_completion_t)(int rc, const struct String_vector *strings, const struct Stat *stat, const void *data); 同上 typedef void (*string_completion_t)(int rc, const char *value, const void *data); 同上 typedef void (*acl_completion_t)(int rc, struct ACL_vector *acl, struct Stat *stat, const void *data); 同上 int zoo_state(zhandle_t *zh); 返回句柄状态 int zoo_acreate(zhandle_t *zh, const char *path, const char *value, int valuelen, const struct ACL_vector *acl, int flags, string_completion_t completion, const void *data); 创建一个之前不存在的节点。如果设置ZOO_EPHEMERAL,客户端会话失效,节点将自动删除;如果设置ZOO_SEQUENCE,一个唯一的自动增加的序列号附加到路径名,序列号宽度是10个数字的宽度,不足用0填充 int zoo_adelete(zhandle_t *zh, const char *path, int version, void_completion_t completion, const void *data); 删除一个节点 int zoo_aexists(zhandle_t *zh, const char *path, int watch, stat_completion_t completion, const void *data); 检查一个节点是否存在 int zoo_awexists(zhandle_t *zh, const char *path, watcher_fn watcher, void* watcherCtx, stat_completion_t completion, const void *data); 检查一个节点是否存在,它允许指定一个watcher对象(一个函数指针watcher和对应的上下文watcherCtx),在watch解除时,此函数会调用,watcherCtx作为watcher的传入参数 int zoo_aget(zhandle_t *zh, const char *path, int watch, data_completion_t completion, const void *data); 获取节点数据(legacy方式)。completion是回调函数,其rc参数可能是以下参数:ZOK-完成,ZNONODE-节点不存在,ZNOAUTH-客户端无权限 int zoo_awget(zhandle_t *zh, const char *path, watcher_fn watcher, void* watcherCtx, data_completion_t completion, const void *data); 获取节点数据(watcher object方式)。 int zoo_aset(zhandle_t *zh, const char *path, const char *buffer, int buflen, int version, stat_completion_t completion, const void *data); 设置节点数据 int zoo_aget_children(zhandle_t *zh, const char *path, int watch, strings_completion_t completion, const void *data); 获取子节点列表(legacy) int zoo_awget_children(zhandle_t *zh, const char *path, watcher_fn watcher, void* watcherCtx, strings_completion_t completion, const void *data); 获取子节点列表(watcher object) int zoo_aget_children2(zhandle_t *zh, const char *path, int watch, strings_stat_completion_t completion, const void *data); 获取子节点列表,3.3.0版本加入(legacy) int zoo_awget_children2(zhandle_t *zh, const char *path, watcher_fn watcher, void* watcherCtx, strings_stat_completion_t completion, const void *data); 获取子节点列表,3.3.0版本加入(watcher object) int zoo_async(zhandle_t *zh, const char *path, string_completion_t completion, const void *data); Flush leader channel. 暂时不明确 int zoo_aget_acl(zhandle_t *zh, const char *path, acl_completion_t completion, const void *data); 获取节点的ACL。ACL描述了操作该节点所需具备的条件,即哪些人(id)具备哪些权限后才允许对节点执行哪些操作。 int zoo_aset_acl(zhandle_t *zh, const char *path, int version, struct ACL_vector *acl, void_completion_t, const void *data); 设置节点的ACL int zoo_amulti(zhandle_t *zh, int count, const zoo_op_t *ops, zoo_op_result_t *results, void_completion_t, const void *data); 以原子方式执行一系列操作 const char* zerror(int c); 返回错误信息 int zoo_add_auth(zhandle_t *zh, const char* scheme, const char* cert, int certLen, void_completion_t completion, const void *data); 为应用程序指定证书。调用此函数用于认证的证书。服务端用scheme指定的安全服务对客户端连接进行认证。 如果认证失败,将与服务端断开连接,watcher触发,状态码是ZOO_AUTH_FAILED_STATE int is_unrecoverable(zhandle_t *zh); 检查zookeeper连接是否可恢复 void zoo_set_debug_level(ZooLogLevel logLevel); 设置调试级别 void zoo_set_log_stream(FILE* logStream); 设置用于记录日志的文件流。默认使用stderr。若logStream为NULL,则使用默认值stderr。 void zoo_deterministic_conn_order(int yesOrNo); 用于启用或停用quarum端点的随机化排序,通常仅在测试时使用。 如果非0,使得client连接到quarum端按照被初始化的顺序。 如果是0,zookeeper_init将变更端点顺序,使得client连接分布在更优的端点上。 int zoo_create(zhandle_t *zh, const char *path, const char *value, int valuelen, const struct ACL_vector *acl, int flags, char *path_buffer, int path_buffer_len); 同步建立节点 int zoo_delete(zhandle_t *zh, const char *path, int version); 同步删除节点 int zoo_exists(zhandle_t *zh, const char *path, int watch, struct Stat *stat); 同步检查节点是否存在 int zoo_wexists(zhandle_t *zh, const char *path, watcher_fn watcher, void* watcherCtx, struct Stat *stat); 同步检查节点是否存在(watcher object) int zoo_get(zhandle_t *zh, const char *path, int watch, char *buffer, int* buffer_len, struct Stat *stat); 同步获取节点数据(legacy) int zoo_wget(zhandle_t *zh, const char *path, watcher_fn watcher, void* watcherCtx, char *buffer, int* buffer_len, struct Stat *stat); 同步获取节点数据(watcher object) int zoo_set(zhandle_t *zh, const char *path, const char *buffer, int buflen, int version); 同步设置节点数据 int zoo_set2(zhandle_t *zh, const char *path, const char *buffer, int buflen, int version, struct Stat *stat); 同步设置节点数据并返回当前节点的stat信息 int zoo_get_children(zhandle_t *zh, const char *path, int watch, struct String_vector *strings); 同步获取子节点列表(legacy) int zoo_wget_children(zhandle_t *zh, const char *path, watcher_fn watcher, void* watcherCtx, struct String_vector *strings); 同步获取子节点列表(watcher object) int zoo_get_children2(zhandle_t *zh, const char *path, int watch, struct String_vector *strings, struct Stat *stat); 同步获取子节点列表并返回当前节点的stat信息(legacy),3.3.0版本加入 int zoo_wget_children2(zhandle_t *zh, const char *path, watcher_fn watcher, void* watcherCtx, struct String_vector *strings, struct Stat *stat); 同步获取子节点列表并返回当前节点的stat信息(watcher object),3.3.0版本加入 int zoo_get_acl(zhandle_t *zh, const char *path, struct ACL_vector *acl, struct Stat *stat); 同步获取节点ACL int zoo_set_acl(zhandle_t *zh, const char *path, int version, const struct ACL_vector *acl); 同步设置节点ACL int zoo_multi(zhandle_t *zh, int count, const zoo_op_t *ops, zoo_op_result_t *results); 同步以原子方式执行一系列操作