ZooKeeper——案例

1、服务器动态上下线

1.1、需求

某分布式系统中,主节点可以有多台,可以动态上下线,任意一台客户端都能实时感知到主节点服务器的上下线。

1.2、需求分析

在这里插入图片描述

1.3、具体实现

  1. 先在集群上创建 /servers 节点

    [zk: localhost:2181(CONNECTED) 3] create /servers "servers"
    Created /servers
    
  2. 在 Idea 中创建包名:zkcase1

  3. 服务器端向 Zookeeper 注册代码

    public class DistributeServer {
        public static String connectString =
                "192.168.196.128:2181,192.168.196.129:2181,192.168.196.130:2181";
    
        private static final int sessionTimeout = 20000;
        private ZooKeeper zkClient = null;
    
        private String parentNode = "/servers";
    
        public void getConnect() throws IOException {
            zkClient = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
                @Override
                public void process(WatchedEvent watchedEvent) {
    
                }
            });
        }
    
        //注册服务器
        public void registServer(String hostname) throws InterruptedException, KeeperException {
            String create = zkClient.create(parentNode + "/server", hostname.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
    
            System.out.println(hostname + " is online " + create);
        }
    
        //业务功能
        public void business(String hostname) throws InterruptedException {
            System.out.println(hostname + " is working ...");
    
            Thread.sleep(Long.MAX_VALUE);
        }
    
        public static void main(String[] args) throws InterruptedException, KeeperException, IOException {
            //1、获取zk连接
            DistributeServer server = new DistributeServer();
            server.getConnect();
    
            //2、利用zk连接注册服务器信息
            server.registServer(args[0]);
    
            //3、开启业务功能
            server.business(args[0]);
        }
    }
    
  4. 客户端代码

    public class DistributeClient {
        public static String connectString =
                "192.168.196.128:2181,192.168.196.129:2181,192.168.196.130:2181";
    
        private static final int sessionTimeout = 20000;
        private ZooKeeper zkClient = null;
    
        private String parentNode = "/servers";
    
        //创建到zk的客户端连接
        public void getConnection() throws IOException {
            zkClient = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
                @Override
                public void process(WatchedEvent watchedEvent) {
                    //再次启动监听
                    try {
                        getServerList();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (KeeperException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    
        //获取服务器列表信息
        private void getServerList() throws InterruptedException, KeeperException {
            //1、获取服务器节点信息,并且对父节点进行监听
            List<String> children = zkClient.getChildren(parentNode, true);
    
            //2、存储服务器信息列表
            ArrayList<String> servers = new ArrayList<>();
    
            //3、遍历所有节点,获取节点中的主机名称信息
            for (String child :
                    children) {
                byte[] data = zkClient.getData(parentNode + "/" + child, false, null);
    
                servers.add(new String(data));
            }
    
            //4、打印服务器列表信息
            System.out.println(servers);
        }
    
        //业务功能
        public void business() throws InterruptedException {
            System.out.println("client is working ...");
    
            Thread.sleep(Long.MAX_VALUE);
        }
    
        public static void main(String[] args) throws InterruptedException, KeeperException, IOException {
            //1、获取zk连接
            DistributeClient client = new DistributeClient();
            client.getConnection();
    
            //2、获取servers的子节点信息,从中获取服务器信息列表
            client.getServerList();
    
            //3、业务进程启动
            client.business();
        }
    }
    

1.4、测试

  1. 在 Linux 命令行上操作增加减少服务器

    1. 启动 DistributeClient 客户端

    2. 在 hadoop102 上 zk 的客户端 /servers 目录上创建临时带序号节点

      [zk: localhost:2181(CONNECTED) 4] create -e -s /servers/hadoop102 "hadoop102"
      Created /servers/hadoop1020000000000
      [zk: localhost:2181(CONNECTED) 5] create -e -s /servers/hadoop103 "hadoop103"
      Created /servers/hadoop1030000000001
      
    3. 观察 Idea 控制台变化

      [hadoop102, hadoop103]
      
    4. 执行删除操作

      [zk: localhost:2181(CONNECTED) 6] delete /servers/hadoop1030000000001
      
    5. 观察 Idea 控制台变化

      [hadoop102]
      
  2. 在 Idea 上操作增加减少服务器

    1. 启动 DistributeClient 客户端(如果已经启动过,不需要重启)

    2. 启动 DistributeServer 服务

      点击 Edit Configurations…

      在弹出的窗口中(Program arguments)输入想启动的主机,例如,hadoop102

      回到 DistributeServer 的 main 方法 ,右键 ,在弹出的窗口中点击 Run “DistributeServer.main()”

      观察 DistributeServer 控制台,提示 hadoop102 is working

      观察 DistributeClient 控制台,提示 hadoop102 已经上线


2、ZooKeeper 分布式锁案例

什么叫做分布式锁呢?

比如说 “进程 1” 在使用该资源的时候,会先去获得锁,“进程 1” 获得锁以后会对该资源保持独占,这样其他进程就无法访问该资源,“进程 1” 用完该资源以后就将锁释放掉,让其他进程来获得锁,那么通过这个锁机制,我们就能保证了分布式系统中多个进程能够有序的访问该临界资源。那么我们把这个分布式环境下的这个锁叫作分布式锁。

在这里插入图片描述

2.1、原生 Zookeeper 实现分布式锁案例

  1. 分布式锁实现

    package lock;
    
    import org.apache.zookeeper.CreateMode;
    import org.apache.zookeeper.ZooDefs;
    import org.apache.zookeeper.ZooKeeper;
    
    import java.util.Collections;
    import java.util.List;
    import java.util.concurrent.locks.LockSupport;
    
    public class DistributedLock {
    
        private ZooKeeper zkClient;
        ThreadLocal<String> threadLocal = new ThreadLocal<>();
    
        public DistributedLock() {
            try {
                String connectString =
                        "192.168.196.128:2181,192.168.196.129:2181,192.168.196.130:2181";
                int sessionTimeout = 20000;
                zkClient = new ZooKeeper(connectString, sessionTimeout, null);
                // 判断节点 /exclusive_lock 是否存在
                if (zkClient.exists("/exclusive_lock", false) == null) {
                    // 不存在则创建节点
                    zkClient.create("/exclusive_lock", null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        public void lock() {
            try {
                // 创建对应的临时带序号节点
                String currentLockNode = zkClient.create("/exclusive_lock/" + "seq-", null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
                // 判断创建的节点是否是最小的序号节点,如果是获取到锁;如果不是,监听他序号前一个节点
                List<String> children = zkClient.getChildren("/exclusive_lock", false);
                // 如果children 只有一个值,那就直接获取锁;如果有多个节点,需要判断谁最小
                if (children.size() > 1) {
                    Collections.sort(children);
                    // 获取节点名称 seq-00000000
                    String thisNode = currentLockNode.substring("/exclusive_lock/".length());
                    // 通过 seq-00000000 获取该节点在children集合的位置
                    int index = children.indexOf(thisNode);
                    /**
                     * 因为在zkClient.create和zkClient.getChildren("/exclusive_lock", false);可能有其它线程也创建了节点,
                     * 所以并不是说只有 children.size() == 1 这个线程才是第一个创建节点的线程
                     */
                    if (index == 0) {// 如果自己就是第一个节点,那么获得锁,
                        System.out.println(Thread.currentThread().getName() + "获得锁");
                        threadLocal.set(currentLockNode);
                        return;
                    }
                    //
                    String preNode = "/exclusive_lock/" + children.get(index - 1);
                    Thread thread = Thread.currentThread();
                    // 监听它前一个节点的变化,如果前一个节点删除了,会调用回调函数把自己唤醒
                    zkClient.getData(preNode, watchedEvent -> LockSupport.unpark(thread), null);
                    // 把自己挂起
                    LockSupport.park();
                }
                threadLocal.set(currentLockNode);
                System.out.println(Thread.currentThread().getName() + "获得锁");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        public void unlock() {
            try {
                System.out.println(Thread.currentThread().getName() + "释放了锁");
                zkClient.delete(threadLocal.get(), -1);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
  2. 分布式锁测试

    1. 创建两个线程

      public class DistributedLockTest {
          public static void main(String[] args) {
              DistributedLock lock01 = new DistributedLock();
              Runnable task01 = () -> {
                  lock01.lock();
                  try {
                      TimeUnit.SECONDS.sleep(1);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
                  lock01.unlock();
              };
      
              DistributedLock lock02 = new DistributedLock();
              Runnable task02 = () -> {
                  lock02.lock();
                  try {
                      TimeUnit.SECONDS.sleep(1);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
                  lock02.unlock();
              };
      
              for (int i = 0; i < 10; i++) {
                  new Thread(task01, "server01-thread-" + i).start();
                  new Thread(task02, "server02-thread-" + i).start();
              }
          }
      }
      
    2. 观察控制台变化:

      server02-thread-0获得锁
      server02-thread-0释放了锁
      server02-thread-2获得锁
      server02-thread-2释放了锁
      server02-thread-1获得锁
      server02-thread-1释放了锁
      server02-thread-3获得锁
      server02-thread-3释放了锁
      server02-thread-4获得锁
      server02-thread-4释放了锁
      

2.2、Curator 框架实现分布式锁案例

  1. 原生的 Java API 开发存在的问题

    1. 会话连接是异步的,需要自己去处理。比如使用 CountDownLatch
    2. Watch 需要重复注册,不然就不能生效
    3. 开发的复杂性还是比较高的
    4. 不支持多节点删除和创建。需要自己去递归
  2. Curator 是一个专门解决分布式锁的框架,解决了原生 Java API 开发分布式遇到的问题

    详情请查看官方文档: https://curator.apache.org/index.html

  3. Curator 案例实操

    1. 添加依赖

      <!-- https://mvnrepository.com/artifact/org.apache.curator/curator-framework -->
      <dependency>
          <groupId>org.apache.curator</groupId>
          <artifactId>curator-framework</artifactId>
          <version>5.2.1</version>
      </dependency>
      
      <!-- https://mvnrepository.com/artifact/org.apache.curator/curator-recipes -->
      <dependency>
          <groupId>org.apache.curator</groupId>
          <artifactId>curator-recipes</artifactId>
          <version>5.2.1</version>
      </dependency>
      
      <!-- https://mvnrepository.com/artifact/org.apache.curator/curator-client -->
      <dependency>
          <groupId>org.apache.curator</groupId>
          <artifactId>curator-client</artifactId>
          <version>5.2.1</version>
      </dependency>
      
    2. 代码实现

      public class CuratorLockTest {
          private String rootNode = "/locks";
      
          // zookeeper server 列表
          String connectString =
                  "192.168.196.128:2181,192.168.196.129:2181,192.168.196.130:2181";
      
          // session超时时间
          private int sessionTimeout = 20000;
      
          //connect超时时间
          private int connectTimeout = 20000;
      
          public static void main(String[] args) {
              new CuratorLockTest().test();
          }
      
          //测试
          private void test() {
              //创建分布式锁 1
              final InterProcessMutex lock1 = new InterProcessMutex(getCuratorFramework(), rootNode);
      
              //创建分布式锁 2
              final InterProcessMutex lock2 = new InterProcessMutex(getCuratorFramework(), rootNode);
      
              new Thread(new Runnable() {
                  @Override
                  public void run() {
                      try {
                          lock1.acquire();
                          System.out.println("线程1获取锁");
      
                          //测试锁重入
                          lock1.acquire();
                          System.out.println("线程1再次获取锁");
                          Thread.sleep(5 * 1000);
                          lock1.release();
                          System.out.println("线程1释放锁");
                          lock1.release();
                          System.out.println("线程1再次释放锁");
                      } catch (Exception e) {
                          e.printStackTrace();
                      }
                  }
              }).start();
      
              new Thread(new Runnable() {
                  @Override
                  public void run() {
                      try {
                          lock2.acquire();
                          System.out.println("线程2获取锁");
      
                          //测试锁重入
                          lock2.acquire();
                          System.out.println("线程2再次获取锁");
                          Thread.sleep(5 * 1000);
                          lock2.release();
                          System.out.println("线程2释放锁");
                          lock2.release();
                          System.out.println("线程2再次释放锁");
                      } catch (Exception e) {
                          e.printStackTrace();
                      }
                  }
              }).start();
          }
      
          // 分布式锁初始化
          private CuratorFramework getCuratorFramework() {
              //重试策略,初试时间 3 秒,重试 3 次
              RetryPolicy policy = new ExponentialBackoffRetry(3000, 3);
      
              //通过工厂创建 Curator
              CuratorFramework client = CuratorFrameworkFactory.builder()
                      .connectString(connectString)
                      .connectionTimeoutMs(connectTimeout)
                      .sessionTimeoutMs(sessionTimeout)
                      .retryPolicy(policy).build();
      
              //开启连接
              client.start();
              System.out.println("zookeeper 初始化完成...");
              return client;
      
          }
      }
      
    3. 观察控制台变化

      zookeeper 初始化完成...
      线程2获取锁
      线程2再次获取锁
      线程2释放锁
      线程2再次释放锁
      线程1获取锁
      线程1再次获取锁
      线程1释放锁
      线程1再次释放锁
      

3、企业面试真题

3.1、选举机制

半数机制,超过半数的投票通过,即通过。

  1. 第一次启动选举规则:

    投票过半数时, 服务器 id 大的胜出

  2. 第二次启动选举规则:

    1. EPOCH 大的直接胜出
    2. EPOCH 相同,事务 id 大的胜出
    3. 事务 id 相同,服务器 id 大的胜出

3.2、生产集群安装多少 zk 合适?

安装奇数台。

生产经验:

  • 10 台服务器:3 台 zk;
  • 20 台服务器:5 台 zk;
  • 100 台服务器:11 台 zk;
  • 200 台服务器:11 台 zk。

服务器台数多:好处,提高可靠性;坏处:提高通信延时。

3.3、常用命令

ls、get、create、delete

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值