分布式架构之Zookeeper实现分布式锁

一、实现分布式锁

1、作用

分布式锁可以应用于分布式中选举 leader,即拥有锁的就是leader,也可以用于分布式中并发控制(如支付业务或下单业务等保证一致性),分布式系统中当使用TCC事务模型时,没有数据库资源锁的支撑,需要在业务层进行事务隔离,此时可以用zookeepe实现,获得锁的即可操作资源。常用的另外还有另外两种 基于数据库的锁、基于redis的锁,这里且不详述。

2、思路

  1. 保持独占
  • 每个客户端都尝试创建同一个 znode,但只有一个可以成功创建,创建成功的客户端即获取到锁
  • 当该客户端处理完业务删除该节点,释放锁
  • 其他客户端观察 znode的删除,又回到刚开始一样,抢占创建同个 znode,最终获取锁

     2.控制时序(引用石杉哥的一张图)

    分布式架构之Zookeeper实现分布式锁_第1张图片

  • 给需要得到锁的客户端各自创建一个临时有序的顺序znode,作为子节点,即顺序号最小的客户端便持有锁(之所以要临时是防止出现客户端宕机而该节点没有删除,一直持有锁又没有用,像废弃的节点一直拿锁但其他节点一直得不到锁形成了死锁)
  • 当删除了前面顺序号小的znode节点,则释放锁,锁将留给后续排队的节点
  • 客户端观察znode删除,判断自己是不是列表中序号最小,是则获得锁,不是则继续监听

3、问题

  • 羊群效应:如果每个客户端都监听锁的释放,当锁释放时每个客户端都收到通知导致服务器压力大,解决办法是只需要通知排序号在后面一个的客户端
  • 连接丢失:当连接丢失后哪些持久化的节点怎么重新匹配上子节点,解决办法是在znode名称嵌入一个sessionId,通过id匹配

4、自带的客户端实现方式(控制时序)

如果出现同个ip限制连接问题,可到zoo.cfg文件修改 maxClientCnxns 字段

如果是Error while calling watcher问题,可能是new Zookeeper() 时没有把watcher注册进去

  • 引入依赖


	org.apache.zookeeper
	zookeeper
	3.4.6






  • 我们创建 ZookeeperLock 类,里面包括方法有节点创建、获取锁、释放锁、监听锁等
public class ZookeeperLock {

    private ZooKeeper zooKeeper;
    private final static String IP = "192.168.1.60:2181";
    private final static int TIME_OUT = 5000;
    private final static String ROOT_PATH = "/lock";
    private final static String ROOT_LOCK_PATH = "/Lock";
    private String lock = null;

    public ZookeeperLock() throws Exception{
        zooKeeper = new ZooKeeper(IP, TIME_OUT, watcher);
    }


    /**
     * 节点创建
     * @throws Exception
     */
    public void createNode() throws Exception{
        Stat stat = zooKeeper.exists(ROOT_PATH, false);
        if(null == stat){
            //创建持久化的无序根节点
            zooKeeper.create(ROOT_PATH,new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        }
        //创建一个临时有序的子节点
        String lock = zooKeeper.create(ROOT_PATH + ROOT_LOCK_PATH, Thread.currentThread().getName().getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
        System.out.println(Thread.currentThread().getName() + "客户锁创建成功:" + lock);
        this.lock = lock;
    }


    /**
     * 请求获取锁
     * @throws Exception
     */
    public void acquire() throws Exception{
        List childNode = zooKeeper.getChildren(ROOT_PATH,false);//获取所有子节点
        Collections.sort(childNode);//排序
        int index = childNode.indexOf(lock.substring(ROOT_LOCK_PATH.length() + 1));//判断当前客户端的子节点是在子节点列表里面的位置 /lock/Lock
        if(index == 0){
            System.out.println(Thread.currentThread().getName() + "客户获取到锁," + lock);
        }else{
            String preLockPath = childNode.get(index - 1);//当前节点的上一个节点
            Stat stat = zooKeeper.exists(ROOT_PATH + "/" + preLockPath,watcher);
            if(null == stat){
                acquire();
            }else{
                System.out.println("等待上一个节点释放锁,当前为:" + lock + ",上一个节点为:" + preLockPath);
                synchronized (watcher) {
                    watcher.wait();
                }
                acquire();
            }
        }
    }


    /**
     * 释放锁
     * @throws KeeperException
     * @throws InterruptedException
     */
    public void releaseLock() throws KeeperException, InterruptedException {
        zooKeeper.delete(lock, -1);
        zooKeeper.close();
        System.out.println("释放锁");
    }


    /**
     * 监听锁的释放
     */
    public Watcher watcher = new Watcher() {
        @Override
        public void process(WatchedEvent watchedEvent) {
            System.out.println("我的竞争对手释放锁");
            //唤起客户端线程
            synchronized (this) {
                notifyAll();
            }
        }
    };

}
  • 接着我们模拟使用上面提供的方法,使用过程如第二点讲的锁的“思路”
@Component
public class ZooKeeperClient {
    public void service() throws Exception {
        //模拟多个获取锁的客户端
        for(int i=0;i<300;i++){
            ZookeeperLock zookeeperLock = new ZookeeperLock();
            zookeeperLock.createNode();//先各自创建一个有序节点
            zookeeperLock.acquire();//请求获取锁
            /**业务执行**/
            System.out.println("执行业务!");
            Thread.sleep(10000);
            /**业务执行**/
            zookeeperLock.releaseLock();//处理完业务释放锁
        }
    }
}
  • 最后我们写个控制器,模拟多个用户请求,在浏览器调用多次,我这里的访问地址是 http://localhost:8090/index
@RestController
public class IndexController {
    @Autowired
    ZooKeeperClient zooKeeperClient;

    @RequestMapping("/index")
    public String index(){
        try {
            zooKeeperClient.service();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "index:content";
    }
}
  • 结果如下

 分布式架构之Zookeeper实现分布式锁_第2张图片 分布式架构之Zookeeper实现分布式锁_第3张图片

5、使用curator框架,帮我们封装了很多东西,不需要我们自己实现(控制时序)

public class ZooKeeperClient{

    @Value("${zookeeper.url}")
    String url;
    
    String path = "/curatorLock";

    public void service() throws Exception {
        //创建客户端
        CuratorFramework curatorFramework = CuratorFrameworkFactory.newClient(url, new ExponentialBackoffRetry(1000, 3));
        curatorFramework.start();
        //创建锁节点,根节点路径 /zkLock
        InterProcessMutex interProcessMutex = new InterProcessMutex(curatorFramework, path);
        interProcessMutex.acquire();//请求获取锁
        /**业务执行**/
        Thread.sleep(5000);
        /**业务执行**/
        interProcessMutex.release();//完成业务,释放锁
        curatorFramework.close();//关闭客户端
    }

}

6、使用zkClint框架(保持独占)

 

 

 

你可能感兴趣的:(中间件)