zookeeper的应用场景

本文深入探讨ZooKeeper在分布式系统中的核心应用,包括配置中心、命名服务、Master选举、集群管理和分布式队列与锁的实现。通过示例代码展示如何使用ZooKeeper解决实际问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ZooKeeper 典型应用场景

  • 数据发布订阅(配置中心)

  • 命名服务(解决配置问题等)

  • Master 选举

  • 集群管理(时时在集群里面节点的加入和离开)

  • 分布式队列(多个进程之间共享数据的时候可能用到)

  • 分布式锁(多个进程之间共享数据的时候可能用到)

 

ZooKeeper 实现配置中心

    解决系统参数配置,及动态改参问题

 

    用 ZooKeeper 实现配置中心

  • znode 能存储数据

  • Watch 能监听数据改变

  • 存放方式:

    • 一个配置项一个 znode
    • 一个配置文件一个 znode

 

    示例代码:

package com.study.mike.zookeeper;
import java.io.UnsupportedEncodingException;
import org.I0Itec.zkclient.exception.ZkMarshallingError;
import org.I0Itec.zkclient.serialize.ZkSerializer;
public class MyZkSerializer implements ZkSerializer {
   String charset = "UTF-8";
   public Object deserialize(byte[] bytes) throws ZkMarshallingError {
      try {
         return new String(bytes, charset);
      } catch (UnsupportedEncodingException e) {
         throw new ZkMarshallingError(e);
      }
   }
   public byte[] serialize(Object obj) throws ZkMarshallingError {
      try {
         return String.valueOf(obj).getBytes(charset);
      } catch (UnsupportedEncodingException e) {
         throw new ZkMarshallingError(e);
      }
   }
}
package com.study.mike.zookeeper;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.ZkClient;
import org.I0Itec.zkclient.serialize.BytesPushThroughSerializer;
/**
*
* 配置中心示例
*
*/
public class ConfigCenterDemo {
   // 1 将单个配置放到zookeeper上
   public void put2Zk() {
      ZkClient client = new ZkClient("ip:2181");
      client.setZkSerializer(new MyZkSerializer());
      String configPath = "/config1";
      String value = "1111111";
      if (client.exists(configPath)) {
         client.writeData(configPath, value);
      } else {
         client.createPersistent(configPath, value);
      }
      client.close();
   }

   // 将配置文件的内容存放到zk节点上
   public void putConfigFile2ZK() throws IOException {
      File f = new File(this.getClass().getResource("/config.xml").getFile());
      FileInputStream fin = new FileInputStream(f);
      byte[] datas = new byte[(int) f.length()];
      fin.read(datas);
      fin.close();
      ZkClient client = new ZkClient("ip:2181");
      client.setZkSerializer(new BytesPushThroughSerializer());
      String configPath = "/config2";
      if (client.exists(configPath)) {
         client.writeData(configPath, datas);
      } else {
         client.createPersistent(configPath, datas);
      }
      client.close();
   }
   // 需要配置的服务都从zk上取,并注册watch来实时获得配置更新
   public void getConfigFromZk() {
      ZkClient client = new ZkClient("ip:2181");
      client.setZkSerializer(new MyZkSerializer());
      String configPath = "/config1";
      String value = client.readData(configPath);
      System.out.println("从zk读到配置config1的值为:" + value);
      // 监控配置的更新
      client.subscribeDataChanges(configPath, new IZkDataListener() {
         @Override
         public void handleDataDeleted(String dataPath) throws Exception {
            // TODO Auto-generated method stub
         }
         @Override
         public void handleDataChange(String dataPath, Object data) throws Exception {
            System.out.println("获得更新的配置值:" + data);
         }
      });
      // 这里只是为演示实时获取到配置值更新而加的等待。实际项目应用中根据具体场景写(可用阻塞方式)
      try {
         Thread.sleep(5 * 60 * 1000);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
   }
   public static void main(String[] args) throws IOException {
      ConfigCenterDemo demo = new ConfigCenterDemo();
      demo.put2Zk();
      demo.putConfigFile2ZK();
      demo.getConfigFromZk();
   }
}

    在zookeeper上修改配置信息,可看见时时动态更新:

 

ZooKeeper 实现命名服务

    何为命名服务

  • 示例:解决服务 A 可以动态得到服务 B 的调用地址,利用zookeeper中的Watcher监听机制实现。

ZooKeeper 实现 Master 选举

    当 Master 发生故障,这个系统还是要可以用的。这个时候就重新选举主节点。

    如何自动选举出新的 Master?

    ZooKeeper 只是实现选举方式的一种,有很多种方式都可以使用。

ZooKeeper 如何来实现 Master 选举?

  • 除了下图,还可以使用最小节点方式来竞选 Leader

示例代码实现模仿选举实现:

package com.study.mike.zookeeper;
import java.util.concurrent.CountDownLatch;
import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.ZkClient;
import org.I0Itec.zkclient.exception.ZkNodeExistsException;
public class MasterElectionDemo {
   static class Server {
      private String cluster, name, address;
      private final String path, value;
      private String master;
      public Server(String cluster, String name, String address) {
         super();
         this.cluster = cluster;
         this.name = name;
         this.address = address;
         path = "/" + this.cluster + "Master";
         value = "name:" + name + " address:" + address;
         ZkClient client = new ZkClient("ip:2181");
         client.setZkSerializer(new MyZkSerializer());
         new Thread(new Runnable() {
            @Override
            public void run() {
               electionMaster(client);
            }
         }).start();
      }
      public void electionMaster(ZkClient client) {
         try {
            client.createEphemeral(path, value);
            master = client.readData(path);
            System.out.println(value + "创建节点成功,成为Master");
         } catch (ZkNodeExistsException e) {
            master = client.readData(path);
            System.out.println("Master为:" + master);
         }
         // 为阻塞自己等待而用
         CountDownLatch cdl = new CountDownLatch(1);
         // 注册watcher
         IZkDataListener listener = new IZkDataListener() {
            @Override
            public void handleDataDeleted(String dataPath) throws Exception {
               System.out.println("-----监听到节点被删除");
               cdl.countDown();
            }
            @Override
            public void handleDataChange(String dataPath, Object data) throws Exception {
            }
         };
         client.subscribeDataChanges(path, listener);
         // 让自己阻塞
         if (client.exists(path)) {
            try {
               cdl.await();
            } catch (InterruptedException e1) {
               e1.printStackTrace();
            }
         }
         // 醒来后,取消watcher
         client.unsubscribeDataChanges(path, listener);
         // 递归调自己(下一次选举)
         electionMaster(client);

      }
   }
   public static void main(String[] args) {
      // 测试时,依次开启多个Server实例java进程,然后停止获取的master的节点,看谁抢到Master
//     Server s = new Server("cluster1", "server1", "192.168.1.11:8991");
//     Server s = new Server("cluster1", "server2", "192.168.1.11:8992");
//    Server s = new Server("cluster1", "server3", "192.168.1.11:8993");
       Server s = new Server("cluster1", "server4", "192.168.1.11:8994");
   }
}

ZooKeeper 实现分布式队列

    利用顺序节点

    无界队列:

  • 在 Queue 节点上创建顺序节点,顺序代表了入队顺序。

  • 消费者,先进先出,消费者获取所有子节点,移除最小号节点

 

    有界队列:

  • 实现比无界队列复杂,需要用到分布式锁。

ZooKeeper 实现分布式锁

    ZooKeeper 实现分布式锁方式一:

  • 临时节点创建节点,利用临时节点唯一性。

  • 未争抢到锁利用 Watch 机制监听节点,节点删除后,进入下一轮的争抢,循环。

  • 使用临时节点(避免服务挂掉,节点不会消失,不释放锁)

 

    缺点:

  • 惊群效应

    • 如:有 1000 并发线程来争抢锁,只有一个线程能抢到,其他 999 线程进入阻塞等待,Watch 机制,当抢到锁的线程释放后,都要通知 999 线程进行抢锁。

    • 这样容易对网络冲击,甚至导致服务崩溃。

    • 适用于数据量小的场景。

package com.study.mike.zookeeper;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.ZkClient;
import org.I0Itec.zkclient.exception.ZkNodeExistsException;
public class ZKDistributeLock implements Lock {
   private String lockPath;
   private ZkClient client;
   // 锁重入计数
   private ThreadLocal<Integer> reentrantCount = new ThreadLocal<>();
   public ZKDistributeLock(String lockPath) {
      super();
      this.lockPath = lockPath;
      client = new ZkClient("ip:2181");
      client.setZkSerializer(new MyZkSerializer());
   }
   @Override
   public boolean tryLock() { // 不会阻塞
      if (this.reentrantCount.get() != null) {
         int count = this.reentrantCount.get();
         if (count > 0) {
            this.reentrantCount.set(++count);
            return true;
         }
      }
      // 创建节点
      try {
         client.createEphemeral(lockPath);
         this.reentrantCount.set(1);
      } catch (ZkNodeExistsException e) {
         return false;
      }
      return true;
   }
   @Override
   public void unlock() {
      // 重入的释放锁处理
      if (this.reentrantCount.get() != null) {
         int count = this.reentrantCount.get();
         if (count > 1) {
            this.reentrantCount.set(--count);
            return;
         } else {
            this.reentrantCount.set(null);
         }
      }
      client.delete(lockPath);
   }
   @Override
   public void lock() { // 如果获取不到锁,阻塞等待
      if (!tryLock()) {
         // 没获得锁,阻塞自己
         waitForLock();
         // 再次尝试
         lock();
      }
   }
   private void waitForLock() {
      CountDownLatch cdl = new CountDownLatch(1);
      IZkDataListener listener = new IZkDataListener() {
         @Override
         public void handleDataDeleted(String dataPath) throws Exception {
            System.out.println("----收到节点被删除了-------------");
            cdl.countDown();
         }
         @Override
         public void handleDataChange(String dataPath, Object data) throws Exception {
         }
      };
      client.subscribeDataChanges(lockPath, listener);
      // 阻塞自己
      if (this.client.exists(lockPath)) {
         try {
            cdl.await();
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      }
      // 取消注册
      client.unsubscribeDataChanges(lockPath, listener);
   }
   @Override
   public void lockInterruptibly() throws InterruptedException {
      // TODO Auto-generated method stub
   }

   @Override
   public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
      // TODO Auto-generated method stub
      return false;
   }

   @Override
   public Condition newCondition() {
      // TODO Auto-generated method stub
      return null;
   }
   public static void main(String[] args) {
      // 并发数
      int currency = 50;
      // 循环屏障
      CyclicBarrier cb = new CyclicBarrier(currency);
      // 多线程模拟高并发
      for (int i = 0; i < currency; i++) {
         new Thread(new Runnable() {
            public void run() {
               System.out.println(Thread.currentThread().getName() + "---------我准备好---------------");
               // 等待一起出发
               try {
                  cb.await();
               } catch (InterruptedException | BrokenBarrierException e) {
                  e.printStackTrace();
               }
               ZKDistributeLock lock = new ZKDistributeLock("/distLock11");

               try {
                  lock.lock();
                  System.out.println(Thread.currentThread().getName() + " 获得锁!");
               } finally {
                  lock.unlock();
               }
            }
         }).start();
      }
   }
}

 

ZooKeeper 实现分布式锁方式二:

  • 创建临时顺序节点

  • 关注比自己小的前一个节点,Watch 机制,如 98 监听 97 。

 

 

package com.study.mike.zookeeper;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.ZkClient;
import org.I0Itec.zkclient.exception.ZkNodeExistsException;
public class ZKDistributeImproveLock implements Lock {
   /*
    * 利用临时顺序节点来实现分布式锁
    * 获取锁:取排队号(创建自己的临时顺序节点),然后判断自己是否是最小号,如是,则获得锁;不是,则注册前一节点的watcher,阻塞等待
    * 释放锁:删除自己创建的临时顺序节点
    */
   private String lockPath;
   private ZkClient client;
   private ThreadLocal<String> currentPath = new ThreadLocal<>();
   private ThreadLocal<String> beforePath = new ThreadLocal<>();
   // 锁重入计数
   private ThreadLocal<Integer> reentrantCount = new ThreadLocal<>();
   public ZKDistributeImproveLock(String lockPath) {
      super();
      this.lockPath = lockPath;
      client = new ZkClient("ip:2181");
      client.setZkSerializer(new MyZkSerializer());
      if (!this.client.exists(lockPath)) {
         try {
            this.client.createPersistent(lockPath);
         } catch (ZkNodeExistsException e) {

         }
      }
   }
   @Override
   public boolean tryLock() {
      if (this.reentrantCount.get() != null) {
         int count = this.reentrantCount.get();
         if (count > 0) {
            this.reentrantCount.set(++count);
            return true;
         }
      }
      if (this.currentPath.get() == null) {
         currentPath.set(this.client.createEphemeralSequential(lockPath + "/", "aaa"));
      }
      // 获得所有的子
      List<String> children = this.client.getChildren(lockPath);
      // 排序list
      Collections.sort(children);
      // 判断当前节点是否是最小的
      if (currentPath.get().equals(lockPath + "/" + children.get(0))) {
         this.reentrantCount.set(1);
         return true;
      } else {
         // 取到前一个
         // 得到字节的索引号
         int curIndex = children.indexOf(currentPath.get().substring(lockPath.length() + 1));
         beforePath.set(lockPath + "/" + children.get(curIndex - 1));
      }
      return false;
   }
   @Override
   public void lock() {
      if (!tryLock()) {
         // 阻塞等待
         waitForLock();
         // 再次尝试加锁
         lock();
      }
   }
   private void waitForLock() {
      CountDownLatch cdl = new CountDownLatch(1);
      // 注册watcher
      IZkDataListener listener = new IZkDataListener() {
         @Override
         public void handleDataDeleted(String dataPath) throws Exception {
            System.out.println("-----监听到节点被删除");
            cdl.countDown();
         }
         @Override
         public void handleDataChange(String dataPath, Object data) throws Exception {
         }
      };
      client.subscribeDataChanges(this.beforePath.get(), listener);
      // 怎么让自己阻塞
      if (this.client.exists(this.beforePath.get())) {
         try {
            cdl.await();
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      }
      // 醒来后,取消watcher
      client.unsubscribeDataChanges(this.beforePath.get(), listener);
   }

   @Override
   public void unlock() {
      // 重入的释放锁处理
      if (this.reentrantCount.get() != null) {
         int count = this.reentrantCount.get();
         if (count > 1) {
            this.reentrantCount.set(--count);
            return;
         } else {
            this.reentrantCount.set(null);
         }
      }
      // 删除节点
      this.client.delete(this.currentPath.get());
   }
   @Override
   public void lockInterruptibly() throws InterruptedException {
      // TODO Auto-generated method stub
   }
   @Override
   public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
      // TODO Auto-generated method stub
      return false;
   }
   @Override
   public Condition newCondition() {
      // TODO Auto-generated method stub
      return null;
   }
   public static void main(String[] args) {
      // 并发数
      int currency = 50;
      // 循环屏障
      CyclicBarrier cb = new CyclicBarrier(currency);
      // 多线程模拟高并发
      for (int i = 0; i < currency; i++) {
         new Thread(new Runnable() {
            public void run() {
               System.out.println(Thread.currentThread().getName() + "---------我准备好---------------");
               // 等待一起出发
               try {
                  cb.await();
               } catch (InterruptedException | BrokenBarrierException e) {
                  e.printStackTrace();
               }
               ZKDistributeImproveLock lock = new ZKDistributeImproveLock("/distLock");
               try {
                  lock.lock();
                  System.out.println(Thread.currentThread().getName() + " 获得锁!");
               } finally {
                  lock.unlock();
               }
            }
         }).start();
      }
   }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值