ZooKeeper

ZooKeeper 数据模型与集群原理 ZooKeeper 为分布式应用提供协调服务,常用于注册发现、配置、选主和锁。它维护树形 znode,支持持久节点、临时节点及顺序节点。临时节点随会话结束删除;网络短暂中断不等于会话立即过期。 客户端请求可连接任意 Server,写入通过 Leader 排序并

ZooKeeper

数据模型与集群原理

ZooKeeper 为分布式应用提供协调服务,常用于注册发现、配置、选主和锁。它维护树形 znode,支持持久节点、临时节点及顺序节点。临时节点随会话结束删除;网络短暂中断不等于会话立即过期。

客户端请求可连接任意 Server,写入通过 Leader 排序并由多数派确认。3 个投票节点可容忍 1 个故障,5 个可容忍 2 个;偶数节点通常不会增加故障容忍数。Observer 不参与投票,不能计入 quorum。

传统 Watch 是一次性通知,触发后需要重新注册并重新读取状态;支持持久 Watch 的版本也需要处理断线、会话过期和重新同步。不要把 Watch 当成不会丢失的消息队列。

Kubernetes 三节点部署

下面基于 ZooKeeper 3.9 系列,使用官方镜像、Headless Service、StatefulSet 和每节点独立 PVC。生产固定经过审核的补丁版本或 digest。要求至少 3 台可调度工作节点和默认 StorageClass;没有默认存储类时在 PVC 中指定 storageClassName。

保存为 zookeeper.yaml:

apiVersion: v1
kind: Service
metadata:
  name: zk-hs
spec:
  clusterIP: None
  publishNotReadyAddresses: true
  selector:
    app: zk
  ports:
    - name: client
      port: 2181
    - name: quorum
      port: 2888
    - name: election
      port: 3888
---
apiVersion: v1
kind: Service
metadata:
  name: zk-cs
spec:
  selector:
    app: zk
  ports:
    - name: client
      port: 2181
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: zk
spec:
  maxUnavailable: 1
  selector:
    matchLabels:
      app: zk
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: zk
spec:
  serviceName: zk-hs
  replicas: 3
  podManagementPolicy: Parallel
  selector:
    matchLabels:
      app: zk
  template:
    metadata:
      labels:
        app: zk
    spec:
      terminationGracePeriodSeconds: 60
      securityContext:
        fsGroup: 1000
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                matchLabels:
                  app: zk
              topologyKey: kubernetes.io/hostname
      containers:
        - name: zookeeper
          image: zookeeper:3.9.3
          command: ["/bin/bash", "-ec"]
          args:
            - |
              export ZOO_MY_ID="$(( ${HOSTNAME##*-} + 1 ))"
              exec /docker-entrypoint.sh zkServer.sh start-foreground
          env:
            - name: ZOO_SERVERS
              value: "server.1=zk-0.zk-hs:2888:3888;2181 server.2=zk-1.zk-hs:2888:3888;2181 server.3=zk-2.zk-hs:2888:3888;2181"
            - name: ZOO_TICK_TIME
              value: "2000"
            - name: ZOO_INIT_LIMIT
              value: "10"
            - name: ZOO_SYNC_LIMIT
              value: "5"
            - name: ZOO_AUTOPURGE_PURGEINTERVAL
              value: "24"
            - name: ZOO_AUTOPURGE_SNAPRETAINCOUNT
              value: "5"
            - name: ZOO_4LW_COMMANDS_WHITELIST
              value: "srvr,ruok"
            - name: JVMFLAGS
              value: "-Xms512m -Xmx512m"
          ports:
            - name: client
              containerPort: 2181
            - name: quorum
              containerPort: 2888
            - name: election
              containerPort: 3888
          resources:
            requests:
              cpu: 250m
              memory: 768Mi
            limits:
              memory: 1Gi
          startupProbe:
            exec:
              command: ["/bin/bash", "-ec", "zkServer.sh status"]
            periodSeconds: 10
            failureThreshold: 30
          readinessProbe:
            exec:
              command: ["/bin/bash", "-ec", "zkServer.sh status"]
            periodSeconds: 10
            timeoutSeconds: 5
          volumeMounts:
            - name: data
              mountPath: /data
            - name: data
              mountPath: /datalog
              subPath: transaction-log
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes: ["ReadWriteOnce"]
        resources:
          requests:
            storage: 10Gi

Parallel 允许首次同时启动多个成员,避免首个 Pod 等待 quorum 而后续成员未创建。publishNotReadyAddresses 让尚未就绪的成员能互相发现。数据卷与事务日志共用每个成员自己的 PVC,生产可按 I/O 需求拆成两个独立 PVC。

kubectl apply --dry-run=server -f zookeeper.yaml
kubectl apply -f zookeeper.yaml
kubectl rollout status statefulset/zk --timeout=5m
kubectl get pods,pvc -l app=zk
kubectl exec zk-0 -- zkServer.sh status
kubectl exec zk-1 -- zkServer.sh status
kubectl exec zk-2 -- zkServer.sh status
kubectl exec -it zk-0 -- zkCli.sh -server zk-0.zk-hs:2181,zk-1.zk-hs:2181,zk-2.zk-hs:2181

PVC 没有自动附加 Pod 的 app 标签,必要时使用 kubectl get pvc 查看 data-zk-*。运行后核对一个 Leader、两个 Follower,以及各节点 myid 与对应的服务器编号。

本例尚未配置认证与 TLS,只能用于受限测试网络。对外提供服务前补齐下节安全配置,不能直接创建公网 NodePort 或 LoadBalancer。

会话、ACL 与 TLS

客户端连接串列出多个节点,设置合理的连接与会话超时;通过成熟客户端库实现重连、锁和选主,避免自行拼接临时节点逻辑。分布式锁还应使用 fencing token 等机制阻止过期持有者继续操作外部资源。

测试环境可在 zkCli.sh 中验证 digest ACL,REPLACE_WITH_STRONG_PASSWORD 必须替换:

addauth digest app:REPLACE_WITH_STRONG_PASSWORD
create /app "" auth::cdrwa
getAcl /app

ACL 绑定在节点上,不会自动递归继承到所有子节点;创建子节点时也必须使用合适 ACL。ACL 认证不加密传输,生产使用 TLS 客户端端口和 quorum TLS,配置受限证书、信任库与网络策略。启用安全端口后使用匹配的 Netty 客户端配置,不能直接把明文客户端连到 TLS 端口。

持久化、备份与恢复

快照与事务日志共同构成恢复数据。autopurge 负责清理旧文件,不是备份。备份保留服务器配置、myid、所需快照和对应事务日志,使用停止的成员或经验证的一致性存储快照获取文件,并把备份放在独立故障域。

恢复前在隔离环境验证数据树与 ACL。单成员损坏和整个 ensemble 恢复是不同流程;不要把其他成员的 myid 原样复制,也不要在运行集群中随意删除数据目录。完整集群灾难恢复按目标版本官方步骤重建成员关系,再允许客户端连接。

监控、故障与升级

监控请求延迟、未完成请求、连接数、watch 数、znode 数、同步状态、磁盘 fsync 和 Leader 变动。ruok=imok 仅说明进程响应,不证明集群有 quorum;结合角色、同步情况与实际读写验收。

现象 检查方向
集群无法选主 节点数、DNS、2888/3888 连通性、myid、时钟与事务日志
Pod Pending PVC/StorageClass、反亲和性与可用工作节点数量
客户端频繁 session expired GC 停顿、磁盘延迟、网络抖动、会话超时和连接数
NoAuth 节点 ACL、客户端 addauth、认证方案及目标路径
磁盘持续增长 autopurge、日志保留、备份策略和数据规模

升级逐个成员进行,操作前确认 quorum、备份和版本兼容。PDB 仅约束支持 eviction 的自愿中断,不能防止手动删除、硬件故障或所有维护操作。

评论