Zookeeper C API 错误码介绍 ZOO_ERRORS
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) |
问题与解决
一、 关于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基本上所有的时间都是这个时间的整数倍。 |
四、 zoo_get_children内存泄露问题
问题描述:
调用zoo_get_children函数出现内存泄露问题。
问题分析:
通过查看代码发现问题在于 ZOOAPI int zoo_get_children(zhandle_t *zh, const char *path, int watch,struct String_vector *strings), 该API中String_vector *strings结构体定义如下:
struct String_vector
{
int32_t count;
char * *data;
};
Zookeeper API将getchildren结果通过String_vector结构体返回时,malloc分配内存,将子节点所有目录存放在data中,而释放内存需要有客户端来做处理。
问题解决:
调用zoo_get_children(zh, path, watch, strings);需要通过调用zookeper提供的释放内存的方法:deallocate_String_vector(strings)。
int deallocate_String_vector(struct String_vector *v){
if (v->data)
{
int32_t i;
for(i = 0; i < v->count; i++)
{
deallocate_String(&v->data[i]);
}
free(v->data);
v->data = 0;
}
return 0;
六、 zookeeper连接数问题
问题描述:
zookeeper服务器都运行正常,而客户端连接异常。
问题分析:
这是由于zookeeper client连接数已经超过了zookeeper server获取的配置最大连接数。所以导致zookeeper client连接失败。
解决方法:
修改zookeeper安装目录下 conf/zoo.cfg文件。将maxClientCnxns参数改成更大的值。
七、 zookeeper服务器安装相关问题
问题一:是否可以只装一个zookeeper服务器。
问题解答:
可以安装,此时没有leader,follow,此时zookeeper server状态为standalone。
./zkServer.sh status JMX enabled by default Using config: /home/bbs/zookeeper-3.4.5/bin/../conf/zoo.cfg Mode: standalone |
关于zoo.cfg配置:
tickTime=2000initLimit=10 syncLimit=5 dataDir=../data clientPort=4181 |
问题二:开发没有足够机器,一台机子上是否装三个zookeeper服务器集群。
问题解答:
这种安装模式只能说是一种伪集群模式。三个zookeeper服务器都安装在同一个服务器(platform)上,需保证clientPort不相同。
将zookeeper安装包分别解压在三个目录server1,server2,server3下,配置文件zoo.cfg
Server1配置文件 zoo.cfg,server1在data目录下增加文件myid内容为1。
dataDir=../datadata LogDir=../dataLog clientPort=5181 server.1=platform:5888:6888 server.2= platform:5889:6889 server.3= platform:5890:6890 |
Server2配置文件 zoo.cfg,server1在data目录下增加文件myid内容为2。
dataDir=../datadata LogDir=../dataLog clientPort=6181 server.1=platform:5888:6888 server.2= platform:5889:6889 server.3= platform:5890:6890 |
Server3配置文件 zoo.cfg,server1在data目录下增加文件myid内容为3。
dataDir=../datadata LogDir=../dataLog clientPort=7181 server.1=platform:5888:6888 server.2= platform:5889:6889 server.3= platform:5890:6890 |
server.id=host:port:port : 表示了不同的zookeeper服务器的自身标识,作为集群的一部分,每一台服务器应该知道其他服务器的信息。用户可以从“server.id=host:port:port” 中读取到相关信息。在服务器的data(dataDir参数所指定的目录)下创建一个文件名为myid的文件,这个文件的内容只有一行,指定的是自身的id值。比如,服务器“1”应该在myid文件中写入“1”。这个id必须在集群环境中服务器标识中是唯一的,且大小在1~255之间。这一样配置中,zoo1代表第一台服务器的IP地址。第一个端口号(port)是从follower连接到leader机器的端口,第二个端口是用来进行leader选举时所用的端口。所以,在集群配置过程中有三个非常重要的端口:clientPort:2181、port:2888、port:3888。