大家好,我是程序员xiao熊,今天分享的是ZooKeeper开发手册官方文档的最后一部分内容了,主要包括zookeeper一致性保证、支持的语言绑定以及常见的故障说明;之后会继续分享Zookeeper实践相关的内容,欢迎大家继续关注;之前也分享过Zookeeper相关的文档,可参考如下链接:,下面我们就来说一说关于zookeeper的基本原理和使用?我们一起去了解并探讨一下这个问题吧!
zookeeper的基本原理和使用
大家好,我是程序员xiao熊,今天分享的是ZooKeeper开发手册官方文档的最后一部分内容了,主要包括zookeeper一致性保证、支持的语言绑定以及常见的故障说明;之后会继续分享Zookeeper实践相关的内容,欢迎大家继续关注;之前也分享过Zookeeper相关的文档,可参考如下链接:
- Zookeeper概览-官方文档翻译
- Zookeeper开发者指南——Zookeeper数据模型
- Zookeeper开发指南——Zookeeper会话
- Zookeeper开发指南——Zookeeper监听
- Zookeeper开发指南——权限控制&身份验证
ZooKeeper是一个高性能、可扩展的服务。Zookeeper读和写操作都很快,但是读要比写更快。之所以读比写快是因为在一些读取的场景下,ZooKeeper可能会提供较老的数据,而之所以会提供较老的数据又是因为ZooKeeper的一致性, Zookeeper一致性保证依靠以下几个特性:
- 顺序一致性(Sequential Consistency) : 客户端发送的更新操作将会按照发送的顺序在服务端生效
- 原子性(Atomicity) : 客户端发送的操作,要么全部成功,要么全部失败;没有中间状态
- 统一的系统镜像(Single System Image) : 无论客户端连接服务端的哪台服务器,看见的视图都是一样的;客户端不会看见另一个版本的服务端视图,即使客户端当前连接的服务端宕机,转而连接其他服务器,看见的服务端视图都是一致的
- 及时性(Timeliness) : 保证系统的客户端视图在一定的时间范围内(大约几十秒)是最新的。客户端将在此范围内看到Zookeeper发生的变更,或者客户端将检测到服务中断。使用这些一致性保证可以很容易地在 ZooKeeper 客户端构建更高级别的功能,例如领导者选举、屏障、队列和读/写可撤销锁(无需添加到 ZooKeeper)
- 可靠性(Reliability) : 只要操作在服务端生效,操作产生的结果将会进行持久化,直到下一次的更新操作为止;这个特性还有以下两个推论:
第一:如果客户端获得成功返回的code码,则客户端发出的更新操作生效。 在某些场景下,例如通信错误、超时等,客户端将不知道更新操作是否已已经生效。Zookeeper采取措施尽量减少失败,但始终只有成功返回后才能保证数据会持久化。这在 Paxos 中称为单调性条件
第二:客户端通过读请求或者成功的更新操作能看到之前的任何更新操作的结果,并且在服务器故障恢复时这些更新操作的结果将永远不会回滚
注意事项:(Note)有时开发人员会错误地认为 ZooKeeper 实际上没有做出另一种保证。这就是:*跨客户端同一时刻能看见同样的视图*:ZooKeeper 不保证在每个实例中,两个不同的客户端在同一时刻将具有相同的 ZooKeeper 数据视图。由于网络延迟等因素,一个客户端可能会在另一个客户端收到更改通知之前执行更新。 考虑两个客户端 A 和 B 的场景,如果客户端 A 将 znode /a 的值从 0 设置为 1,然后告诉客户端 B 读取 /a,客户端 B 可能会读取旧值 0,具体取决于B客户端连接的服务器。 如果要保证客户端 A 和客户端 B 读取相同的值,则客户端 B 应该在执行读取之前从 ZooKeeper API 方法调用 sync() 方法,保证数据在Zookeeper服务器之间进行同步。 因此,ZooKeeper 本身并不能保证更新操作在所有服务器之间同步发生,但是利用ZooKeeper 原语可以实现同步功能。
绑定(Bindings)Zookeeper客户端库支持两种语言:C和JAVA; 以下将会详细描述相关内容:
Java(Java Binding)组成Zookeeper的Java 绑定的有两个包:org.Apache.zookeeper 和 org.apache.zookeeper.data。其余包在内部使用或者是服务器实现的一部分。 org.apache.zookeeper.data 包由仅用作容器的类组成。
Zookeeper Java 客户端使用的主要类是 Zookeeper 类。(3.40版本之前)它的两个构造函数的区别仅在于会话 ID 和密码两个参数。Zookeeper 支持跨进程实例的会话恢复。Java 程序可以将其会话 ID 和密码保存到稳定的存储介质上、重新启动和恢复该程序的早期实例使用的会话。
创建 ZooKeeper 对象时,也会创建两个线程:一个 IO 线程和一个事件线程。所有 IO 都发生在 IO 线程上(使用 Java NIO)。所有事件回调都发生在事件线程上。会话维护是在 IO 线程上完成的,例如:重新连接到 ZooKeeper 服务器和维护心跳。同步方法的响应也在 IO 线程中处理。对异步方法和监听事件的所有响应都在事件线程上处理。这种设计有几点需要注意:
- 异步调用和监听回调的所有完成操作都将按顺序进行,一次一个。调用者可以进行任何他们希望的处理,但在此期间不会处理其他回调
- 回调不会阻塞 IO 线程的处理或同步调用的处理
- 同步调用可能不会以正确的顺序返回。例如,假设客户端执行以下处理:发出节点 /a 的异步读取(watch 设置为 true ),然后在读取的完成回调中执行 /a 的同步读取(也许不是好的做法,但也不违法,这是一个简单的例子)。请注意,如果在异步读取和同步读取之间对 /a 进行了更改,客户端库将接收到 /a 的监听事件,但是由于完成回调阻塞了事件队列,同步读取将在处理更改操作的监听事件之前就能够读取到/a 的新值
- 最后,与关闭相关的规则很简单:一旦 ZooKeeper 对象关闭或收到严重错误事件(SESSION_EXPIRED 和 AUTH_FAILED),ZooKeeper 对象就会失效。关闭时,两个线程关闭,并且对 zookeeper 句柄的任何进一步访问都是未定义的行为,应该避免。
以下列表包含了 Java 客户端的配置属性。开发者可以使用 Java 系统属性设置这些属性。有关服务器属性,请查看管理员指南的服务器配置部分。ZooKeeper Wiki 也有关于 ZooKeeper SSL 和 ZooKeeper 的 SASL 身份验证的相关说明。
- zookeeper.sasl.client : 将该值设置为 false 以禁用 SASL 身份验证。默认为true
- zookeeper.sasl.clientconfig : 指定 JAAS 登录文件中的context键。 默认为“Client”
- zookeeper.server.principal : 启用 Kerberos 身份验证之后,当连接到 zookeeper 服务器,指定客户端用于身份验证的服务器主体。如果提供了此配置属性,则Zookeeper 客户端将不会使用以下任何参数来确定服务器主体:zookeeper.sasl.client.username, zookeeper.sasl.client.canonicalize.hostname, zookeeper.server.realm;需要注意的是当前参数只在3.5.7以上以及3.6.0以上的版本有效
- zookeeper.sasl.client.username : 在旧版本中,主体分为三个部分:primary、instance和realm。 典型的 Kerberos V5 主体的格式是 primary/instance@REALM。 zookeeper.sasl.client.username 指定服务器主体的primary部分, 默认为“Zookeeper”。 instance部分是从服务器 IP 派生的。 最后server的principal是username/IP@realm,其中username是zookeeper.sasl.client.username的值,IP是服务器IP,realm是zookeeper.server.realm的值。
- zookeeper.sasl.client.canonicalize.hostname : 如果没有提供 zookeeper.server.principal 参数,Zookeeper 客户端将尝试确定 ZooKeeper 服务器主体的“instance”(主机)部分。 首先,客户端将提供的主机名作为 ZooKeeper 服务器连接字符串。然后客户端尝试通过获取属于该地址的完全限定域名来“规范化”该地址。开发者可以通过设置禁用此“规范化”:zookeeper.sasl.client.canonicalize.hostname=false
- zookeeper.server.realm : 服务器主体的realm部分。 默认情况下,它是客户端主体realm
- zookeeper.disableAutoWatchReset : 此开关控制是否启用自动的监听重置。 默认情况下,客户端在会话重新连接期间自动重置监听,此选项允许客户端通过将 zookeeper.disableAutoWatchReset 设置为 true 来关闭此行为
- zookeeper.client.secure : 3.5.5 新功能:如果要连接到服务器安全客户端端口,则需要在客户端将此属性设置为 true。 这将使用具有指定凭据的 SSL 连接到服务器。 请注意,它需要 Netty 客户端
- zookeeper.clientCnxnSocket : 指定要使用的 ClientCnxnSocket。可能的值为 org.apache.zookeeper.ClientCnxnSocketNIO 和 org.apache.zookeeper.ClientCnxnSocketNetty 。 默认为 org.apache.zookeeper.ClientCnxnSocketNIO 。如果要连接到服务器的安全客户端端口,则需要在客户端将此属性设置为 org.apache.zookeeper.ClientCnxnSocketNetty
- zookeeper.ssl.keyStore.location and zookeeper.ssl.keyStore.password : 3.5.5 中的新增功能:指定 JKS 的文件路径,其中包含用于 SSL 连接的本地凭据,以及解锁文件的密码
- zookeeper.ssl.trustStore.location and zookeeper.ssl.trustStore.password : 3.5.5 中的新增功能:指定 JKS 的文件路径,其中包含用于 SSL 连接的远程凭据,以及解锁文件的密码
- zookeeper.ssl.keyStore.type and zookeeper.ssl.trustStore.type: 3.5.5 中的新功能:指定用于建立与 ZooKeeper 服务器的 TLS 连接的密钥/信任存储文件的文件格式。 可用的值为:JKS、PEM、PKCS12 或 null(按文件名检测)。 默认值:null。 3.6.3、3.7.0 中的新功能:添加了 BCFKS 格式。
- jute.maxbuffer : 在客户端,它指定来自服务器的传入数据的最大值。 默认值为 0xfffff(1048575) 字节,或略低于 1M。 这是一个健全的检查。 ZooKeeper 服务器旨在存储和发送以千字节为单位的数据。 如果传入的数据长度大于此值,则会引发 IOException。 客户端的这个值要和服务端保持一致(在客户端设置 System.setProperty("jute.maxbuffer", "xxxx")),否则会出问题
- zookeeper.kinit : 指定 kinit 二进制文件的路径。 默认为“/usr/bin/kinit”
C 语言具有单线程和多线程库。多线程库最容易使用,并且与 Java API 最相似。该库将创建一个 IO 线程和一个事件调度线程,用于处理连接维护和回调。通过暴露多线程库中使用的event loop,单线程库允许 ZooKeeper 在事件驱动的应用程序中使用。
该软件包包括两个共享库:zookeeper_st 和 zookeeper_mt。 前者仅提供用于集成到应用程序event loop中的异步 API 和回调。 这个库存在的唯一原因是在pthread库不可用或不稳定(比如FreeBSD 4.x)的情况下支持平台。 在所有其他情况下,应用程序开发人员应与 zookeeper_mt 链接,因为它包括对 Sync 和 Async API 的支持。
安装(Installation)如果开发者是从Apache存储库获取的代码中构建客户端,请遵循下面列出的步骤。如果是从apache下载的项目源代码包中构建,请跳过步骤3。
- 从 ZooKeeper 顶级目录 (.../trunk) 运行 ant compile_jute。 这将在 .../trunk/zookeeper-client/zookeeper-client-c 下创建一个名为“generated”的目录
- 切换到目录*.../trunk/zookeeper-client/zookeeper-client-c* 并运行 autoreconf -if 来引导 autoconf、automake 和 libtool。确保已安装 autoconf 的2.59 或更高版本。跳至步骤 4
- 如果您是从项目源代码包构建,请解压缩源 tarball 并切换到* zookeeper-x.x.x/zookeeper-client/zookeeper-client-c* 目录
- 运行 ./configure <your-options> 以生成 makefile。以下是配置实用程序支持的一些选项,在此步骤中可能很有用:
- --enable-debug 启用优化并启用调试信息编译器选项,默认禁用
- --without-syncapi 禁用同步 API 支持;zookeeper_mt 库不会被构建,默认启用
- --disable-static 不要构建静态库,默认启用
- --disable-shared 不要构建共享库,默认启用
有关运行配置的通用信息,请参阅“INSTALL”。 1. 运行 make 或 make install 来构建库并安装它们。 2. 要为 ZooKeeper API 生成 doxygen 文档,请运行 make doxygen-doc。 所有文档都将放置在名为 docs 的新子文件夹中。 默认情况下,此命令仅生成 HTML。 有关其他文档格式的信息,请运行 ./configure –help
构建 C 客户端(Building Your Own C Client)为了能够在开发者的应用程序中使用 ZooKeeper C API,请必须记住以下两点
- 引入 ZooKeeper: #include <zookeeper/zookeeper.h>
- 如果开发者正在构建多线程客户端,请使用 -DTHREADED 编译器标识进行编译以启用库的多线程版本,然后链接 zookeeper_mt 库。 如果开发者正在构建单线程客户端,请不要使用 -DTHREADED 进行编译,并确保链接到 the_zookeeper_st_library
有关 C 客户端实现的示例,请参见 .../trunk/zookeeper-client/zookeeper-client-c/src/cli.c
构建块:ZooKeeper 操作指南(Building Blocks: A Guide to ZooKeeper Operations)本节调查开发人员可以对 ZooKeeper 服务器执行的所有操作。 它是比本手册中早期概念章节更低级别的信息,但比 ZooKeeper API 更高级别。 它涵盖了以下主题:
处理错误(Handling Errors)Java和C客户端都可能报告错误。Java客户端通过抛出KeeperException来实现,在异常上调用code()将返回特定的错误代码。C客户端返回一个在enum ZOO_ERRORS中定义的错误码。API回调说明了这两种语言的结果代码。有关可能的错误及其含义的详细信息,请参阅API文档(javadoc用于Java, doxygen用于C)。
连接Zookeeper(Connecting to ZooKeeper)在开始之前,开发者必须设置一个正在运行的Zookeeper服务器,以便可以开始开发客户端。对于C客户端,我们将使用多线程库(zookeeper_mt)和一个简单的用C编写的例子。要建立与Zookeeper服务器的连接,我们使用C API - zookeeper_init,其签名如下:
int zookeeper_init(const char *host, watcher_fn fn, int recv_timeout, const clientid_t *clientid, void *context, int flags);
· *host : 连接zookeeper服务器的字符串,格式为host:port。如果有多个服务器,请使用逗号分隔。如:“127.0.0.1:2181,127.0.0.1:3001 127.0.0.1:3002”
· fn : 监听功能,用于在触发通知时处理事件。
· recv_timeout : 会话过期时间(毫秒)
· *clientid : 我们可以为新会话指定0。如果会话之前已经建立,我们可以提供客户端ID,它将重新连接到之前的会话。
· *context : 可以与zkhandle_t处理程序关联的上下文对象。如果不需要使用,可以将它设置为0
· flags : 在初始化过程中,可以让它为0
我们将演示客户端在成功连接后输出“Connected to Zookeeper”,否则将输出错误消息。让我们调用以下代码zkClient.cc :
#include <stdio.h>
#include <zookeeper/zookeeper.h>
#include <errno.h>
using namespace std;
// Keeping track of the connection state
static int connected = 0;
static int expired = 0;
// *zkHandler handles the connection with Zookeeper
static zhandle_t *zkHandler;
// watcher function would process events
void watcher(zhandle_t *zkH, int type, int state, const char *path, void *watcherCtx)
{
if (type == ZOO_SESSION_EVENT) {
// state refers to states of zookeeper connection.
// To keep it simple, we would demonstrate these 3: ZOO_EXPIRED_SESSION_STATE, ZOO_CONNECTED_STATE, ZOO_NOTCONNECTED_STATE
// If you are using ACL, you should be aware of an authentication failure state - ZOO_AUTH_FAILED_STATE
if (state == ZOO_CONNECTED_STATE) {
connected = 1;
} else if (state == ZOO_NOTCONNECTED_STATE ) {
connected = 0;
} else if (state == ZOO_EXPIRED_SESSION_STATE) {
expired = 1;
connected = 0;
zookeeper_close(zkH);
}
}
}
int main(){
zoo_set_debug_level(ZOO_LOG_LEVEL_DEBUG);
// zookeeper_init returns the handler upon a successful connection, null otherwise
zkHandler = zookeeper_init("localhost:2181", watcher, 10000, 0, 0, 0);
if (!zkHandler) {
return errno;
}else{
printf("Connection established with Zookeeper. \n");
}
// Close Zookeeper connection
zookeeper_close(zkHandler);
return 0;
}
使用前面提到的多线程库编译代码.
> g -Iinclude/ zkClient.cpp -lzookeeper_mt -o Client
执行以下命令
> ./Client
如果连接成功,在输出中应该看到“Connected to Zookeeper”以及Zookeeper的DEBUG消息
陷阱:常见问题和故障排除(Gotchas: Common Problems and Troubleshooting)现在对ZooKeeper已经有一些了解了。Zookeeper快速,简单,能够让开发者的应用程序工作,但是还是会遇见一些问题;以下是ZooKeeper用户可能会遇到的一些问题:
- 如果在使用监听,则必须查找已连接的监听事件。当 ZooKeeper 客户端与服务器断开连接时,客户端将不会收到更改通知,直到重新连接。 如果正在监听一个 znode 是否存在,如果 znode 在断开连接时被创建和删除,客户端将错过该事件
- 测试ZooKeeper服务器故障的场景。只要大多数服务器处于可用状态,ZooKeeper服务就可以对外提供服务。问题是:开发者的应用程序能够处理服务器故障吗?在实际场景中,客户端与Zookeeper的连接可能会中断。(ZooKeeper服务器故障、网络分区是导致连接丢失的常见原因)Zookeeper客户端库负责恢复连接并让开发者知道发生了什么,但必须确保开发者恢复了应用程序的状态和任何未完成的失败请求。在测试实验室中检查是否正确,而不是在生产环境中——使用由几个服务器组成的Zookeeper服务进行测试,并让它们重新启动
- 客户端使用的ZooKeeper服务器列表必须与每个ZooKeeper服务器对应的ZooKeeper服务器列表匹配。如果客户端列表是真实ZooKeeper服务器列表的子集,虽然不是最理想的,但如果客户端列出的ZooKeeper服务器不在ZooKeeper集群中,就不能正常工作
- 注意存放事务日志的地方。ZooKeeper中对性能最关键的部分是事务日志。ZooKeeper必须在返回响应之前将事务同步到媒介。专用的事务日志设备是保持良好性能的关键。将日志放在繁忙的设备上将会对性能产生不利影响。如果开发者只有一个存储设备,那么将跟踪文件放在NFS上并增加snapshotCount;它不能消除问题,但可以减轻问题
- 正确设置Java最大堆的值。避免内存页的交换是非常重要的。不必要地使用磁盘几乎肯定会使性能下降到不可接受的程度。记住,在ZooKeeper中,所有的请求都是有序的,所以如果一个请求命中磁盘,其他所有排队的请求都会命中磁盘。为了避免交换,尝试将堆大小设置为机器物理内存的数量,减去操作系统和缓存所需的数量。此外还需要进行堆大小的负载测试,保证配置的值没有问题。如果由于某些原因不能进行交换,请在评估时保守一点,并选择一个远低于会导致机器进行交换的限制的数字。例如,在4G机器上,3G的堆大小是一个保守的评估。
以上就是为大家分享的内容;如果有翻译的不正确的地方,欢迎大家在评论区里面留言,后续也会分享Zookeeper实践相关的文章以及其他技术的文章
,