# 用法

# 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 。
    • 临时节点不支持创建子节点。
  • 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
      
  • 支持给 znode 设置配额(quota),限制节点数量、大小。

    • quota 并不具有强制性,超过限制时只会打印一条 WARN 级别的日志:Quota exceeded
    • 给一个节点设置了 quota 之后,不允许再给它的祖先节点或子孙节点设置 quota 。
    • 所有节点的 quota 信息都记录在 /zookeeper/quota/<path>/zookeeper_limits 中。
      • 当节点被删除时,其 quota 信息并不会自动删除。

# 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 机制,用于当某个节点发生某种事件时,通知客户端。

    • 用法:
      1. 客户端定义一个 watcher 类,注册到 zk ,监听某个事件。
      2. zk 记录了所有 watcher 。当相应的事件发生时,就通知客户端,并删除 watcher 。
    • 用 Python 的 kazoo 库注册 watcher 的示例:
      @client.DataWatch('/test')  # 注册一个 watcher ,当目标节点的数据变化时,调用该函数
      def fun1(data, stat):
          print('data changed: {}'.format(data))
      
  • 一种基于 zk 实现分布式锁的方案:

    1. 在 zk 中创建一些代表某种资源的 znode ,比如 /mysql/table1/write 。
    2. 多个业务程序同时申请占用某种资源时,需要分别作为客户端连接到 zk ,在相应的 znode 下创建一个子节点,带顺序编号。比如 create -s /mysql/table1/write 。
    3. 每个客户端检查目标 znode 下的所有子节点:
      • 如果自己创建的子节点编号最小,则代表自己获得了锁,有权占用资源。等使用完资源之后,再删除自己的子节点,代表释放锁。
      • 否则,注册 watcher ,监听前一个编号的子节点,等它被删除时,代表自己获得了锁。

# ♢ 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)