【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分布式锁原理

核心思想:当客户端要获取锁,则创建节点,使用完锁,则删除该节点

  1. 客户端获取锁时,在lock节点下创建临时顺序节点
    临时:以防出现客户端宕机,而导致锁无法释放问题。当客户端宕机,连接会断开,临时节点仅存在于一个会话中,当连接断开,临时节点就会消失。zk通过临时节点,解决掉了死锁的问题,一旦客户端获取到锁之后突然挂掉(session连接断开),那么这个临时节点就会自动删除掉,其他客户端自动获取锁
  2. 然后获取lock下面的所有子节点,客户端获取到所有的子节点之后,如果发现自己创建的子节点序号最小,那么就认为该客户端获取到了锁。使用完锁后,将该节点删除
  3. 如果发现自己创建的节点并非lock所有子节点中最小的,说明自己还没有获取到锁,此时客户端需要找到比自己小的那一个节点,同时对其注册事件监听器,监听删除事件
  4. 如果发现比自己小的那个节点被删除,则客户端的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:领导者
  1. 处理事务请求
  2. 集群内部各服务器的调度者

 

  • follower:跟随者
  1. 处理非事务请求,转发事务请求到leader服务器
  2. 参与leader选举投票

 

  • observer:观察者
  1. 处理非事务请求,转发事务请求到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
 
posted @   忱康  阅读(63)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
点击右上角即可分享
微信分享提示