关于后端:在代码世界游走没几把锁防身可不行-京东云技术团队

31次阅读

共计 15222 个字符,预计需要花费 39 分钟才能阅读完成。

一、开篇背景

“锁”代表平安。在程序中(这里指 java)尤其多线程环境下,有了锁的帮忙,会给数据安全带来保障,帮忙线程更好的运作,防止竞争和互斥。

锁共有 15 种算法:乐观锁、乐观锁、自旋锁、重入锁、读写锁、偏心锁、非偏心锁、共享锁、独占锁、重量级锁、轻量级锁、偏差锁、分段锁、互斥锁、同步锁 …. 一口气输入真的累,谁记这个啊。咱们要吃现成的。ok,下面的一大堆在咱 java 里就是:

ReentrantLock,Synchronieed,ReentrantReadWriteLock,Atomic 全家桶,Concurrent 全家桶

已上在并发场景中都是被经常用到,想必大家都已炉火纯青般 ….. 巴特!咱们还有后浪同学们可能不相熟,那我在这里聊下锁的用法和应用场景。

ReentrantLock:

ReentrantLock 是一个互斥的可重入锁。互斥的意思就是排他,独占,只能一个线程获取到锁。可重入的意思就是单个线程能够多次重复获取锁。它实现了乐观锁、重入锁、独占锁、非偏心锁和互斥锁的算法。

罕用办法

办法名 阐明 异样
lock() 始终阻塞获取锁,直到获取胜利
lockInterruptibly() 尝试获取锁,直到获取锁或者线程被中断 InterruptedException
tryLock() 尝试获取闲暇的锁,获取胜利返回 true,获取失败返回 false,不会阻塞,立刻返回
tryLock(long time, TimeUnit unit) 尝试在 time 工夫内获取闲暇的锁,在等待时间内能够被中断 InterruptedException
unLock() 开释锁
newCondition() 返回以后锁的一个 condition 实例,能够唤醒或期待线程

场景

递归嵌套的业务场景中,例如一棵树型的业务逻辑,办法有嵌套和调用,这时候我从外层加锁后,在递归遍历屡次,每次都要是同一把锁,并且递归到其余层级时锁还不能生效。这个时候就能够应用重入锁了。江湖上还有个花名,叫递归锁。

Synchronized

Synchronized 是乐观锁,默认是偏差锁,解锁失败后会降级为轻量级锁,当竞争持续加剧,进入 CAS 自旋 10 次后会降级为重量级锁。它实现了乐观锁、重入锁(用关键字润饰办法或代码段时)、独占锁,非偏心锁、轻量级锁、重量级锁、偏差锁和同步锁算法。

应用办法

应用场景 阐明
润饰办法 public synchronized void someMethod() { // 办法体}
润饰代码块 public void someMethod() { synchronized (lockObject) {// 临界区代码} }
润饰静态方法 public static synchronized void someStaticMethod() { // 办法体}
润饰类 public class MyClass {public void method1() {synchronized(MyClass.class) {// 同步代码块} } public synchronized void method2() { // 同步办法} }

场景

多个线程访问共享变量,为了保证期原子性就能够应用 synchronized。一个典型的业务场景是银行转账操作。假如有多个用户在同时进行转账操作,须要确保转账过程的原子性和数据的一致性,避免出现反复转账或者转账金额谬误的状况。在这种状况下,能够应用 synchronized 关键字来实现同步拜访。

ReentrantReadWriteLock

ReentrantReadWriteLock 是 Java 提供的读写锁,它反对多个线程同时读取共享数据,但只容许一个线程进行写操作。他实现了读写锁和共享锁算法。

罕用办法

办法 阐明
readLock() 获取读锁
writeLock() 获取写锁
readLock().lock() 获取读锁并加锁
writeLock().lock() 获取写锁并加锁
readLock().unlock() 开释读锁
writeLock().unlock() 开释写锁
newCondition() 创立与锁相关联的 Condition 实例,能够唤醒或期待线程

场景

读写锁利用在读多写少的状况下。读取时不波及数据批改,写入时须要互斥操作。当初根本所有的号称效率高的准实时数据库都有实现读写锁的算法。

Atomic 全家桶

Atomic 全家桶 咱们已 AtomicInteger 为例。他的特点是实现了 CAS 算法,同时解决了 ABA 问题保障原子性。还实现了自旋锁的 CLHLock 算法,用于 CAS 比拟失败后自旋期待。它实现了乐观锁、自旋锁和轻量级锁的算法。

罕用办法

办法 阐明
get(); 获取以后 AtomicInteger 对象的值
set(int newValue); 将 AtomicInteger 对象的值设置为指定的新值
getAndSet(int newValue) 将 AtomicInteger 对象的值设置为指定的新值,并返回设置前的旧值
incrementAndGet() 将 AtomicInteger 对象的值递增 1,并返回递增后的新值
decrementAndGet() 将 AtomicInteger 对象的值递加 1,并返回递加后的新值
getAndIncrement() 先获取 AtomicInteger 对象的以后值,而后将其递增 1,并返回获取的旧值
getAndDecrement() 先获取 AtomicInteger 对象的以后值,而后将其递加 1,并返回获取的旧值
addAndGet(int delta) 将指定的值加到 AtomicInteger 对象的以后值上,并返回相加后的后果
getAndAdd(int delta) 先获取 AtomicInteger 对象的以后值,而后将指定的值加到以后值上,并返回获取的旧值

场景

用来做计数器十分适合,再有就是线程通信,数据共享。

Concurrent 全家桶

Concurrent 全家桶咱们已 ConcurrentHashMap 为代表。它实现了分段锁算法(Segmented Locking)的策略,将整个数据结构划分成多个 Segments,每个段都领有独立的锁。

罕用办法

办法 阐明
clear(); 移除所有关系
containsKey(object value); 查看指定对象是都为表中的键
containsValue(object value); 如果此映射将一个或多个健映射到指定值,返回 true
elements() 返回此表值的枚举
entrySet() 返回此映射所蕴含的映射关系 Set 视图
get() 返回指定键映射到的值没如果此映射不蕴含该键的映射关系,则返回 null
isEmpty() 此映射不蕴含键值则返回 true
keys() 返回此表中键的枚举
put(K key, V value) 指定将健映射到此表中的指定值
putAll(Map<? extends k, ? extends v> m) 将指定映射中所有的映射关系复制到此映射中
size() 返回此映射中的键值映射关系数
remove(object key) 将键从此映射中移除
replace(K key, V value) 只有目前将键的条目映射到给定值时,才替换该键的条目

场景

在 java 中 ConcurrentHashMap,就是将数据分为 16 段,每一段都有独自的锁,并且处于不同锁段的数据互不烦扰,以此来晋升锁的性能。

比方在秒杀扣库存的场景中,当初的库存中有 2000 个商品,用户能够秒杀。为了防止出现超卖的状况,通常状况下,能够对库存加锁。如果有 1W 的用户竞争同一把锁,显然零碎吞吐量会非常低。为了晋升零碎性能,咱们能够将库存分段,比方:分为 100 段,这样每段就有 20 个商品能够参加秒杀。在秒杀的过程中,先把用户 id 获取 hash 值,而后除以 100 取模。模为 1 的用户拜访第 1 段库存,模为 2 的用户拜访第 2 段库存,模为 3 的用户拜访第 3 段库存,前面以此类推,到最初模为 100 的用户拜访第 100 段库存。如此一来,在多线程环境中,能够大大的缩小锁的抵触。

二、重点分布式场景 redisson 和 zk 的锁的介绍

Redisson

咱们日常开发中用用的最多的场景还是分布式锁。提到分布式锁就不可回避 Redisson。WHY?他就是权威好用。应用场景最多没有之一。Redisson 官网一共提供了 8 把锁。咱们逐个看一下。

1 可重入锁(Reentrant Lock)

基于 Redis 的 Redisson 分布式可重入锁 **RLock** Java 对象实现了java.util.concurrent.locks.Lock 接口。同时还提供了异步(Async)、反射式(Reactive)和 RxJava2 规范的接口。

   public static void main(String[] args) throws InterruptedException {
        // connects to 127.0.0.1:6379 by default
        RedissonClient redisson = Redisson.create();
        
        RLock lock = redisson.getLock("lock");
        lock.lock(2, TimeUnit.SECONDS);

        Thread t = new Thread() {public void run() {RLock lock1 = redisson.getLock("lock");
                lock1.lock();
                lock1.unlock();};
        };

        t.start();
        t.join();

        redisson.shutdown();}



大家都晓得,如果负责贮存这个分布式锁的 Redisson 节点宕机当前,而且这个锁正好处于锁住的状态时,这个锁会呈现锁死的状态。为了防止这种状况的产生,Redisson 外部提供了一个监控锁的看门狗,它的作用是在 Redisson 实例被敞开前,一直的缩短锁的有效期。默认状况下,看门狗的查看锁的超时工夫是 30 秒 钟,也能够通过批改 Config.lockWatchdogTimeout 来另行指定。

另外 Redisson 还通过加锁的办法提供了 leaseTime 的参数来指定加锁的工夫。超过这个工夫后锁便主动解开了。

// 加锁当前 10 秒钟主动解锁
// 无需调用 unlock 办法手动解锁
lock.lock(10, TimeUnit.SECONDS);

// 尝试加锁,最多期待 100 秒,上锁当前 10 秒主动解锁
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
if (res) {
   try {...} finally {lock.unlock();
   }
}



Redisson 同时还为分布式锁提供了异步执行的相干办法:

RLock lock = redisson.getLock("anyLock");
lock.lockAsync();
lock.lockAsync(10, TimeUnit.SECONDS);
Future<Boolean> res = lock.tryLockAsync(100, 10, TimeUnit.SECONDS);



RLock对象完全符合 Java 的 Lock 标准。也就是说只有领有锁的过程能力解锁,其余过程解锁则会抛出 IllegalMonitorStateException 谬误。然而如果遇到须要其余过程也能解锁的状况,请应用分布式信号量Semaphore 对象.

2. 偏心锁(Fair Lock)

基于 Redis 的 Redisson 分布式可重入偏心锁也是实现了 java.util.concurrent.locks.Lock 接口的一种 RLock 对象。同时还提供了异步(Async)、反射式(Reactive)和 RxJava2 规范的接口。它保障了当多个 Redisson 客户端线程同时申请加锁时,优先调配给先发出请求的线程。所有申请线程会在一个队列中排队,当某个线程呈现宕机时,Redisson 会期待 5 秒后持续下一个线程,也就是说如果后面有 5 个线程都处于期待状态,那么前面的线程会期待至多 25 秒。

public static void main(String[] args) throws InterruptedException {
        // connects to 127.0.0.1:6379 by default
        RedissonClient redisson = Redisson.create();
        
        RLock lock = redisson.getFairLock("test");

        int size = 10;
        List<Thread> threads = new ArrayList<Thread>();
        for (int i = 0; i < size; i++) {
            final int j = i;
            Thread t = new Thread() {public void run() {lock.lock();
                    lock.unlock();};
            };
            
            threads.add(t);
        }
        
        for (Thread thread : threads) {thread.start();
            thread.join(5);
        }
        
        for (Thread thread : threads) {thread.join();
        }
    }

同样也有看门狗机制来避免死锁。另外 Redisson 还通过加锁的办法提供了 leaseTime 的参数来指定加锁的工夫。超过这个工夫后锁便主动解开了。

// 10 秒钟当前主动解锁
// 无需调用 unlock 办法手动解锁
fairLock.lock(10, TimeUnit.SECONDS);

// 尝试加锁,最多期待 100 秒,上锁当前 10 秒主动解锁
boolean res = fairLock.tryLock(100, 10, TimeUnit.SECONDS);
...
fairLock.unlock();



Redisson 同时还为分布式可重入偏心锁提供了异步执行的相干办法:

RLock fairLock = redisson.getFairLock("anyLock");
fairLock.lockAsync();
fairLock.lockAsync(10, TimeUnit.SECONDS);
Future<Boolean> res = fairLock.tryLockAsync(100, 10, TimeUnit.SECONDS);



3. 联锁(MultiLock)

基于 Redis 的 Redisson 分布式联锁 **RedissonMultiLock** 对象能够将多个 RLock 对象关联为一个联锁,每个 RLock 对象实例能够来自于不同的 Redisson 实例。



 public static void main(String[] args) throws InterruptedException {
        // connects to 127.0.0.1:6379 by default
        RedissonClient client = Redisson.create();
        // 同时加锁:lock1 lock2 lock3 所有的锁都上锁胜利才算胜利。RLock lock1 = client.getLock("lock1");
        RLock lock2 = client.getLock("lock2");
        RLock lock3 = client.getLock("lock3");
        
        Thread t = new Thread() {public void run() {RedissonMultiLock lock = new RedissonMultiLock(lock1, lock2, lock3);
                lock.lock();
                
                try {Thread.sleep(3000);
                } catch (InterruptedException e) { }
                
                lock.unlock();};
        };
        t.start();
        t.join(1000);

        RedissonMultiLock lock = new RedissonMultiLock(lock1, lock2, lock3);
        lock.lock();
        lock.unlock();}

同样也有看门狗机制来避免死锁。另外 Redisson 还通过加锁的办法提供了 leaseTime 的参数来指定加锁的工夫。超过这个工夫后锁便主动解开了。

RedissonMultiLock lock = new RedissonMultiLock(lock1, lock2, lock3);
// 给 lock1,lock2,lock3 加锁,如果没有手动解开的话,10 秒钟后将会主动解开
lock.lock(10, TimeUnit.SECONDS);

// 为加锁期待 100 秒工夫,并在加锁胜利 10 秒钟后主动解开
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
...
lock.unlock();



4. 红锁(RedLock)

基于 Redis 的 Redisson 红锁 RedissonRedLock 对象实现了 Redlock 介绍的加锁算法。该对象也能够用来将多个 RLock 对象关联为一个红锁,每个 RLock 对象实例能够来自于不同的 Redisson 实例。

RLock lock1 = redissonInstance1.getLock("lock1");
RLock lock2 = redissonInstance2.getLock("lock2");
RLock lock3 = redissonInstance3.getLock("lock3");

RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);
// 同时加锁:lock1 lock2 lock3
// 红锁在大部分节点上加锁胜利就算胜利。lock.lock();
...
lock.unlock();。

 public static void main(String[] args) throws InterruptedException {
        // connects to 127.0.0.1:6379 by default
        RedissonClient client1 = Redisson.create();
        RedissonClient client2 = Redisson.create();
        
        RLock lock1 = client1.getLock("lock1");
        RLock lock2 = client1.getLock("lock2");
        RLock lock3 = client2.getLock("lock3");
        
        Thread t1 = new Thread() {public void run() {lock3.lock();
            };
        };
        t1.start();
        t1.join();
        
        Thread t = new Thread() {public void run() {RedissonMultiLock lock = new RedissonRedLock(lock1, lock2, lock3);
                lock.lock();
                
                try {Thread.sleep(3000);
                } catch (InterruptedException e) { }
                lock.unlock();};
        };
        t.start();
        t.join(1000);

        lock3.forceUnlock();
        
        RedissonMultiLock lock = new RedissonRedLock(lock1, lock2, lock3);
        // 给 lock1,lock2,lock3 加锁,如果没有手动解开的话,10 秒钟后将会主动解开
        lock.lock(10, TimeUnit.SECONDS);
        lock.unlock();

        client1.shutdown();
        client2.shutdown();}
    



5. 读写锁(ReadWriteLock)

基于 Redis 的 Redisson 分布式可重入读写锁 RReadWriteLock Java 对象实现了java.util.concurrent.locks.ReadWriteLock 接口。其中读锁和写锁都继承了 RLock 接口。

分布式可重入读写锁容许同时有多个读锁和一个写锁处于加锁状态。

RReadWriteLock rwlock = redisson.getReadWriteLock("anyRWLock");
// 最常见的应用办法
rwlock.readLock().lock();
// 或
rwlock.writeLock().lock();




  public static void main(String[] args) throws InterruptedException {
        // connects to 127.0.0.1:6379 by default
        RedissonClient redisson = Redisson.create();

        final RReadWriteLock lock = redisson.getReadWriteLock("lock");

        lock.writeLock().tryLock();

        Thread t = new Thread() {public void run() {RLock r = lock.readLock();
                 // 10 秒钟当前主动解锁,无需调用 unlock 办法手动解锁
                 //lock.readLock().lock(10, TimeUnit.SECONDS);
                 r.lock();

                 try {Thread.sleep(1000);
                } catch (InterruptedException e) {e.printStackTrace();
                }
                r.unlock();};
        };

        t.start();
        t.join();

        lock.writeLock().unlock();

        t.join();
        
        redisson.shutdown();}



6. 信号量(Semaphore)

基于 Redis 的 Redisson 的分布式信号量(Semaphore)Java 对象 RSemaphore 采纳了与 java.util.concurrent.Semaphore 类似的接口和用法。同时还提供了异步(Async)、反射式(Reactive)和 RxJava2 规范的接口。


 public static void main(String[] args) throws InterruptedException {
        // connects to 127.0.0.1:6379 by default
        RedissonClient redisson = Redisson.create();

        RSemaphore s = redisson.getSemaphore("test");
        s.trySetPermits(5);
        s.acquire(3);

        Thread t = new Thread() {
            @Override
            public void run() {RSemaphore s = redisson.getSemaphore("test");
                s.release();
                s.release();}
        };

        t.start();
        // 或
        s.acquire(4);
        
        redisson.shutdown();}



7. 可过期性信号量(PermitExpirableSemaphore)

基于 Redis 的 Redisson 可过期性信号量(PermitExpirableSemaphore)是在 RSemaphore 对象的根底上,为每个信号减少了一个过期工夫。每个信号能够通过独立的 ID 来辨识,开释时只能通过提交这个 ID 能力开释。它提供了异步(Async)、反射式(Reactive)和 RxJava2 规范的接口。


 public static void main(String[] args) throws InterruptedException {
        // connects to 127.0.0.1:6379 by default
        RedissonClient redisson = Redisson.create();

        RPermitExpirableSemaphore s = redisson.getPermitExpirableSemaphore("test");
        s.trySetPermits(1);
        // 获取一个信号,有效期只有 2 秒钟。String permitId = s.tryAcquire(100, 2, TimeUnit.SECONDS);

        Thread t = new Thread() {public void run() {RPermitExpirableSemaphore s = redisson.getPermitExpirableSemaphore("test");
                try {String permitId = s.acquire();
                    s.release(permitId);
                } catch (InterruptedException e) {e.printStackTrace();
                }
            };
        };

        t.start();
        t.join();
        
        s.tryRelease(permitId);
        
    }

8. 闭锁(CountDownLatch)

基于 Redisson 的 Redisson 分布式闭锁(CountDownLatch)Java 对象 RCountDownLatch 采纳了与 java.util.concurrent.CountDownLatch 类似的接口和用法。

 public static void main(String[] args) throws InterruptedException {
        // connects to 127.0.0.1:6379 by default
        RedissonClient redisson = Redisson.create();

        ExecutorService executor = Executors.newFixedThreadPool(2);

        final RCountDownLatch latch = redisson.getCountDownLatch("latch1");
        latch.trySetCount(1);
        // 在其余线程或其余 JVM 里
        executor.execute(new Runnable() {
            @Override
            public void run() {latch.countDown();
            }
            
        });

        executor.execute(new Runnable() {

            @Override
            public void run() {
                try {latch.await(550, TimeUnit.MILLISECONDS);
                } catch (InterruptedException e) {e.printStackTrace();
                }
            }
            
        });

        
        executor.shutdown();
        executor.awaitTermination(10, TimeUnit.SECONDS);

    }

Zookeeper

接着咱们再看 zookeeper 的锁。zk 不是为高可用性设计的,但它应用 **ZAB** 协定达到了极高的一致性。所以它常常被选作注册核心、配置核心、分布式锁等场景。它的性能是十分无限的,而且 API 并不是那么好用。xjjdog 偏向于应用基于 **Raft** 协定的 **Etcd** 或者**Consul**,它们更加轻量级一些。咱们看一下 zk 的加锁时序图。

Curator是 netflix 公司开源的一套 zookeeper 客户端,目前是 Apache 的顶级我的项目。与 Zookeeper 提供的原生客户端相比,Curator 的抽象层次更高,简化了 Zookeeper 客户端的开发量。Curator 解决了很多 zookeeper 客户端十分底层的细节开发工作,包含连贯重连、重复注册 wathcer 和 NodeExistsException 异样等。Curator 由一系列的模块形成,对于个别开发者而言,罕用的是 curator-framework 和 curator-recipes,咱们跳过他的其余能力,间接看分布式锁。

1.(可重入锁 Shared Reentrant Lock)

Shared 意味着锁是全局可见的,客户端都能够申请锁。Reentrant 和 JDK 的 ReentrantLock 相似,意味着同一个客户端在领有锁的同时,能够屡次获取,不会被阻塞。它是由类 InterProcessMutex 来实现。通过 acquire 取得锁,并提供超时机制。通过 release()办法开释锁。InterProcessMutex 实例能够重用。Revoking ZooKeeper recipes wiki 定义了可协商的撤销机制。为了撤销 mutex, 调用 makeRevocable 办法。咱们来看示例:

public class ExampleClientThatLocks {
    private final InterProcessMutex lock;
    private final FakeLimitedResource resource;
    private final String clientName;

    public ExampleClientThatLocks(CuratorFramework client, String lockPath, FakeLimitedResource resource, String clientName) {
        this.resource = resource;
        this.clientName = clientName;
        lock = new InterProcessMutex(client, lockPath);
    }

    public void doWork(long time, TimeUnit unit) throws Exception {if (!lock.acquire(time, unit)) {throw new IllegalStateException(clientName + "could not acquire the lock");
        }
        try {System.out.println(clientName + "has the lock");
            resource.use();} finally {System.out.println(clientName + "releasing the lock");
            // 开释锁
            lock.release();}
    }
}

2. 不可重入锁(Shared Lock)

应用 InterProcessSemaphoreMutex,调用办法相似,区别在于该锁是不可重入的,在同一个线程中不可重入。

3. 可重入读写锁(Shared Reentrant Read Write Lock)

相似 JDK 的 ReentrantReadWriteLock. 一个读写锁治理一对相干的锁。一个负责读操作,另外一个负责写操作。读操作在写锁没被应用时可同时由多个过程应用,而写锁应用时不容许读 (阻塞)。此锁是可重入的。一个领有写锁的线程可重入读锁,然而读锁却不能进入写锁。这也意味着写锁能够降级成读锁,比方申请写锁 —> 读锁 —-> 开释写锁。从读锁升级成写锁是不成的。次要由两个类实现:

InterProcessReadWriteLock
InterProcessLock

4. 信号量(Shared Semaphore)

一个计数的信号量相似 JDK 的 Semaphore。JDK 中 Semaphore 保护的一组许可(permits),而 Cubator 中称之为租约(Lease)。留神,所有的实例必须应用雷同的 numberOfLeases 值。调用 acquire 会返回一个租约对象。客户端必须在 finally 中 close 这些租约对象,否则这些租约会失落掉。然而,然而,如果客户端 session 因为某种原因比方 crash 丢掉,那么这些客户端持有的租约会主动 close,这样其它客户端能够持续应用这些租约。租约还能够通过上面的形式返还:

public void returnAll(Collection<Lease> leases)
public void returnLease(Lease lease)

留神一次你能够申请多个租约,如果 Semaphore 以后的租约不够,则申请线程会被阻塞。同时还提供了超时的重载办法:

public Lease acquire()
public Collection<Lease> acquire(int qty)
public Lease acquire(long time, TimeUnit unit)
public Collection<Lease> acquire(int qty, long time, TimeUnit unit)



次要类有:

InterProcessSemaphoreV2
Lease
SharedCountReader

5. 多锁对象(Multi Shared Lock)

Multi Shared Lock 是一个锁的容器。当调用 acquire,所有的锁都会被 acquire,如果申请失败,所有的锁都会被 release。同样调用 release 时所有的锁都被 release(失败被疏忽)。基本上,它就是组锁的代表,在它下面的申请开释操作都会传递给它蕴含的所有的锁。次要波及两个类:

InterProcessMultiLock
InterProcessLock

它的构造函数须要蕴含的锁的汇合,或者一组 ZooKeeper 的 path。

public InterProcessMultiLock(List<InterProcessLock> locks)
public InterProcessMultiLock(CuratorFramework client, List<String> paths)

6. 残缺锁示例

咱们再看一个官网残缺锁示例:

public class LockingExample {
    private static final int QTY = 5;
    private static final int REPETITIONS = QTY * 10;

    private static final String PATH = "/examples/locks";

    public static void main(String[] args) throws Exception {
        // all of the useful sample code is in ExampleClientThatLocks.java

        // FakeLimitedResource simulates some external resource that can only be access by one process at a time
        final FakeLimitedResource resource = new FakeLimitedResource();

        ExecutorService service = Executors.newFixedThreadPool(QTY);
        final TestingServer server = new TestingServer();
        try {for (int i = 0; i < QTY; ++i) {
                final int index = i;
                Callable<Void> task = new Callable<Void>() {
                    @Override
                    public Void call() throws Exception {
                        CuratorFramework client = CuratorFrameworkFactory.newClient(server.getConnectString(), new ExponentialBackoffRetry(1000, 3));
                        try {client.start();

                            ExampleClientThatLocks example =
                                    new ExampleClientThatLocks(client, PATH, resource, "Client" + index);
                            for (int j = 0; j < REPETITIONS; ++j) {example.doWork(10, TimeUnit.SECONDS);
                            }
                        } catch (InterruptedException e) {Thread.currentThread().interrupt();} catch (Exception e) {e.printStackTrace();
                            // log or do something
                        } finally {CloseableUtils.closeQuietly(client);
                        }
                        return null;
                    }
                };
                service.submit(task);
            }

            service.shutdown();
            service.awaitTermination(10, TimeUnit.MINUTES);
        } finally {CloseableUtils.closeQuietly(server);
        }
    }
}

三、总结

分布式环境中,咱们始终绕不开 CAP 实践,这也是 Redisson 锁和 ZK 锁的本质区别。CAP 指的是在一个分布式系统中:一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)。

这三个因素最多只能同时实现两点,不可能三者兼顾。如果你的理论业务场景,更须要的是保证数据一致性。那么请应用 CP 类型的分布式锁,比方:zookeeper,它是基于磁盘的,性能可能没那么好,但数据个别不会丢。

如果你的理论业务场景,更须要的是保证数据高可用性。那么请应用 AP 类型的分布式锁,比方:Redisson,它是基于内存的,性能比拟好,但有失落数据的危险。

其实,在咱们绝大多数分布式业务场景中,应用 Redisson 分布式锁就够了,真的别太较真。因为数据不统一问题,能够通过最终一致性计划解决。但如果零碎不可用了,对用户来说是暴击一万点挫伤。

四、思考思考

以上就是我对锁的总结和。分布式锁不是完满的,总会有问题;如:在 redis 中,lockName 和已有的 key 不能重名!unlock 的前提是 lock 胜利!必须要设计本人的兜底计划 ……

回顾整个锁界,兄弟们都把握了哪些锁呢?在分布式锁的场景中都遇到哪些疑难杂症?咱们 评论区 持续

五、备注

redisson:

官网实例

官网代码示例

curator:

curator 的 ZK 客户端示例

作者:京东保险 管顺利
起源:京东云开发者社区 转载请注明起源

正文完
 0