2.0 Zookeeper 安装配置
Zookeeper 安装配置
ZooKeeper 是 Apache 开源的高性能分布式协调服务,用于配置管理、命名服务、分布式锁、集群选举等场景,是 HDFS、Kafka、HBase 等分布式系统的核心组件。
1. 环境准备
# 依赖 JDK 8+ java -version # 下载 ZooKeeper wget https://archive.apache.org/dist/zookeeper/zookeeper-3.8.4/apache-zookeeper-3.8.4-bin.tar.gz tar -zxvf apache-zookeeper-3.8.4-bin.tar.gz mv apache-zookeeper-3.8.4-bin /opt/zookeeper
2. 配置 zoo.cfg
# 复制示例配置 cd /opt/zookeeper/conf cp zoo_sample.cfg zoo.cfg # 编辑 zoo.cfg tickTime=2000 # 心跳间隔(毫秒) initLimit=10 # 初始化连接超时(个 tickTime) syncLimit=5 # 同步超时 dataDir=/data/zookeeper # 数据目录(需创建) clientPort=2181 # 客户端端口 maxClientCnxns=60 # 最大连接数
3. 启动与验证
# 创建数据目录 mkdir -p /data/zookeeper # 启动(前台) cd /opt/zookeeper/bin ./zkServer.sh start-foreground # 后台启动 ./zkServer.sh start # 查看状态 ./zkServer.sh status # Mode: standalone # 连接客户端 ./zkCli.sh -server localhost:2181 # 常用命令 ls / # 查看根节点 create /app "data" # 创建节点 get /app # 获取数据 quit # 退出
4. 常用配置项
tickTime 基本时间单位(毫秒) dataDir 数据存储目录 clientPort 客户端端口 initLimit Follower 连接 Leader 超时 syncLimit Follower 与 Leader 同步超时 maxClientCnxns 单 IP 最大连接 autopurge.snapRetainCount 保留快照数 autopurge.purgeInterval 清理间隔(小时)
3.0 Zookeeper linux 服务端集群搭建步骤
Zookeeper linux 服务端集群搭建步骤
1. 集群规划
# 至少 3 台机器(奇数,避免脑裂) # 例如: node1(192.168.1.11) node2(192.168.1.12) node3(192.168.1.13) # 每台机器都安装 ZooKeeper(同单机安装步骤)
2. 配置集群
# 每台机器编辑 conf/zoo.cfg,追加集群配置 server.1=192.168.1.11:2888:3888 server.2=192.168.1.12:2888:3888 server.3=192.168.1.13:2888:3888 # 格式: server.id=host:leader通信端口:选举端口 # 2888: 集群内部通信端口 # 3888: Leader 选举端口
3. 创建 myid
# 每台机器在 dataDir 下创建 myid 文件,内容为对应 id # node1: echo "1" > /data/zookeeper/myid # node2: echo "2" > /data/zookeeper/myid # node3: echo "3" > /data/zookeeper/myid
4. 启动集群
# 三台机器依次启动 cd /opt/zookeeper/bin ./zkServer.sh start # 检查状态 ./zkServer.sh status # node1: Mode: follower # node2: Mode: leader (自动选举) # node3: Mode: follower # 查看集群成员 ./zkCli.sh -server localhost:2181 ls /zookeeper/quota
5. 集群验证
# 在任意节点创建节点,其他节点可见 # node1 创建 create /cluster-test "hello" # node2 查看 get /cluster-test # 输出: hello(数据已同步) # 停掉 Leader 验证故障转移 # 停止 leader 节点,集群自动重新选举 ./zkServer.sh stop # 其他节点仍可读写,服务不中断
4.0 Zookeeper Java 客户端搭建
Zookeeper Java 客户端搭建
1. Maven 依赖
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.8.4</version>
</dependency>
2. 创建连接
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.util.concurrent.CountDownLatch;
public class ZkClient {
private static ZooKeeper zk;
private static CountDownLatch latch = new CountDownLatch(1);
public static void main(String[] args) throws Exception {
// 连接 ZooKeeper(异步建立)
zk = new ZooKeeper(
"192.168.1.11:2181,192.168.1.12:2181,192.168.1.13:2181",
5000, // 会话超时
new Watcher() {
@Override
public void process(WatchedEvent event) {
if (event.getState() == Event.KeeperState.SyncConnected) {
latch.countDown(); // 连接成功
}
}
}
);
latch.await();
System.out.println("连接成功, 会话ID: " + zk.getSessionId());
}
}
3. 节点操作
// 创建节点
zk.create("/app", "data".getBytes(),
ZooDefs.Ids.OPEN_ACL_UNSAFE, // 权限
CreateMode.PERSISTENT); // 节点类型
// 读取数据
Stat stat = new Stat();
byte[] data = zk.getData("/app", false, stat);
System.out.println("数据: " + new String(data));
System.out.println("版本: " + stat.getVersion());
// 更新数据
zk.setData("/app", "new data".getBytes(), stat.getVersion());
// 删除节点
zk.delete("/app", -1); // -1 表示任意版本
// 获取子节点
zk.getChildren("/", false);
4. Watch 监听
// 注册监听:数据变化时回调
zk.getData("/app", new Watcher() {
@Override
public void process(WatchedEvent event) {
System.out.println("节点变化: " + event.getType());
if (event.getType() == Event.EventType.NodeDataChanged) {
System.out.println("数据已更新!");
}
}
}, null);
5. 关闭连接
zk.close();
System.out.println("连接已关闭");
5.0 ZooKeeper 数据模型 znode 结构详解
ZooKeeper 数据模型 znode 结构详解
1. 数据模型
ZooKeeper 维护一棵树形数据结构,类似文件系统,每个节点称为 znode:
/
├── zookeeper (系统保留)
│ └── quota
├── app
│ ├── config
│ └── nodes
│ ├── node-0001
│ └── node-0002
└── locks
└── lock-0001
2. znode 组成
每个 znode 包含: - 数据(data):字节数组,最大 1MB - 版本号(version):数据版本 - ACL 权限列表 - 时间戳(czxid/mzxid) - 子节点列表 # 查看详情 get -s /app # czxid、mzxid、ctime、mtime # dataVersion、cversion、aversion # ephemeralOwner、dataLength、numChildren
3. 节点类型
# 持久节点(PERSISTENT) # 创建后一直存在,直到显式删除 create /app "data" # 临时节点(EPHEMERAL) # 客户端会话结束时自动删除 create -e /temp-node "data" # 顺序节点(SEQUENTIAL) # 自动追加递增序号 create -s /seq- "data" # 实际创建: /seq-0000000001 # 组合类型 create -s -e /temp-seq- "data" # 临时 + 顺序:用于分布式锁/Leader 选举
4. 节点特性
- znode 数据大小上限 1MB
- 路径以 / 开头,不支持相对路径
- 临时节点不能有子节点
- 删除父节点需先删除所有子节点
- 所有节点都有 ACL 控制
5. 典型应用
# 配置管理: /config/xxx 存储配置,客户端监听 # 命名服务: /services/xxx 注册服务地址 # 分布式锁: /locks/xxx 临时顺序节点 # 集群选举: /election/xxx 临时节点竞争
6.0 Zookeeper session 基本原理
Zookeeper session 基本原理
1. 什么是 Session
客户端与 ZooKeeper 服务端建立的一次会话,是客户端所有操作的上下文。会话具有超时时间,超时后临时节点自动删除。
2. 会话建立流程
1. 客户端发送连接请求 2. 服务端创建 Session 并分配 SessionID 3. 返回连接成功(携带 SessionID + 超时时间) 4. 客户端与服务器保持心跳 5. 心跳超时 → 会话过期
3. 心跳机制
# 客户端在超时时间内周期发送心跳 # 服务端刷新 Session 的过期时间 # 参数 tickTime = 2000 毫秒(基本单位) sessionTimeout = 客户端指定(如 5000ms) minSessionTimeout = 2 * tickTime(最小 4s) maxSessionTimeout = 20 * tickTime(最大 40s)
4. 会话状态
CONNECTING 连接中(初始) CONNECTED 已连接(正常) RECONNECTING 重连中(网络中断) RECONNECTED 已重连 EXPIRED 会话过期(需重新创建) CLOSED 已关闭 # 会话重连 # 客户端断线后自动重连 # 若在超时时间内重连成功,会话保留 # 超时后会话过期,临时节点删除
5. 会话管理
# 查看会话(服务器端) # 通过四字命令或 JMX 监控 echo "stat" | nc localhost 2181 # 会话中的临时节点 create -e /app/session-node "data" # 会话断开(超时)后该节点自动消失 # 客户端关闭会话 zk.close();
7.0 Zookeeper 客户端基础命令使用
Zookeeper 客户端基础命令使用
1. 启动客户端
# 连接本机 ./zkCli.sh # 指定服务器 ./zkCli.sh -server 192.168.1.11:2181 # 指定超时 ./zkCli.sh -server localhost:2181 -timeout 5000
2. 节点操作命令
# 查看子节点 ls / ls -s /app # 带统计信息 # 创建节点 create /app "hello" create -e /temp "data" # 临时节点 create -s /seq- "data" # 顺序节点 create -s -e /tmp-seq- "x" # 临时顺序节点 # 读取数据 get /app get -s /app # 带状态信息 get -w /app # 注册监听(一次性) # 更新数据 set /app "new data" set -s /app "data" # 设置版本(乐观锁) # 删除节点 delete /app deleteall /app # 递归删除(含子节点) # 查看帮助 help
3. 状态信息解读
get -s /app # czxid: 创建该节点的事务 ID # mzxid: 最后修改的事务 ID # ctime: 创建时间 # mtime: 修改时间 # dataVersion: 数据版本号 # cversion: 子节点版本号 # aversion: ACL 版本号 # ephemeralOwner: 临时节点所属会话(0=持久) # dataLength: 数据长度 # numChildren: 子节点数量
4. 监听示例
# 终端1:注册监听 get -w /app # WatchedEvent state:SyncConnected type:NodeDataChanged path:/app # 终端2:修改数据 set /app "changed" # 终端1收到通知 # WATCHER:: WatchedEvent ... type:NodeDataChanged
5. 批量脚本
# 通过 here-doc 批量执行命令 ./zkCli.sh -server localhost:2181 <<EOF create /app "data" create /app/config "prod" get /app quit EOF
8.0 Zookeeper 四字命令
Zookeeper 四字命令
1. 什么是四字命令
ZooKeeper 提供以 4 个字母命名的管理命令,通过 telnet/nc 发送到客户端端口,用于监控服务状态。
2. 启用四字命令
# 默认启用,如需禁用 # zoo.cfg 配置 4lw.commands.whitelist=* # 启用全部 4lw.commands.whitelist=stat,ruok,conf,srvr,cons,mntr # 指定启用
3. 常用命令
# 发送方式:nc 或 telnet echo "stat" | nc localhost 2181 # conf: 输出服务配置 # cons: 客户端连接信息 # crst: 重置连接统计 # dump: 未处理会话与临时节点 # envi: 环境信息 # ruok: 是否存活(输出 imok) # srst: 重置服务器统计 # srvr: 服务器状态 # stat: 状态概览 # wchs: 监听器汇总 # wchc: 按会话列出监听 # wchp: 按路径列出监听 # mntr: 监控指标(推荐)
4. mntr 监控指标
echo "mntr" | nc localhost 2181 # zk_version: 3.8.4 # zk_server_state: standalone / leader / follower # zk_num_alive_connections: 2 # zk_outstanding_requests: 0 # zk_znode_count: 8 # zk_watch_count: 1 # zk_ephemerals_count: 0 # zk_approximate_data_size: 45 # zk_open_file_descriptor_count: 32 # zk_packets_received: 100 # zk_packets_sent: 100
5. 集成监控
# Prometheus 采集
# 使用 zookeeper_exporter 抓取 mntr 指标
# 脚本监控
if [ "$(echo ruok | nc localhost 2181)" = "imok" ]; then
echo "ZooKeeper 存活"
else
echo "ZooKeeper 异常!"
fi
9.0 Zookeeper 节点特性
Zookeeper 节点特性
1. znode 类型回顾
# 持久节点(PERSISTENT) # 创建后一直存在 create /persist "data" # 临时节点(EPHEMERAL) # 会话结束自动删除 create -e /ephemeral "data" # 持久顺序节点 create -s /persist-seq- "data" # 临时顺序节点(分布式锁核心) create -s -e /lock- "data"
2. 顺序节点序号
# 顺序节点自动追加 10 位递增序号 create -s /seq- "data" # 创建: /seq-0000000000 create -s /seq- "data" # 创建: /seq-0000000001 # 序号全局递增,跨父节点唯一 # 用途:队列、锁、Leader 选举
3. 临时节点特性
# 1. 会话存活期间存在 # 2. 会话过期/主动关闭 → 自动删除 # 3. 不能有子节点 # 4. ephemeralOwner 记录所属会话 # 用途: # - 服务注册/发现(服务下线自动清除) # - 分布式锁(持有者断开自动释放) # - Leader 选举(竞选者退出自动让位)
4. Watch 监听机制
# 监听类型 NodeCreated 节点创建 NodeDeleted 节点删除 NodeDataChanged 数据变化 NodeChildrenChanged 子节点变化 # 特性: # - 一次性触发:监听后只通知一次 # - 需要持续监听需重新注册 # - 通过会话连接传输 # 示例:监听子节点变化 ls -w /app # 当 /app 子节点变化时收到通知
5. 节点操作细节
# 版本控制(乐观锁) set /app "v1" # 版本变 1 set /app "v2" # 版本变 2 set /app "v3" 1 # 指定版本号更新 # 版本不匹配报错: BadVersion # 递归删除 deleteall /app # 数据大小限制(1MB) # 超过报错: DataTooLargeException # 权限控制(ACL) # 每个节点可设置独立权限
10.0 Zookeeper 权限控制 ACL
Zookeeper 权限控制 ACL
1. ACL 概念
ZooKeeper 的 ACL(Access Control List)基于每个 znode 控制访问权限,格式为 scheme:auth:permission。
2. 权限类型
权限: CREATE 允许创建子节点(c) READ 允许读取节点与子节点(r) WRITE 允许修改数据(w) DELETE 允许删除子节点(d) ADMIN 允许设置 ACL(a) # 组合写法: crwda(全部权限)
3. 认证方式(scheme)
# world:所有人(默认) create /app "data" # ACL: world:anyone:cdrwa # auth:已认证用户 addauth digest user:password create /app "data" auth:user:cdrwa # digest:用户名密码摘要 # 生成密码 echo -n "user:password" | openssl dgst -binary -sha1 | openssl base64 # 创建带 ACL 节点 create /app "data" digest:user:密码摘要:cdrwa # ip:按 IP 限制 create /app "data" ip:192.168.1.11:cdrwa
4. 客户端使用
# 命令行设置认证
addauth digest user:password
# 设置 ACL(需要 admin 权限)
setAcl /app auth:user:cdrwa
# 查看 ACL
getAcl /app
# Java 客户端
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Id;
// 创建带 ACL 节点
List<ACL> acls = new ArrayList<>();
acls.add(new ACL(Perms.ALL,
new Id("digest", "user:密码摘要")));
zk.create("/app", "data".getBytes(), acls, CreateMode.PERSISTENT);
5. 超级用户
# 配置超级管理员(zoo.cfg) # 生成 digest 密码 echo -n "super:password" | openssl dgst -binary -sha1 | openssl base64 # zoo.cfg 添加 -Dzookeeper.DigestAuthenticationProvider.superDigest=super:摘要 # 客户端 addauth digest super:password
11.0 Zookeeper watcher 事件机制原理剖析
Zookeeper watcher 事件机制原理剖析
1. Watcher 机制
Watcher 是 ZooKeeper 的发布/订阅机制:客户端在节点上注册监听,节点发生变化时服务端推送事件通知客户端。
2. 事件类型
# 状态事件(连接状态变化) None 连接状态变化 SyncConnected 已连接 Disconnected 已断开 Expired 会话过期 # 节点事件 NodeCreated 节点被创建 NodeDeleted 节点被删除 NodeDataChanged 节点数据变化 NodeChildrenChanged 子节点变化
3. 注册方式
# 客户端命令
get -w /app # 监听数据变化
ls -w /app # 监听子节点变化
stat -w /app # 监听节点状态
exists -w /app # 监听节点是否存在
# Java 客户端
zk.getData("/app", watcher, null);
zk.getChildren("/app", watcher);
zk.exists("/app", watcher);
4. 触发流程
1. 客户端注册 Watcher(存于客户端) 2. 客户端把 Watch 请求发送服务端 3. 服务端在对应节点记录 Watch 4. 节点变化 → 服务端生成事件 5. 服务端推送事件给客户端(异步) 6. 客户端回调 Watcher.process() 7. 触发后 Watcher 失效(一次性) # 重要:Watcher 是一次性的 # 每次触发后需要重新注册
5. 原理剖析
# 存储 # 客户端:WatchManager 管理本地注册 # 服务端:DataTree + WatchManager 按节点存储 # 通知时机 - setData → 触发 NodeDataChanged - create/delete 子节点 → NodeChildrenChanged - delete 节点 → NodeDeleted - 会话过期 → 所有临时节点相关 Watcher 触发 # 性能特点 - 通知不携带新数据,客户端需重新 get - 网络中断期间事件丢失,重连后需重新注册 - 大量 Watcher 注意数量限制
12.0 Zookeeper 数据同步流程
Zookeeper 数据同步流程
1. 数据同步概述
ZooKeeper 集群中,Leader 处理写请求,Follower 与 Leader 保持数据一致。新 Leader 选举或新节点加入时,需要数据同步。
2. 写入流程
1. 客户端向任意节点发送写请求 2. 若目标是 Follower,转发给 Leader 3. Leader 生成全局递增的 ZXID(事务ID) 4. Leader 广播 Proposal 给所有 Follower 5. Follower 写本地事务日志,返回 ACK 6. Leader 收到多数派(majority)ACK 7. Leader 提交事务并通知所有 Follower 提交 8. 返回客户端写入成功 # 写成功条件: 超过半数节点确认 # 这就是"多数派写"机制
3. ZXID 结构
# ZXID = 高32位(epoch) + 低32位(计数器) # epoch: Leader 任期 # counter: 任期内事务序号 # 每次新 Leader 选举,epoch + 1 # 保证全局有序 # 查看 get -s /app # czxid 即创建时的事务 ID
4. 新节点加入同步
# 新 Follower 加入集群: 1. 与 Leader 建立连接 2. 发送 FOLLOWERINFO(带自己 lastZxid) 3. Leader 比较双方 ZXID # 同步方式选择: - DIFF: 差异同步(差距小,同步增量) - TRUNC: 截断(自己比 Leader 新,删除多余) - SNAP: 全量快照(差距大,全量同步) - 组合: SNAP + DIFF 4. 同步完成后开始正常服务
5. 数据一致性
# 顺序一致性 # 同一客户端请求按顺序执行 # 最终一致性 # 读请求可能读到稍旧数据(可设置 sync 强制同步) # 保证机制 - 事务日志 + 数据快照持久化 - 多数派提交 - Follower 只读(写走 Leader) # 读一致性选项 get /app # 可能读旧数据 get -sync /app # 强制同步读
13.0 Zookeeper Leader 选举原理
Zookeeper Leader 选举原理
1. 选举触发时机
# 1. 集群启动:没有 Leader,需要选出 # 2. Leader 宕机:其他节点触发重新选举 # 3. 网络分区:多数派内部重新选举 # 选举算法:FastLeaderElection(默认)
2. 选举要素
# 投票关键信息 - myid:节点唯一 ID - zxid:最后一次事务 ID(数据新旧) - epoch:逻辑时钟(选举轮次) # 选举规则 1. 优先比较 epoch,epoch 大者胜 2. epoch 相同比较 zxid,zxid 大者胜(数据新) 3. zxid 相同比较 myid,myid 大者胜
3. 选举流程
1. 节点进入 LOOKING 状态 2. 投票给自己 (myid, zxid, epoch) 3. 广播投票给所有节点 4. 接收他人投票并比较 5. 更新自己的投票(投更优者) 6. 重新广播 7. 某节点获得超过半数的投票 → 成为 Leader 8. 其他节点成为 Follower 9. Leader 与 Follower 建立连接并同步数据 # 示例(3节点: myid 1/2/3, 全部 zxid 相同) # 1 投自己,2 投自己,3 投自己 # 1 收到 2 的票: 2>1 → 1 改投 2 # 2 收到 3 的票: 3>2 → 2 改投 3 # 3 收到 2 的票: 3>2 → 保持投自己 # 3 获得 2 票(自己+2 的)超过半数(2/3) → 3 当选 Leader
4. 选举特性
# 多数派原则 # 只有获得超过半数节点支持才能当选 # 因此集群必须是奇数节点(3、5、7) # 半数以下节点不可用 → 集群不可写(可用性保障) # 脑裂防护 # 5 节点分区为 3+2: # 3 节点分区可选出 Leader(3>2.5) # 2 节点分区无法选出(2<2.5) → 不会出现双主
5. 选举观察
# 查看当前 Leader ./zkServer.sh status # Mode: leader / follower # 动态观察(新 Leader 选举时) # 日志观察 tail -f /opt/zookeeper/logs/zookeeper.log | grep -i election # 设置法定人数(默认多数派) # zoo.cfg 可选配置 # (不配置则使用多数派规则)
14.0 Zookeeper 分布式锁实现原理
Zookeeper 分布式锁实现原理
1. 为什么需要分布式锁
多台机器上的多个进程并发操作共享资源(数据库、缓存、文件)时,需要跨进程互斥的锁机制,这就是分布式锁。
2. ZooKeeper 实现思路
# 核心:临时顺序节点 # /locks/mylock-0000000001 # /locks/mylock-0000000002 # /locks/mylock-0000000003 # 规则: # 序号最小的节点获得锁 # 其他节点监听前一个节点的删除事件
3. 加锁流程
1. 客户端在 /locks 下创建临时顺序节点 create -s -e /locks/mylock- 2. 获取 /locks 下所有子节点并排序 3. 若自己是最小序号 → 获得锁 4. 否则监听比自己小一号的节点 5. 等待前序节点删除通知 6. 收到通知后重新检查 → 获得锁 # 锁释放: # - 主动删除节点 # - 会话超时自动删除(临时节点特性) # 不会死锁!
4. Java 实现示例
import org.apache.zookeeper.*;
public class DistributedLock {
private ZooKeeper zk;
private String lockPath;
private String currentPath;
private static final String LOCK_ROOT = "/locks";
public DistributedLock(String connectString, String lockPath) throws Exception {
this.lockPath = lockPath;
this.zk = new ZooKeeper(connectString, 5000, event -> {});
if (zk.exists(LOCK_ROOT, false) == null) {
zk.create(LOCK_ROOT, new byte[0],
ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
}
// 加锁(阻塞)
public void lock() throws Exception {
currentPath = zk.create(
LOCK_ROOT + "/" + lockPath + "-",
new byte[0],
ZooDefs.Ids.OPEN_ACL_UNSAFE,
CreateMode.EPHEMERAL_SEQUENTIAL);
while (true) {
var children = zk.getChildren(LOCK_ROOT, false);
java.util.Collections.sort(children);
String currentName = currentPath.substring(currentPath.lastIndexOf("/") + 1);
if (currentName.equals(children.get(0))) {
return; // 最小序号,获得锁
}
// 监听前一个节点
int idx = children.indexOf(currentName);
String prevPath = LOCK_ROOT + "/" + children.get(idx - 1);
var latch = new java.util.concurrent.CountDownLatch(1);
zk.exists(prevPath, event -> latch.countDown());
latch.await(); // 等待前序节点释放
}
}
// 释放锁
public void unlock() throws Exception {
zk.delete(currentPath, -1);
zk.close();
}
}
5. 锁方案对比
# ZooKeeper 锁 优点:临时节点自动释放,无死锁;公平(FIFO) 缺点:需维护 ZooKeeper 集群;性能略低 # Redis 锁(Redisson) 优点:性能高,实现简单 缺点:主从切换可能丢锁;需看门狗续期 # 数据库锁(悲观/乐观) 优点:通用,无额外组件 缺点:性能差,容易死锁 # 选择建议: # 强一致 + 低并发 → ZooKeeper # 高并发 + 可容忍短暂不一致 → Redis # 简单场景 → 数据库
第 1 / 13 节