首先,我们需要明白一个问题,分布式锁是什么?

  • 同一个JVM进程内,多个线程同步执行 ==> synchronized,Lock等锁
  • zookeeper setacl加密后密码忘记了怎么办_zookeeper

  • 分布式下,不同JVM进程访问同一资源 ==> Redis,Zookeeper等中间件
  • zookeeper setacl加密后密码忘记了怎么办_zookeeper_02

所以,一个多核CPU也可以认为是一个小型分布式系统,多核CPU的并发编程的问题也可以认为是微观的分布式锁问题。

那如何通过 Zookeeper 实现分布式锁呢?

1.同级节点唯一性

1)因为Zookeeper的同级节点唯一性,所以多个进程来向zookeeper创建一个相同名称节点,只会有一个成功。所以,可以通过在 zk 中创建同名 znode 来实现加锁(注:这里应该采用临时节点,因为临时节点会在会话关闭时消失,而进程拿锁也是,你都关了,还拿什么锁)

2)失败的进程全部通过watcher机制来监听zookeeper这个子节点的变化,一旦监听到该节点的删除事件,则再次触发所有进程去写锁

zookeeper setacl加密后密码忘记了怎么办_zookeeper_03

这种方式虽然简单可行,但是会产生惊群效应,即等锁释放后,所有等待进程都会被唤醒(zookeeper发送大量子节点变化信息给所有等待的客户端),但最终只有一个客户端获得锁。如果在集群够大的情况下,会对zookeeper服务器性能产生较大影响。

PS:通过 Redis 实现分布式锁

  • Redis 的 String 类型的 setnx(key, value) ,只有当 key 不存在时才能设置成功;所以同一时刻如果有多个请求过来,最终也只有一个进程会拿锁
  • 当拿锁失败的进程,会被设置休眠指定时间 – 尽量避免惊群效应
  • 拿锁进程操作完后,会删除当前 key
  • 阻塞的进程,待醒来后再次尝试 setnx(key,value)

那有什么更好的办法吗?答:利用 Zookpeer 的有序节点实现分布式锁

2.临时有序节点

所有客户端都在zookeeper的指定节点下创建一个临时有序节点,越早创建的顺序编号越小

  • 因此可以将最小的节点设置为获得锁
  • 失败的节点只用监听比自己小的节点,当比自己小的节点删除以后,客户端就会收到watcher事件,然后再判断自己是否是最小的是否获得锁。这样就不会导致惊群效应,因为每个客户端只监听一个节点

InterProcessMutex

curator对于锁这块做了一些封装,curator提供了 InterProcessMutex 这样一个api。除了分布式锁之外, 还提供了leader选举、分布式队列等常用的功能。

  • InterProcessMutex:分布式可重入排它锁
  • InterProcessSemaphoreMutex:分布式排它锁
  • InterProcessReadWriteLock:分布式读写锁
public static void main(String[] args) {
        CuratorFramework curatorFramework = CuratorFrameworkFactory.builder().
                connectString("39.105.136.112:2181").
                sessionTimeoutMs(5000).
                retryPolicy(new ExponentialBackoffRetry(1000, 3)).
                build();
        curatorFramework.start();
		
     	// 可重入排它锁 ,传入指定父节点
        final InterProcessMutex lock = new InterProcessMutex(curatorFramework, "locks/");

        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "-> 尝试获得锁");
                try {
                    lock.acquire(); // 加锁
                    System.out.println(Thread.currentThread().getName() + "-> 成功获得锁");
                } catch (Exception e) {
                    e.printStackTrace();
                }

                try {
                    lock.release(); // 释放锁
                    System.out.println(Thread.currentThread().getName() + "-> 释放锁成功");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }, "t" + i).start();
        }
    }