# 用法
# znode
zk 数据库以键值对的形式存储数据。 key 称为寄存器、znode ,
- znode 以目录树的形式命名,通过从 / 开始的绝对路径进行定位。
- 每个 znode 可以存储一段任意格式的文本数据,比如一个字符串、一些键值对、一个 JSON 。
- zk 将全部 znode 数据放在内存中,因此读取速度快。每次对 znode 进行写操作时,会备份写操作到事务日志中。
- znode 的读写操作具有顺序一致性、原子性,不允许并发写入。
- zk 不擅长担任像 Redis 的通用数据库,建议只存储少量的、关键的数据,比如每个 znode 低于 1KB 。
create -e
命令会在创建 znode 时,将它声明为临时节点(Ephemeral)。- 客户端连接到 zk server 时,会创建一个会话(session)。
- 每个会话会被分配一个唯一的 session ID 。
- 当客户端断开连接一定时间之后,zk 会认为该 session 失效,删除该 session 及其创建的临时节点。如果客户端重新连接,则需要创建一个新 session 。
- 临时节点不支持创建子节点。
- 客户端连接到 zk server 时,会创建一个会话(session)。
create -s
命令会在创建 znode 时,给 znode 名称添加一个编号作为后缀。- 此编号为 10 位十进制数,从 0 开始递增。
- 编号与当前父节点关联,分配给各个子节点使用。
- 即使子节点被删除,编号也会继续递增。
- 可以与临时节点搭配使用,创建一组按顺序编号的临时节点:
[zk: localhost:2181(CONNECTED) 0] create -e -s /test/a Created /test/a0000000000 [zk: localhost:2181(CONNECTED) 1] create -e -s /test/a Created /test/a0000000001 [zk: localhost:2181(CONNECTED) 2] create -e -s /test/b Created /test/b0000000002
- 此编号为 10 位十进制数,从 0 开始递增。
支持给 znode 设置配额(quota),限制节点数量、大小。
- quota 并不具有强制性,超过限制时只会打印一条 WARN 级别的日志:
Quota exceeded
- 给一个节点设置了 quota 之后,不允许再给它的祖先节点或子孙节点设置 quota 。
- 所有节点的 quota 信息都记录在
/zookeeper/quota/<path>/zookeeper_limits
中。- 当节点被删除时,其 quota 信息并不会自动删除。
- quota 并不具有强制性,超过限制时只会打印一条 WARN 级别的日志:
# zkCli
zk 的 bin 目录下自带了多个 shell 脚本。执行以下脚本可进入 zk 的命令行终端:
bin/zkCli.sh -server [host][:port] # 连接到指定的 zk server 。默认是 localhost:2181
常用命令:
connect [host][:port] # 连接到指定的 zk server version # 显示 zkCli 的版本 ls <path> # 显示指定路径下的所有 znode 。path 中不支持通配符 * -R # 递归显示子节点 create <path> [data] [acl] # 创建 znode -e # 创建临时节点 -s # 给 znode 名称添加一个编号作为后缀 delete <path> # 删除 znode 。存在子节点时不允许删除,会报错:Node not empty deleteall <path> # 删除 znode 及其所有子节点 get <path> # 读取 znode 中的数据 set <path> <data> # 设置 znode 中的数据 stat <path> # 显示 znode 的状态 listquota <path> # 查看某个节点的配额 setquota <path> # 设置配额 -b <bytes> # 限制该节点及子孙节点的总大小 -n <num> # 限制该节点及子孙节点的总数 delquota <path> # 删除配额
例:创建 znode
[zk: localhost:2181(CONNECTED) 0] create /test Created /test [zk: localhost:2181(CONNECTED) 1] get /test null
- 命令提示符
[zk: localhost:2181(CONNECTED) 1]
中的数字编号表示这是当前终端执行的第几条命令,从 0 开始递增。
- 命令提示符
例:查看 znode 的状态
[zk: localhost:2181(CONNECTED) 0] stat /test cZxid = 0x1bd # 创建该节点时的事务编号 ctime = Wed Jul 28 10:09:01 UTC 2021 # 创建该节点时的 UTC 时间 mZxid = 0x1bd mtime = Wed Jul 28 10:09:01 UTC 2021 pZxid = 0x1bd cversion = 0 dataVersion = 0 # 该节点中的数据版本。每次 set 都会递增,即使数据没有变化 aclVersion = 0 ephemeralOwner = 0x0 # 对于临时节点,该参数用于存储 Session ID 。对于其它节点,该参数的值为 0 dataLength = 0 # 该节点中的数据长度 numChildren = 0 # 子节点的数量
# watch
zk 提供了 watch 机制,用于当某个节点发生某种事件时,通知客户端。
- 用法:
- 客户端定义一个 watcher 类,注册到 zk ,监听某个事件。
- zk 记录了所有 watcher 。当相应的事件发生时,就通知客户端,并删除 watcher 。
- 用 Python 的 kazoo 库注册 watcher 的示例:
@client.DataWatch('/test') # 注册一个 watcher ,当目标节点的数据变化时,调用该函数 def fun1(data, stat): print('data changed: {}'.format(data))
- 用法:
一种基于 zk 实现分布式锁的方案:
- 在 zk 中创建一些代表某种资源的 znode ,比如 /mysql/table1/write 。
- 多个业务程序同时申请占用某种资源时,需要分别作为客户端连接到 zk ,在相应的 znode 下创建一个子节点,带顺序编号。比如 create -s /mysql/table1/write 。
- 每个客户端检查目标 znode 下的所有子节点:
- 如果自己创建的子节点编号最小,则代表自己获得了锁,有权占用资源。等使用完资源之后,再删除自己的子节点,代表释放锁。
- 否则,注册 watcher ,监听前一个编号的子节点,等它被删除时,代表自己获得了锁。
# import kazoo
:Python 的第三方库,提供了 Zookeeper 客户端的功能。
- 官方文档 (opens new window)
- 安装:
pip install kazoo
- 例:
>>> from kazoo.client import KazooClient >>> zk.get_children('/') # 获取子节点,返回一个 list ,包含各个子节点的名称 ['config', 'zookeeper', ...] >>> zk.exists('/test') # 判断节点是否存在。存在则返回其 stat ,不存在则返回 None >>> zk.create('/test', b'Hello') # 创建节点,值必须为 bytes 类型 '/test' >>> zk.set('/test', b'Hello') # 设置节点的值,返回其 stat ZnodeStat(czxid=25769805253, mzxid=25769805254, ...) >>> data, stat = zk.get('/test') # 读取节点的值和 stat >>> zk.delete('/test') # 删除节点 True >>> zk.delete('/test') # 操作不存在的节点时会报错 kazoo.exceptions.NoNodeError
- 例:连接时进行 SASL 认证,需要安装
pip install pure-sasl
from kazoo.client import KazooClient sasl_options = { 'mechanism': 'DIGEST-MD5', 'username': 'client', 'password': '******' } zk = KazooClient(hosts='10.0.0.1:2181', timeout=3, sasl_options=sasl_options)