Zookeeper基本使用

27 阅读6分钟

简介

数据模型:

Zookeeper 通过树形结构来存储数据

  • 树中节点类型
创建命令生命周期顺序性子节点适用场景
持久节点create /path data永久存在支持配置存储、元数据
临时节点create -e /path data会话结束删除不支持服务发现、分布式锁
持久顺序节点create -s /path data永久存在有序(节点名称拼接一个10位序号,zk内部用完就不允许再创建,基本用不完)支持任务队列、命名序列
临时顺序节点create -e -s /path data会话结束删除有序不支持公平锁、领导者选举
容器节点create -c /path data自动清理/后代无节点后会在特定的时机被zk自动清理可选专门存放工作组模式
TTL节点create -t ttl /path data超时删除可选支持临时缓存
  • 节点信息

    状态属性说明
    czxid数据节点创建时的事务 ID
    ctime数据节点创建时的时间
    mzxid数据节点最后一次更新时的事务 ID
    mtime数据节点最后一次更新时的时间
    pzxid数据节点的子节点最后一次被修改时的事务 ID
    cversion子节点的更改次数
    version节点数据的更改次数
    aversion节点的 ACL 的更改次数
    ephemeralOwner如果节点是临时节点,则表示创建该节点的会话的 SessionID;如果节点是持久节点,则该属性值为 0
    dataLength数据内容的长度
    numChildren数据节点当前的子节点个数
  • 节点权限ACL

    CREATE:允许创建子节点;
    READ:允许从节点获取数据并列出其子节点;
    WRITE:允许为节点设置数据;
    DELETE:允许删除子节点;
    ADMIN:允许为节点设置权限。
    ​
    

高可用:zookeeper集群保证只要集群中半数以上机器正常工作整个集群就能正常工作,通过zab算法保证分布式集群的可用

顺序性: 每个写操作交由leader节点来执行都会分配一个id,能够保证集群内所有节点的写操作顺序一致

高性能:写操作交由leader执行,读操作任意节点都可以执行,非常适合读多写少场景,监听机制使得客户端不用轮询

集群概念:

  • leader : 组织写
  • follower : 读写服务
  • Observer : 只读节点

会话: 每个客户端都会和集群中的某个节点建立tcp长连接

应用:

  • 基于顺序节点实现分布式锁
  • 用于管理集群:比如作为集群的注册中心(dubbo)

Linux安装

  1. 去官网下载对应的二进制版本
  2. linux解压
  3. 进入解压目录的conf目录,就在目录里面拷贝一份zoo_sample.cfg然后改名为zoo.cfg

修改配置文件

# 默认服务端口8080
admin.serverPort=8088 
# 修改数据目录
dataDir=/home/ekko/Public/apache-zookeeper-3.9.4-bin/data
# 可以修改客户端连接的端口 配置文件里面默认为2181
clientPort=2181

启动:进入解压目录的bin目录

./zkServer.sh start 

没有报错就启动成功,输入jps 能看到QuorumPeerMain进程说明启动成功

# 检查是否启动成功
./zkServer.sh status 

zookeeper提供的客户端连接zookeeper,然后就可以通过命令行操作zookeeper

./zkCli.sh -server 127.0.0.1:2181

基本操作

命令行基本操作

数据操作get /path获取指定节点的数据和详细信息(如事务ID、时间戳等)。
set /path "newData"更新指定节点的数据。
delete /path删除没有子节点的指定节点。
rmr /path递归删除节点及其所有子节点。
辅助功能stat /path查看指定节点的状态信息(不返回节点数据本身)。
quitclose退出ZooKeeper客户端。

java操作zk

        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.5.5</version>
        </dependency>
public class Main {
     //所有的api都在Zookeeper对象中,使用说明的注释也都很清晰
    private static ZooKeeper zk;
    
    public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
        initZk();
        // ACL 访问权限 createMode 节点类型
         zk.create("/ekko/e", "hello".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
​
        // 用于接收返回的节点状态
        Stat stat = new Stat();
        byte[] data = zk.getData("/ekko/e", new Watcher() {
            @Override
            public void process(WatchedEvent event) {
                System.out.println("已经触发了" + event.getType() + "事件!");
            }
        }, stat);
        System.out.println(new String(data));
        System.out.println(stat);
​
        // -1表示任意版本号
        zk.create("/ekko/e2", "hello".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        zk.delete("/ekko/e2",-1);
​
        for (int i = 0; i < 9; i++) {
            zk.create("/ekko/e", "hello".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL);
        }
​
        List<String> children = zk.getChildren("/ekko", false);
        for (int i = 0; i < children.size(); i++) {
            System.out.println(children.get(i));
        }
        
        Thread.sleep(10000);
​
​
    }
​
    private static void initZk() throws IOException {
        zk = new ZooKeeper("192.168.7.223:2181", 10000,  event->{
                // getType() 是枚举类型
                System.out.println("已经触发了" + event.getType() + "事件!");
        });
    }
}

使用zookeeper实现的分布式锁的一种思路:zookeeper集群会保证命令执行的顺序性

  1. 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。 使用持久带TTL的顺序节点,创建节点,然后获取所有节点,并监听(exists命令)比自己大的序号的节点的状态,删除节点时说明获取锁成功。
  2. 乐观锁:总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,通常使用版本号机制或CAS(Compare and Swap)操作。 如果我们操作的zookeeper节点数据,那么可以用节点版本号来进行乐观锁的判断,更新的时候携带版本号。
  3. 可重入锁:也称为递归锁,指的是同一个线程在外层方法获取锁之后,内层递归方法仍然可以获取该锁。在分布式环境中,我们需要记录锁的持有者以及重入次数。 可以通过线程变量实现,每次尝试加锁都先从线程变量中获取锁,获取成功就不用再次加锁,需要维护一个加锁次数。
  4. 不可重入锁:与可重入锁相反,同一个线程在已经持有锁的情况下再次请求锁时会阻塞。在分布式环境中,我们只需要判断当前线程是否已经持有锁,如果已经持有则阻塞或报错。
  5. 公平锁:多个客户端按照申请锁的顺序来获取锁,遵循先来先服务(FCFS)的原则
  6. 非公平锁:允许客户端在锁可用时立即获取,而不考虑它们的请求顺序 先尝试在某个路径创建一个固定名称的节点,创建成功说明加锁成功,创建失败则监听这个路径是否有节点删除,缺点是这种方式在竞争激烈的情况下会触发羊群效应(接收到通知后,只有一个请求有效,其余请求都是无效的,然而zk却需要处理所有请求)