zookeeper入门个人笔记,从安装到分布式锁的实现,常用命令与java客户端操作zookeeper

Zookeeper

1 Zookeeper概述

1.1 工作机制

  • 概念:zookeeper是一种面向分布式应用程序的分布式开源协调服务。
  • zookeeper是文件系统和通知机制的融合

1.2 主要特点

  • 顺序一致性:来自客户端的请求将按照发送顺序依次执行
  • 原子性:数据的更新要么成功,要么失败
  • 单个系统映像:客户端看到相同的服务视图
  • 可靠性:应用更新后,它将从该时间持续存在,直到数据被更新
  • 及时性:保证系统的客户端视图在一定时间范围内是最新的

1.3 数据结构

  • 分层次的命名空间,与Unix的文件系统类似,整体可以看作是一个文件树,但与标准文件系统不同,zookeeper命名空间中的每个节点都可以具有与其关联的数据以及子节点。节点用znode表示,每个znode默认能够存储1MB的数据,并且每个znode可以通过其路径唯一标识。节点的路径都是绝对路径,没有相对路径。

注:临时节点不能创建子节点

1.4 实现

  • zookeeper服务的高级组件,除请求处理器外,构成zookeeper的每个服务器都会复制每个组件的副本

    image-20220506092156312

1.5 主要应用

  • 统一命名服务
  • 统一配置管理
  • 统一集群管理
  • 服务器节点动态上下线
  • 软负载均衡
  • 分布式锁

2 zookeeper安装

2.1 单机安装

#从官网下载对应版本的压缩包,解压文件到指定安装路径
tar -zxvf apache-zookeeper-3.7.1-bin.tar.gz -C /opt/softwares/
#更改安装目录名称
cd /opt/softwares/
mv apache-zookeeper-3.7.1-bin zookeeper-3.7.1
#修改配置文件名称
cd /opt/softwares/zookeeper-3.7.1/conf
mv zoo_sample.cfg zoo.cfg
#创建zookeeper数据存放目录
mkdir /opt/softwares/zookeeper-3.7.1/zkData
#修改内容
vim zoo.cfg
dataDir=/opt/softwares/zookeeper-3.7.1/zkData
  • 服务启动与客户端连接
cd /opt/softwares/zookeeper-3.7.1/
#启动zookeeper
bin/zkServer.sh start
#查看状态
bin/zkServer.sh status
#停止服务
bin/zkServer.sh stop
#启动客户端
bin/zkClid.sh 
#退出客户端
quit
  • zoo.cfg配置文件内容
# The number of milliseconds of each tick
tickTime=2000
# The number of ticks that the initial 
# synchronization phase can take
# LF初始通信时限
initLimit=10
# The number of ticks that can pass between 
# sending a request and getting an acknowledgement 
# LF同步通信时限
syncLimit=5
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just 
# example sakes.
dataDir=/opt/softwares/zookeeper-3.7.1/zkData
# the port at which the clients will connect
clientPort=2181
# the maximum number of client connections.
# increase this if you need to handle more clients
#maxClientCnxns=60

2.2 集群配置

  • 在zkData目录下新建文件myid
cd /opt/softwares/zookeeper-3.7.1/zkData
#新建文件并输入服务器编号
vim myid 
  • 在配置文件中增加如下的内容
##########################Cluster基本格式############################
server.1=host1:port1:port2
server.2=host2:port1:port2
server.3=host3:port1:port2
##########################伪集群配置Cluster##########################
server.1=localhost:2888:3888
server.2=localhost:2889:3889
server.3=localhost:2890:3890
###########################纯集群配置Cluster##########################
server.1=myvm001:2888:3888
server.2=myvm001:2888:3888
server.3=myvm001:2888:3888
  • 增加内容解读
server.A=B:C:D

A表示服务器的标识,是一个唯一的数字通过在dataDir目录下新建文件myid指定
B表示服务器的地址(可以是ip地址,设置了主机名映射后也可以是主机名)
C表示集群中Follower与Leader进行信息交换的端口号
D表示集群选举时各个服务器之间进行通信的端口号
  • 集群启停脚本
#!/bin/bash
case $1 in
"start"){
    for i in myvm001 myvm002 myvm003
    do
        echo ===============zookeeper $i 启动======================
        ssh $i "/opt/softwares/zookeeper-3.7.1/bin/zkServer.sh start"
    done
};;
"stop"){
    for i in myvm001 myvm002 myvm003
    do
        echo ===============zookeeper $i 停止=======================
        ssh $i "/opt/softwares/zookeeper-3.7.1/bin/zkServer.sh stop"
    done
};;
"status"){
    for i in myvm001 myvm002 myvm003
    do
        echo ===============zookeeper $i 状态=======================
        ssh $i "/opt/softwares/zookeeper-3.7.1/bin/zkServer.sh status"
    done
};;
esac

3 客户端常用命令与节点介绍

3.1 常用命令

常用命令命令作用
help显示所有的操作命令
ls 【-w -s】 path查看当前path节点的子节点 -w 监听子节点的变化 -s 显示当前节点的统计结构信息
create 【-e -s】 path创建节点 -e 创建临时节点 -s创建带有序列的节点
get 【-w -s】path获取节点的值 -w 监听节点内容变化 -s 显示节点的统计结构信息
set path data设置节点的值
stat path显示节点的统计结构的信息
delete path删除节点
deleteall递归删除节点

3.2 节点分类与节点内容

  • 节点分类

    • 持久化节点(PERSISTENT)
    • 持久化顺序编号节点(PERSISTENT_SEQUENTIAL)
    • 临时目录节点(EPHEMERAL)
    • 临时顺序编号目录节点(EPHEMERAL_SEQUENTIAL)
    • 容器节点(新增)
    • TTL节点(新增)
  • 节点维护的stat structure

    Zxid:每次对zookeeper状态的修改都会产生一个ZooKeeper Transaction Id

    • cZxid:创建节点时的Zxid
    • mZxid:最后一次修改节点时的Zxid
    • pZxid:最后一次修改节点的子节点的Zxid
    • ctime:节点创建时的时间
    • mtime:节点最后依次被修改的时间
    • cversion:改变节点的子节点的次数
    • dataVersion:改变节点的数据的次数
    • aclVersion:改变节点的ACL(Access Control List)的次数
    • ephemeralOwner:节点所有者的会话ID(如果当前节点时临时节点),不是临时节点该值为0
    • dataLength:节点的数据字段的长度
    • numChildren:节点的儿子的个数

3.3 监听节点

  • get -w path

    • 监听节点的修改 NodeDataChanged

    • 监听节点的删除 NodeDeleted

      注:无法监听节点的创建

  • ls -w path

    • 监听子节点的增加 NodeChildrenChanged

    • 监听子节点的修改

    • 监听本节点的删除

      注:无法监听本节点的修改,同时无法监听子节点的修改

3.4 权限控制(Access Control List)

  • ACL的组成
    • 权限模式
    • 权限对象
    • 权限信息
  • 权限模式
    • 范围验证
    • 口令验证
  • 权限信息
    • 创建权限
    • 更新权限
    • 读取权限
    • 删除权限
    • 管理者权限

4 JAVA客户端连接zookeeper

  1. 引入依赖

        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.5.7</version>
        </dependency>
    
  2. 配置客户端连接信息

    zookeeper:
      address: myvm001:2181,myvm002:2181,myvm003:2181
      timeout: 5000
    
    package com.lzx.zookeeper.config;
    
    import lombok.extern.slf4j.Slf4j;
    import org.apache.zookeeper.WatchedEvent;
    import org.apache.zookeeper.Watcher;
    import org.apache.zookeeper.ZooKeeper;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    import java.util.concurrent.CountDownLatch;
    
    @Configuration
    @Slf4j
    public class ZooConfig {
    
        //服务器地址
        @Value("${zookeeper.address}")
        private String connectString;
    
        //连接超时时间
        @Value("${zookeeper.timeout}")
        private int timeout;
    
        //客户端名称
        @Bean(name = "zkClient")
        public ZooKeeper zkClient(){
            ZooKeeper zooKeeper = null;
            try {
                CountDownLatch countDownLatch = new CountDownLatch(1);
                zooKeeper = new ZooKeeper(connectString, timeout, new Watcher() {
                    @Override
                    public void process(WatchedEvent watchedEvent) {
                        if(Event.KeeperState.SyncConnected==watchedEvent.getState()){
                            countDownLatch.countDown();
                        }
                    }
                });
                countDownLatch.await();
                log.info("zookeeper初始化连接状态:{}",zooKeeper.getState());
            } catch (Exception e) {
                log.error("zookeeper初始化连接异常:{}",e);
            }
            return zooKeeper;
        }
    }
    
  3. 测试相关API

    • 监听器实现

      package com.lzx.zookeeper.watcher;
      
      import lombok.extern.slf4j.Slf4j;
      import org.apache.zookeeper.WatchedEvent;
      import org.apache.zookeeper.Watcher;
      
      @Slf4j
      public class WatcherImp implements Watcher {
          @Override
          public void process(WatchedEvent event) {
              log.info("【Watcher监听事件】={}",event.getState());
              log.info("【监听路径为】={}",event.getPath());
              //  三种监听类型: 创建,删除,更新
              log.info("【监听的类型为】={}",event.getType());
          }
      }
      
    • 相关API实现

      package com.lzx.zookeeper.utils;
      
      import com.lzx.zookeeper.watcher.WatcherImp;
      import lombok.extern.slf4j.Slf4j;
      import org.apache.zookeeper.*;
      import org.apache.zookeeper.data.Stat;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.stereotype.Component;
      
      import javax.annotation.PostConstruct;
      import java.util.List;
      
      @Slf4j
      @Component
      public class ZooUtil {
      
          @Autowired
          private ZooKeeper zkClient;
      
          /**
           * 判断指定节点是否存在
           * @param path
           * @param needWatch  指定是否复用zookeeper中默认的Watcher
           * @return
           */
          public Stat exists(String path, boolean needWatch){
              try {
                  return zkClient.exists(path,needWatch);
              } catch (Exception e) {
                  log.error("【断指定节点是否存在异常】{},{}",path,e);
                  return null;
              }
          }
      
          /**
           *  检测结点是否存在 并设置监听事件
           *      三种监听类型: 创建,删除,更新
           *
           * @param path
           * @param watcher  传入指定的监听类
           * @return
           */
          public Stat exists(String path,Watcher watcher ){
              try {
                  return zkClient.exists(path,watcher);
              } catch (Exception e) {
                  log.error("【断指定节点是否存在异常】{},{}",path,e);
                  return null;
              }
          }
      
          /**
           * 创建持久化节点
           * @param path
           * @param data
           */
          public boolean createNode(String path, String data){
              try {
                  zkClient.create(path,data.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
                  return true;
              } catch (Exception e) {
                  log.error("【创建持久化节点异常】{},{},{}",path,data,e);
                  return false;
              }
          }
      
      
          /**
           * 修改持久化节点
           * @param path
           * @param data
           */
          public boolean updateNode(String path, String data){
              try {
                  //zk的数据版本是从0开始计数的。
                  //如果客户端传入的是-1,则表示zk服务器需要基于最新的数据进行更新。
                  //如果对zk的数据节点的更新操作没有原子性要求则可以使用-1.
                  //version参数指定要更新的数据的版本, 如果version和真实的版本不同, 更新操作将失败. 指定version为-1则忽略版本检查
                  zkClient.setData(path,data.getBytes(),-1);
                  return true;
              } catch (Exception e) {
                  log.error("【修改持久化节点异常】{},{},{}",path,data,e);
                  return false;
              }
          }
      
          /**
           * 删除持久化节点
           * @param path
           */
          public boolean deleteNode(String path){
              try {
                  //version参数指定要更新的数据的版本, 如果version和真实的版本不同, 更新操作将失败. 指定version为-1则忽略版本检查
                  zkClient.delete(path,-1);
                  return true;
              } catch (Exception e) {
                  log.error("【删除持久化节点异常】{},{}",path,e);
                  return false;
              }
          }
      
          /**
           * 获取当前节点的子节点(不包含孙子节点)
           * @param path 父节点path
           */
          public List<String> getChildren(String path) throws KeeperException, InterruptedException{
              List<String> list = zkClient.getChildren(path, false);
              return list;
          }
      
          /**
           * 获取指定节点的值
           * @param path
           * @return
           */
          public  String getData(String path, Watcher watcher){
              try {
                  Stat stat=new Stat();
                  byte[] bytes=zkClient.getData(path,watcher,stat);
                  return  new String(bytes);
              }catch (Exception e){
                  e.printStackTrace();
                  return  null;
              }
          }
      
          /**
           * 测试方法  初始化
           */
          @PostConstruct
          public  void init(){
              log.info("【执行初始化测试方法。。。。。。。。。。。。】");
              String path="/zk-watch-test";
              Stat stat = exists(path, null);
              log.info("【判断节点是否存在,返回stat】={}",stat);
              String data = "测试监听";
              createNode(path,data);
              log.info("【创建节点,初始值】={}",data);
              String value=getData(path,new WatcherImp());
              log.info("【执行初始化测试方法getData返回值。。。。。。。。。。。。】={}",value);
              //更新节点,触发删除事件
              updateNode(path,"测试更新");
              value=getData(path,new WatcherImp());
              log.info("【执行更改操作后节点的值】={}",value);
              // 删除节点,触发监听事件
              deleteNode(path);
          }
      
      }
      
      

5 Zookeeper实现分布式锁

5.1独占式锁

  • 通过单节点实现

5.2 顺序锁

  • 通过临时顺序节点进行实现
5.2.1 zookeeper+java API实现
package com.lzx.zookeeper.dislock;


import lombok.extern.slf4j.Slf4j;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;

import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;

/**
 * 分布式锁客户端
 */
@Slf4j
public class DisClient {
    //当前连接创建的节点
    private String curNodePath;
    //当前节点的上一个节点
    private String preNodePath;
    //zookeeper客户端
    private ZooKeeper zkClient = null;
    //创建连接时的线程计数器
    private CountDownLatch clientLatch=null;
    //在当前节点等待的线程计数器
    private CountDownLatch waitLatch =null;
    //根节点
    private String lockRoot = "/disLock";
    //子节点的前缀
    private final static String SEQ_LOCK_PREFIX="/lock-";

    public DisClient(){
        //初始化客户端连接
        try {
            clientLatch = new CountDownLatch(1);
            zkClient = new ZooKeeper("myvm001:2181,myvm002:2181,myvm003:2181", 2000, new Watcher() {
                @Override
                public void process(WatchedEvent watchedEvent) {
                    if(watchedEvent.getState()==Event.KeeperState.SyncConnected){
                        clientLatch.countDown();
                    }
                }
            });
            clientLatch.await();
            //利用同步代码块创建根节点
            synchronized (DisClient.class){
                if(zkClient.exists(lockRoot,false)==null){
                    zkClient.create(lockRoot,"the root of disLock".getBytes(),
                            ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);
                }
            }
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }

    }

    //上锁
    public void lock() throws KeeperException, InterruptedException {
        if(tryLock()){
            log.info("线程:---{}---获得了锁",Thread.currentThread().getName());
        }else {
            //获取锁失败,等待上一个节点释放锁
            waitForLock();
            log.info("线程:---{}---在等待锁",Thread.currentThread().getName());
            //重新获取锁
            lock();
        }
    }

    public boolean tryLock() throws KeeperException, InterruptedException {

        //在根节点创建当前节点
        if(curNodePath==null||"".equals(curNodePath)){
            curNodePath = zkClient.create(lockRoot + SEQ_LOCK_PREFIX, new byte[0],
                    ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
        }
        List<String> children = zkClient.getChildren(lockRoot, false);
        if(children.size()==1){
            return true;
        }else{
            Collections.sort(children);
            int curIndex = children.indexOf( curNodePath.substring((lockRoot + "/").length()));
            if(curIndex==-1){
                log.error("数据异常");
            }else if(curIndex==0){
                return true;
            }else{
                preNodePath=lockRoot+"/"+children.get(curIndex-1);
            }
        }
        return false;
    }

    public void waitForLock() throws KeeperException, InterruptedException {
        waitLatch=new CountDownLatch(1);
        zkClient.getData(preNodePath, new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {
                if(watchedEvent.getType()== Event.EventType.NodeDeleted&&
                        preNodePath.equals(watchedEvent.getPath())){
                    waitLatch.countDown();
                }
            }
        },new Stat());
        waitLatch.await();
    }

    public void unLock() throws KeeperException, InterruptedException {
        zkClient.delete(curNodePath,-1);
        log.info("线程:---{}---释放了锁",Thread.currentThread().getName());
    }
}


package com.lzx.zookeeper.dislock;

import org.apache.zookeeper.KeeperException;

public class DisLockTest {
    public static void main(String[] args) {
        for(int i =0;i<10;i++){
            new Thread(new GetLockRunnable()).start();
        }
    }

    static class GetLockRunnable implements Runnable{
        @Override
        public void run() {
            DisClient disClient = new DisClient();
            try {
                disClient.lock();
            } catch (KeeperException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            try {
                disClient.unLock();
            } catch (KeeperException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

5.2.2 Curator客户端实现
package com.lzx.zookeeper.dislock;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;

import java.util.concurrent.TimeUnit;

public class CuratorDisLock {
    //根节点
    private static final String LOCK_ROOT_NODE = "/curatorDisLock";
    //
    private CuratorFramework curatorFramework;
    //
    private RetryPolicy retryPolicy;
    private String connectString = "myvm001:2181,myvm002:2181,myvm003:2181";
    private int connectTimeout = 2000;

    public CuratorDisLock(){
        //初始化客户端连接
        retryPolicy = new ExponentialBackoffRetry(3000,3);
        curatorFramework = CuratorFrameworkFactory.builder().connectString(connectString)
                .connectionTimeoutMs(connectTimeout)
                .retryPolicy(retryPolicy).build();
        curatorFramework.start();
    }
    public static void main(String args[]){
        for(int i =0;i<10;i++){
            new Thread(()->{
                new CuratorDisLock().lock();
            }).start();
        }
    }

    public void lock(){
        InterProcessMutex lock = new InterProcessMutex(curatorFramework, LOCK_ROOT_NODE);
        try {
            if (lock.acquire(1, TimeUnit.SECONDS)) {
                System.out.println(Thread.currentThread().getName()+"获取锁成功");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            try {
                if(lock.isAcquiredInThisProcess()){
                    lock.release();
                    System.out.println(Thread.currentThread().getName()+"释放锁");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Zookeeper分布式锁实现原理: 1. 创建一个Zookeeper节点作为锁的根节点。 2. 当一个进程想要获取锁时,它在根节点下创建一个临时顺序节点。 3. 进程通过比较自己创建的节点和兄弟节点的名称来判断自己是否获得了锁。 4. 如果进程没有获得锁,它就会监听它前面的一个节点,当这个节点被删除时,它就可以再次比较自己的节点和兄弟节点的名称来判断自己是否获得了锁。 5. 当进程释放锁时,它会删除它自己创建的节点。 下面是一个简单的Zookeeper分布式锁Java实现: ```java import org.apache.zookeeper.*; import org.apache.zookeeper.data.Stat; public class DistributedLock implements Watcher { private ZooKeeper zk; private String lockPath; private String myZnode; public DistributedLock(String zkHost, String lockPath) throws Exception { this.lockPath = lockPath; zk = new ZooKeeper(zkHost, 5000, this); if (zk.exists(lockPath, false) == null) { zk.create(lockPath, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); } } public void lock() throws Exception { myZnode = zk.create(lockPath + "/lock_", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL); while (true) { Stat stat = zk.exists(lockPath + "/" + getPrevNode(myZnode), true); if (stat == null) { return; } waitLock(); } } public void unlock() throws Exception { zk.delete(myZnode, -1); } private void waitLock() throws Exception { synchronized (this) { wait(); } } private String getPrevNode(String myZnode) { int index = myZnode.lastIndexOf("_"); return index == -1 ? "" : myZnode.substring(0, index); } public void process(WatchedEvent event) { synchronized (this) { notifyAll(); } } } ``` 使用示例: ```java public static void main(String[] args) throws Exception { DistributedLock lock = new DistributedLock("localhost:2181", "/mylock"); lock.lock(); try { // 执行任务 } finally { lock.unlock(); } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值