【Zookeeper】知识点
1 zookeeper是什么?
Zookeeper是一个分布式、开源的分布式应用程序的协调服务,是一个树形的目录服务
主要功能:
- 配置管理
- 分布式锁
- 集群管理
2 zookeeper入门
2.1命令
启动服务端:./zkServer.sh start
关闭服务器:./zkServer.sh stop
启动客户端:./zkCli.sh -server localhost:2181
退出客户端:quit
查看节点状态:./zkServer.sh status
查看命令帮助:help
显示指定目录下节点:ls 目录
显示指定目录详细信息:ls -s 目录
创建节点:create [-es] /节点path value
获取节点值:get /节点path
设置节点值:set /节点path value
删除单个节点:delete /节点path
删除带有子节点的节点:deleteall /节点path
2.1数据模型

- Zookeeper是一个树形目录服务,其数据模型拥有一个层次化结构
- 这里面的每一个节点被称为:ZNode,每个节点上都会保存自己的数据和节点信息
- 节点可以拥有子节点,同时也允许少量(1MB)数据存储在该节点下
- 节点类型:
- PERSISTENT:持久化目录节点
- PERSISTENT_SEQUENTIAL:持久化顺序编号目录节点 -s
- EPHEMERAL:临时目录节点。当前会话未结束数据会一直存在 -e
- EPHEMERAL_SEQUENTIAL:临时顺序编号目录节点 -es
2.3Curator
Curator是Apache Zookeeper的Java客户端库
/**
* @author olic
* @date 2022/6/2814:25
* @描述 Curator
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = JedisTests.class)
public class CuratorTests {
private CuratorFramework client;
/**
* 创建连接
*/
@Before
@Test
public void coon(){
// connectString:多个地址用,隔开; connectionTimeoutMs:连接超时时间; retryPolicy:重试策略
// minSessionTimeout, maxSessionTimeout:一般,客户端连接zookeeper的时候,都会设置一个session timeout,如果超过这个时间client没有zookeeper server有联系,则这个session被设置为过期(如果这个session上有临时节点,则会被全部删除),但是这个时间不是客户端可以无限设置的,服务器可以设置这两个参数来限制客户端设置的范围
RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000, 10);
client = CuratorFrameworkFactory.builder()
.connectString("127.0.0.1:2181")
.retryPolicy(retryPolicy)
.namespace("phenom") //此时phenom就是'/'目录
.build();
//开启连接
client.start();
}
/**
* 释放资源
*/
@After
@Test
public void close(){
if(client != null){
client.close();
}
}
/**
* 创建节点
*/
@Test
public void create() throws Exception {
// 创建节点不指定数据时,默认将当前客户端的ip作为数据存储
String path = client.create()
.creatingParentsIfNeeded()
.withMode(CreateMode.PERSISTENT)
.forPath("/app1/p1", "234".getBytes());
System.out.println(path);
}
/**
* 查询节点
*/
@Test
public void select() throws Exception {
// 节点值
byte[] bytes = client.getData().forPath("/app1");
// 子节点
List<String> sons = client.getChildren().forPath("/");
// 节点状态
Stat stat = new Stat();
client.getData().storingStatIn(stat);
System.out.println("子节点列表:"+ sons);
System.out.println("节点值为:"+ Arrays.toString(bytes));
System.out.println("节点状态:"+ stat);
}
/**
* 修改节点
*/
@Test
public void update() throws Exception {
Stat stat = new Stat();
client.getData().storingStatIn(stat);
int version = stat.getVersion();
// 带着版本号去修改,防止查询和修改之间被别的线程操作数据
client.setData().withVersion(version).forPath("/app1", "iii".getBytes());
}
/**
* 删除节点
*/
@Test
public void delete() throws Exception {
// 删除带有子节点的节点
client.delete().deletingChildrenIfNeeded().forPath("/app1");
// 强制删除,一定删除
client.delete().guaranteed().deletingChildrenIfNeeded().forPath("/app1");
// 删除回调
client.delete().inBackground(new BackgroundCallback() {
@Override
public void processResult(CuratorFramework client, CuratorEvent event) throws Exception {
System.out.println("已经被删除了");
}
});
}
/**
* 给指定的一个节点注册监听器
*/
@Test
public void nodeCache() throws Exception {
// 创建NodeCache对象
NodeCache cache = new NodeCache(client,"/app1");
// 注册监听
cache.getListenable().addListener(new NodeCacheListener() {
@Override
public void nodeChanged() throws Exception {
//只要节点增删改此处就会执行
System.out.println("节点变化了");
//获取修改节点后的数据
byte[] data = cache.getCurrentData().getData();
System.out.println(new String(data));
}
});
// 开启监听
cache.start();
}
/**
* 监控一个ZNode的子节点
*/
@Test
public void pathChildrenCache() throws Exception {
PathChildrenCache cache = new PathChildrenCache(client, "/app1", false);
//2、注册监听
cache.getListenable().addListener(new PathChildrenCacheListener() {
@Override
public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception {
System.out.println(event); //节点变化的信息,内部可直接拿到修改的数据
//1.获取变化类型
PathChildrenCacheEvent.Type type = event.getType();
//2.判断类型
if (type.equals(PathChildrenCacheEvent.Type.CHILD_UPDATED)){
//3.获取变化的数据
byte[] data = event.getData().getData();
System.out.println(new String(data));
}
}
});
//3、开启监听
cache.start();
}
/**
* 监控一个ZNode节点和他的子节点们
*/
@Test
public void testTreeCache() throws Exception {
//1、创建 TreeCache 对象
//构造器参数:连接客户端对象client、要监听的路径path
TreeCache cache = new TreeCache(client,"/app4");
//2、注册监听
cache.getListenable().addListener(new TreeCacheListener() {
@Override
public void childEvent(CuratorFramework client, TreeCacheEvent event) throws Exception {
System.out.println(event); //节点变化的信息,内部可直接拿到修改的数据
//1.获取变化类型
TreeCacheEvent.Type type = event.getType();
//2.判断类型
if (type.equals(TreeCacheEvent.Type.NODE_UPDATED)){
//3.获取变化的数据
byte[] data = event.getData().getData();
System.out.println(new String(data));
}
}
});
//3、开启监听
cache.start();
}
}
3 zookeeper进阶
3.1watch事件监听
Zookeeper允许用户在指定节点上注册一些Watcher,并且在一些特定事件触发的时候,Zookeeper服务端会将事件通知到感兴趣的客户端上去,该机制是Zookeeper实现分布式协调服务的重要特性。Zookeeper使用Watcher机制来实现发布/订阅功能
事件类型(znode节点相关的):
- EventType.NodeCreated
- EventType.NodeDataChanged
- EventType.NodeChildrenChanged
- EventType.NodeDeleted
3.1.1ls
监听子节点的增删,监听一次,监听触发后会被删除
- ls -w [path]:监听当前节点和所有子节点的增删,所属的节点只监听一次,
[zk: localhost:2181(CONNECTED) 39] ls -w /lsh
[]
- ls -R -w [path]:可以递归监听,所属的节点都会监听一次
[zk: localhost:2181(CONNECTED) 38] ls -R -w /lsh
/lsh
/lsh/l1
/lsh/l2
/lsh/l3
3.1.2get
监听当前节点数据变化, 监听一次,监听触发后会被删除
[zk: localhost:2181(CONNECTED) 41] get -w /lsh
22
3.1.3stat
监听当前节点属性变化,监听一次, 监听触发后会被删除
[zk: localhost:2181(CONNECTED) 43] stat -w /lsh
cZxid = 0x145
ctime = Thu Jun 30 07:07:00 UTC 2022
mZxid = 0x14b
mtime = Thu Jun 30 07:11:57 UTC 2022
pZxid = 0x147
cversion = 1
dataVersion = 3
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 2
numChildren = 1
3.1.4addWatch
监听当前节点或所属节点(模式可选),每次监听,监听触发后不会被删除
addWatch [-m mode] path:
- PERSISTENT:只有当前监听的节点有变化了,才能收到通知,会持续的收到每次的通知
- PERSISTENT_RECURSIVE:当前节点和子节点有变化了,就会收到通知,会持续的收到每次的通知
[zk: localhost:2181(CONNECTED) 46] addWatch -m PERSISTENT_RECURSIVE /lsh
[zk: localhost:2181(CONNECTED) 47]
3.2分布式锁
当我们的应用是分布式集群工作的情况下,属于多JVM下的工作环境,跨JVM之间已经无法通过多线程的锁解决同步问题,那么就需要一种更加高级的锁机制, 来处理这种跨机器的进程之间的数据同步问题

3.2.1分布式锁实现

- Redis分布式锁性能极高,但是有隐患,不安全(master节点挂了,并且还未进行数据同步就会导致获取到多把分布式锁)
- 在数据上直接加锁,但是会影响性能,因为数据库本身性能就比较差
3.2.2curator中五种锁方案
- InterProcessSemaphoreMutex:分布式排他锁
- InterProcessMutex:分布式可重入排他锁
- InterProcessReadWriteLock:分布式读写锁
- InterProcessMultiLock:将多个锁作为单个实体管理的容器
- InterProcessSemaphoreV2:共享信号量
3.2.3分布式锁原理
核心思想:当客户端要获取锁,则创建节点,使用完锁,则删除该节点
- 客户端获取锁时,在lock节点下创建临时顺序节点
临时:以防出现客户端宕机,而导致锁无法释放问题。当客户端宕机,连接会断开,临时节点仅存在于一个会话中,当连接断开,临时节点就会消失。zk通过临时节点,解决掉了死锁的问题,一旦客户端获取到锁之后突然挂掉(session连接断开),那么这个临时节点就会自动删除掉,其他客户端自动获取锁 - 然后获取lock下面的所有子节点,客户端获取到所有的子节点之后,如果发现自己创建的子节点序号最小,那么就认为该客户端获取到了锁。使用完锁后,将该节点删除
- 如果发现自己创建的节点并非lock所有子节点中最小的,说明自己还没有获取到锁,此时客户端需要找到比自己小的那一个节点,同时对其注册事件监听器,监听删除事件
- 如果发现比自己小的那个节点被删除,则客户端的watcher会收到相应通知,此时再次判断自己创建的节点是否是lock子节点中序号最小的。如果是,则获取到了锁;如果不是,则重复2到4步骤继续获取到此自己小的那一个节点,并注册监听
这里在监听到事件之后,再次进行一次判断大小,可以防止出现中间客户端宕机状况
例如此时有 1/2/3,2出现宕机删除节点,3监听到了事件,然后再次判断自己并不是序号最小的,就对比自己小的节点注册监听

RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000,10);
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString("127.0.0.1:2181")
.sessionTimeoutMs(60*1000)
.connectionTimeoutMs(15*1000)
.retryPolicy(retryPolicy)
.build();
client.start();
//第一个参数是客户端连接对象,第二个参数是要监控的锁节点
InterProcessMutex lock = new InterProcessMutex(client,"/lock");
//获取锁。未获取到则一直尝试获取。锁释放后,客户端再次尝试获取锁时,会创建新的临时节点。参数指明没拿到锁要等多久
lock.acquire(3, TimeUnit.SECONDS);
//锁释放
lock.release();
3.3集群
3.3.1集群服务中三个角色

- leader:领导者
- 处理事务请求
- 集群内部各服务器的调度者
- follower:跟随者
- 处理非事务请求,转发事务请求到leader服务器
- 参与leader选举投票
- observer:观察者
- 处理非事务请求,转发事务请求到leader服务器
3.3.2leader选举
leader选举流程:
- serverid:服务器ID,比如三台服务器,编号分别是1,2,3。编号越大在选择算法中的权重越大
- zxid:也就是事务id, 为了保证事务的顺序⼀致性,zookeeper采⽤了递增的事务id号(zxid)来标识事务。数值越大说明数据越新在选举算法中的权重越大
1.第一轮投票
每个服务器都会投票给自己,投票包含所投服务器的机器id和事务id,如:Server(myid, zxid)
2.同步投票结果
集群中服务器投票后,会将各自投票结果同步给集群中其他服务器
3.检查投票有效性
是否本轮投票、是否来自Looking服务器
4.处理投票
服务器检查完投票有效性后会进行投票比对,如果发现有比自己更大的选票,则变更自己的投票,重新发起投票
# 比对规则
优先检查zxid,较大的服务器优先作为Leader;如果zxid相同,则myid较大的服务器作为Leader
5.统计投票结果
每轮投票结束都会统计投票结果,确认是否有机器得到半数以上的选票,如果是则选出Leader,否则继续投票(半数是指集群规模,并非可用服务器的半数)
6.更新服务器状态
一旦选举出Leader,每个服务器就会各自更新自己的状态
3.3.3数据同步
数据同步流程:
1.向主节点写数据
2.主节点先把数据写到自己的数据文件中,并给自己返回一个ACK
3.Leader把数据发送给Follower
4.从节点将数据写到本地数据文件
5.从节点返回ACK给Leader
6.Leader收到半数以上的ACK后提交写入的数据维护最新的zxid并向Follower发送Commit。Commit之后数据才会真正的保存到服务器上
7.从节点收到Commit后把数据文件中数据写到内存中维护最新的zxid
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY