“Zookeeper:分布式锁”的版本间差异

来自Wikioe
跳到导航 跳到搜索
(Eijux移动页面Zookeeper:分布式锁实现Zookeeper:分布式锁,不留重定向)
无编辑摘要
第2行: 第2行:


== 关于 ==
== 关于 ==
<pre>
在单体的应用开发场景中涉及并发同步的时候,大家往往采用 Synchronized(同步)或者其他同一个 JVM 内 Lock 机制来解决多线程间的同步问题。
在分布式集群工作的开发场景中,就需要一种更加高级的锁机制来处理跨机器的进程之间的数据同步问题。这种跨机器的锁就是“分布式锁”。
</pre>
分布式锁是控制分布式系统之间同步访问共享资源的一种方式。
分布式锁是控制分布式系统之间同步访问共享资源的一种方式。


== Zookeeper分布式锁思路 ==
根据网络和书籍上的资料,主要有以下三种方式来实现分布式锁:
# 非公平排他锁:利用 Zookeeper 同一目录下 Znode 的唯一性;
# 公平排他锁:利用 Zookeeper 顺序节点的递增有序;
# 公平读写锁:(同上)
此外,以上还用到了:会话失效时临时节点自动删除的特性,以及节点的监听机制等。
* 以上都可以有可重入的实现方式。


下面介绍 zookeeper 如何实现分布式锁,讲解'''排他锁'''和'''共享锁'''两类分布式锁。
=== 思路一:非公平排他锁 ===
 
'''排他锁'''(Exclusive Locks),又被称为'''写锁'''或'''独占锁'''
== 排他锁 ==
'''排他锁'''(Exclusive Locks),又被称为'''写锁'''或'''独占锁'''
: 如果事务 T1 对数据对象 O1 加上排他锁,那么整个加锁期间,只允许事务 T1 对 O1 进行读取和更新操作,其他任何事务都不能进行读或写。
: 如果事务 T1 对数据对象 O1 加上排他锁,那么整个加锁期间,只允许事务 T1 对 O1 进行读取和更新操作,其他任何事务都不能进行读或写。


第19行: 第29行:


实现方式:利用 zookeeper 的'''同级节点的唯一性'''特性:
实现方式:利用 zookeeper 的'''同级节点的唯一性'''特性:
# 在需要获取排他锁时,所有的客户端试图通过调用 “create()” 方法,在 /exclusive_lock 节点下创建'''临时子节点''' /exclusive_lock/lock,最终只有一个客户端能创建成功,那么此客户端就获得了分布式锁。
# 获取锁:每个线程都试图在“/exclusive_lock”节点下创建'''临时节点'''/exclusive_lock/lock”,最终只有一个线程能创建成功,即获得了锁。
# 同时,所有没有获取到锁的客户端可以在 /exclusive_lock 节点上注册一个子节点变更的 '''watcher''' 监听事件,以便重新争取获得锁。
# 监听:所有没有获取到锁的线程在“/exclusive_lock”节点上注册一个子节点变更的 '''watcher''' 监听事件,以便重新争取获得锁。
 
* 竞争锁的线程不会进行排队,意味着此为“非公平锁”。
* 可能出现的问题:当锁被释放或者获得锁的线程宕机,所有监听“/exclusive_lock”子节点的线程都会作出反应,这样会给服务器带来巨大压力。(“羊群效应”)
 
=== 思路二:公平排他锁 ===
定义锁:
'''<syntaxhighlight lang="java" highlight="">
/exclusive_lock/[hostname]-序号
</syntaxhighlight>'''


== 共享锁 ==
'''共享锁'''(Shared Locks),又称'''读锁'''。
: 如果事务 T1 对数据对象 O1 加上了共享锁,那么当前事务只能对 O1 进行读取操作,其他事务也只能对这个数据对象加共享锁,直到该数据对象上的所有共享锁都释放。


实现方式:
# 获取锁:
## 每个线程都试图在“/exclusive_lock”节点下创建'''临时顺序节点''';
## 线程获取所有已创建的子节点列表,通过判断自己是否是序号最小的子节点来确定是否获得锁;
# 监听:所有没有获得锁的线程向比自己序号小的'''前一个节点'''注册 '''watcher''' 监听事件,以便重新争取获得锁。
* 竞争锁的线程按照创建的临时顺序节点的序号进行排队,意味着此为“公平锁”。
* 每次的所释放只会由下一个临时顺序节点作出反应,故不会出现“羊群效应”。


=== 思路三:公平读写锁 ===
定义锁:
定义锁:
'''<syntaxhighlight lang="java" highlight="">
'''<syntaxhighlight lang="java" highlight="">
第34行: 第59行:


实现方式:
实现方式:
# 客户端调用 “create” 方法创建类似定义锁方式的'''临时顺序节点'''
# 获取锁:
# 客户端调用 “getChildren” 接口来获取所有已创建的子节点列表。
## 每个线程都试图在“/exclusive_lock”节点下创建'''临时顺序节点'''
# 判断是否获得锁:
## 线程获取所有已创建的子节点列表,并判断获得锁:
#* 读请求:如果所有比自己小的子节点都是读请求或者没有比自己序号小的子节点,表明已经成功获取共享锁,同时开始执行度逻辑;
##* 读请求:如果所有比自己小的节点都是读请求,或者没有比自己序号小的节点,则可以获取读锁;
#* 写请求:如果自己不是序号最小的子节点,那么就进入等待。
##* 写请求:如果自己是序号最小的节点,则可以获取写锁。
# 如果没有获取到共享锁:
# 监听:
#* 读请求:向比自己序号小的'''最后一个写请求节点'''注册 '''watcher''' 监听;
#* 读请求:向比自己序号小的'''前一个写请求节点'''注册 '''watcher''' 监听;
#* 写请求:向比自己序号小的'''最后一个节点'''注册 '''watcher''' 监听。
#* 写请求:向比自己序号小的'''前一个节点'''注册 '''watcher''' 监听。
 
== Zookeeper分布式锁的实现 ==
以最经典的分布式锁:“可重入的公平锁”来实现。(思路二)
 
 


== curator 实现分布式锁 ==
== curator 提供的分布式锁 ==
实际开发过程中,可以 curator 工具包封装的API('''curator-recipes''')帮助我们实现分布式锁。
实际开发过程中,可以 curator 工具包封装的API('''curator-recipes''')帮助我们实现分布式锁。



2021年9月30日 (四) 23:15的版本


关于

在单体的应用开发场景中涉及并发同步的时候,大家往往采用 Synchronized(同步)或者其他同一个 JVM 内 Lock 机制来解决多线程间的同步问题。

在分布式集群工作的开发场景中,就需要一种更加高级的锁机制来处理跨机器的进程之间的数据同步问题。这种跨机器的锁就是“分布式锁”。

分布式锁是控制分布式系统之间同步访问共享资源的一种方式。

Zookeeper分布式锁思路

根据网络和书籍上的资料,主要有以下三种方式来实现分布式锁:

  1. 非公平排他锁:利用 Zookeeper 同一目录下 Znode 的唯一性;
  2. 公平排他锁:利用 Zookeeper 顺序节点的递增有序;
  3. 公平读写锁:(同上)

此外,以上还用到了:会话失效时临时节点自动删除的特性,以及节点的监听机制等。

  • 以上都可以有可重入的实现方式。

思路一:非公平排他锁

排他锁(Exclusive Locks),又被称为写锁独占锁

如果事务 T1 对数据对象 O1 加上排他锁,那么整个加锁期间,只允许事务 T1 对 O1 进行读取和更新操作,其他任何事务都不能进行读或写。


定义锁:

/exclusive_lock/lock


实现方式:利用 zookeeper 的同级节点的唯一性特性:

  1. 获取锁:每个线程都试图在“/exclusive_lock”节点下创建临时节点“/exclusive_lock/lock”,最终只有一个线程能创建成功,即获得了锁。
  2. 监听:所有没有获取到锁的线程在“/exclusive_lock”节点上注册一个子节点变更的 watcher 监听事件,以便重新争取获得锁。
  • 竞争锁的线程不会进行排队,意味着此为“非公平锁”。
  • 可能出现的问题:当锁被释放或者获得锁的线程宕机,所有监听“/exclusive_lock”子节点的线程都会作出反应,这样会给服务器带来巨大压力。(“羊群效应”)

思路二:公平排他锁

定义锁:

/exclusive_lock/[hostname]-序号


实现方式:

  1. 获取锁:
    1. 每个线程都试图在“/exclusive_lock”节点下创建临时顺序节点
    2. 线程获取所有已创建的子节点列表,通过判断自己是否是序号最小的子节点来确定是否获得锁;
  2. 监听:所有没有获得锁的线程向比自己序号小的前一个节点注册 watcher 监听事件,以便重新争取获得锁。
  • 竞争锁的线程按照创建的临时顺序节点的序号进行排队,意味着此为“公平锁”。
  • 每次的所释放只会由下一个临时顺序节点作出反应,故不会出现“羊群效应”。

思路三:公平读写锁

定义锁:

/shared_lock/[hostname]-请求类型W/R-序号


实现方式:

  1. 获取锁:
    1. 每个线程都试图在“/exclusive_lock”节点下创建临时顺序节点
    2. 线程获取所有已创建的子节点列表,并判断获得锁:
      • 读请求:如果所有比自己小的节点都是读请求,或者没有比自己序号小的节点,则可以获取读锁;
      • 写请求:如果自己是序号最小的节点,则可以获取写锁。
  2. 监听:
    • 读请求:向比自己序号小的前一个写请求节点注册 watcher 监听;
    • 写请求:向比自己序号小的前一个节点注册 watcher 监听。

Zookeeper分布式锁的实现

以最经典的分布式锁:“可重入的公平锁”来实现。(思路二)


curator 提供的分布式锁

实际开发过程中,可以 curator 工具包封装的API(curator-recipes)帮助我们实现分布式锁。


<dependency>
  <groupId>org.apache.curator</groupId>
  <artifactId>curator-recipes</artifactId>
  <version>x.x.x</version>
</dependency>

curator 的几种锁方案

curator 的几种锁方案:

  1. InterProcessMutex:分布式可重入排它锁
  2. InterProcessSemaphoreMutex:分布式排它锁
  3. InterProcessReadWriteLock:分布式读写锁

示例

下面例子模拟 50 个线程使用重入排它锁 InterProcessMutex 同时争抢锁:

public class InterprocessLock {
    private static CuratorFramework getZkClient() {
        String zkServerAddress = "192.168.3.39:2181";
        ExponentialBackoffRetry retryPolicy = new ExponentialBackoffRetry(1000, 3, 5000);
        CuratorFramework zkClient = CuratorFrameworkFactory.builder()
                .connectString(zkServerAddress)
                .sessionTimeoutMs(5000)
                .connectionTimeoutMs(5000)
                .retryPolicy(retryPolicy)
                .build();
        zkClient.start();
        return zkClient;
    }

    public static void main(String[] args)  {
        CuratorFramework zkClient = getZkClient();
        String lockPath = "/lock";
        InterProcessMutex lock = new InterProcessMutex(zkClient, lockPath);
        
        //模拟50个线程抢锁
        for (int i = 0; i < 50; i++) {
            new Thread(new TestThread(i, lock)).start();
        }
    }

    static class TestThread implements Runnable {
        private Integer threadFlag;
        private InterProcessMutex lock;

        public TestThread(Integer threadFlag, InterProcessMutex lock) {
            this.threadFlag = threadFlag;
            this.lock = lock;
        }

        @Override
        public void run() {
            try {
                lock.acquire();
                System.out.println("第"+threadFlag+"线程获取到了锁");
                //等到1秒后释放锁
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                try {
                    lock.release();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
控制台每间隔一秒钟输出一条记录:
Zookeeper:分布式锁:curator:InterProcessMutex示例.png