码农在囧途

朋友,如果喜欢,就去表白吧,不要因为害羞,更不要因为自卑,如果现在你都还不敢表白,那么多年后,再回头来看的时候,你可能会为曾经的胆小而后悔,也可能会为错过一个人而心中久久不能释怀,所以,大胆一点,即使失败也无所谓,至少我们曾经做过,做过了就无怨无悔,在人生这条道路上,时光稍纵即逝,我们应该把握好眼前的一切,爱是一种力量,更是一种内心的慰藉,冲吧!不要因为钱不够,不要因为容貌不出中国,更不要因为身世不显赫,你只要足够勇敢,这一切都是附加品!

得不到你的心,就用“分布式锁”锁住你的人-LMLPHP

前言

今天来分享一下zookeeper分布式锁,分布式锁是为了解决在分布式环境下数据的一致性,在单体系统中,我们可以直接使用Java自带的锁来进行并发控制,比如synchronizedLock等,但是在分布式系统中,因为服务部署在多机或者多容器里面,所以不在一个JVM中,就不能使用Java自带的锁机制,所以就必须得使用分布式锁,实现分布式锁的方式有几种,如Redis可以实现,今天我们主要说Zookeeper实现分布式的原理。

在说zookeeper分布式锁之前,我们先来说一下zk的节点类型,Zookeeper数据结构就像树,由节点构成,节点叫做Znode,Znode分为四种类型。

1.持久化节点(PERSISTENT)

默认的节点类型,客户端与zk断开连接后,节点依然存在

2.持久化顺序节点(PERSISTENT——SEQUENTIAL)

在创建节点时zk根据创建的时间顺序对节点进行编号

得不到你的心,就用“分布式锁”锁住你的人-LMLPHP

3.临时节点(EPHEMERAL)

当创建节点的客户端与zk断开连接后,临时节点会被删除

得不到你的心,就用“分布式锁”锁住你的人-LMLPHP

断开连接

得不到你的心,就用“分布式锁”锁住你的人-LMLPHP

得不到你的心,就用“分布式锁”锁住你的人-LMLPHP

临时顺序节点

临时顺序节点结合和临时节点和顺序节点的特点:在创建节点时,Zookeeper 根据创建的时间顺序给该节点名称进行编号,当创建节点的客户端与 Zookeeper 断开连接后,临时节点会被删除。

Zookeeper分布式锁原理

zookeeper分布式锁运用了临时顺序节点的特点

获取锁

1.在 Zookeeper 当中创建一个节点 ParentLock,这个节点可以设置为临时节点,也可设置成持久节点,Curator内置的分布式锁使用的临时节点,当第一个客户端想要获得锁时,需要在 ParentLock 这个节点下面创建一个临时顺序节点 LockA。

得不到你的心,就用“分布式锁”锁住你的人-LMLPHP

2.ClientA 查找 ParentLock 下面所有的临时顺序节点并排序,判断自己所创建的节点 LockA 是不是顺序最靠前的一个。如果是第一个节点,则成功获得锁。

得不到你的心,就用“分布式锁”锁住你的人-LMLPHP

3.这时候,如果再有一个客户端 ClientB 前来获取锁,则在 ParentLock 下载再创建一个临时顺序节点 LockB。

得不到你的心,就用“分布式锁”锁住你的人-LMLPHP

4.ClientB 查找 ParentLock 下面所有的临时顺序节点并排序,判断自己所创建的节点 LockB 是不是顺序最靠前的一个,结果不是,ClientB 向排序仅比它靠前的节点 LockA 注册 Watcher,用于监听 Lock1A节点是否存在。这意味着 Client2B抢锁失败,进入了等待状态。

得不到你的心,就用“分布式锁”锁住你的人-LMLPHP

5.这时候,如果又有一个客户端 ClientC前来获取锁,则在 ParentLock 下载再创建一个临时顺序节点 LockC。

得不到你的心,就用“分布式锁”锁住你的人-LMLPHP

6.ClientC 查找 ParentLock 下面所有的临时顺序节点并排序,判断自己所创建的节点 LockC 是不是顺序最靠前的一个,结果同样发现节点 LockC并不是最小的。于是,ClientC向排序仅比它靠前的节点 LockB注册 Watcher,用于监听 LockB节点是否存在。这意味着 ClientC同样抢锁失败,进入了等待状态。

得不到你的心,就用“分布式锁”锁住你的人-LMLPHP

这样一来,ClientA 得到了锁,ClientB监听了 LockA,ClientC监听了 LockB,形成了一个等待队列,

释放锁

任务完成,客户端显示释放

当任务完成时,ClientA 会显示调用删除节点 LockA 的指令。

得不到你的心,就用“分布式锁”锁住你的人-LMLPHP

任务执行过程中,客户端崩溃

获得锁的 ClientA 在任务执行过程中,如果崩溃,则会断开与 Zookeeper 服务端的链接。根据临时节点的特性,相关联的节点 LockA 会随之自动删除。

得不到你的心,就用“分布式锁”锁住你的人-LMLPHP

由于 ClientB 一直监听着 LockA的存在状态,当 LockA节点被删除,ClientB会立刻收到通知。这时候 ClientB 会再次查询 ParentLock 下面的所有节点,确认自己创建的节点 LockB 是不是目前最小的节点。如果是最小,则 ClientB 顺理成章获得了锁。

得不到你的心,就用“分布式锁”锁住你的人-LMLPHP

Curator使用分布式锁

Curator是Zookeeper的Java客户端库,使用Curator能够更加方便轻松的使用Zookeeper,Curator内置了分布式锁供我们使用,下面我们使用代码演示一下Curator的分布式锁。

配置CuratorFramework

CuratorFramework配置zookeeper连接,操作zookeeper服务需要使用CuratorFramework。

@Bean("curatorFramework")
public CuratorFramework curatorFramework() {
    CuratorFramework curatorFramework = CuratorFrameworkFactory.builder()
            .connectString("127.0.0.1:2181")
            .sessionTimeoutMs(10000)
            .connectionTimeoutMs(10000)
            .retryPolicy(new BoundedExponentialBackoffRetry(10000,30000, 3))
            .build();
    curatorFramework.start();
    return curatorFramework;
}

测试Controller

@RestController
@AllArgsConstructor
public class LockController {

    final CuratorFramework curatorFramework;

    @GetMapping("/testLock")
    public void testLock() throws Exception {
        InterProcessLock lock = new InterProcessMutex(curatorFramework, "/lock/order");
        try {
            //加锁
            boolean acquire = lock.acquire(20, TimeUnit.SECONDS);
            if (acquire){
                System.out.println("---------------获取锁成功--------------- ");
            }else {
                System.out.println("---------------获取锁失败--------------- ");
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //释放锁
            lock.release();
        }
    }
}

并发访问LockController的testLock接口,查看排队情况。

从Zookeeper可是化工具中可以看出大量请求进行了排队,由此可以看出分布式锁使用成功。

得不到你的心,就用“分布式锁”锁住你的人-LMLPHP

SpringBoot Zookeeper starter

如果直接使用代码的方式使用分布式锁,那么可能比较麻烦,那么我们可以使用注解的方式来封装一个starter,在SpringBoot项目中直接使用,之前基于Curator自定义了一个分布式锁的SpringBoot starter , 直接引入即可,如下使用注解@U2Lock便可。

@GetMapping("/get")
@U2Lock(lockName = "order-get",lockType = LockType.MUTEX_LOCK,requireTime = 5000)
public Map<String,Object> lock(){
    Map<String,Object> map = new HashMap<>();
    if (count > 0){
        count--;
        map.put("order_num",count);
        return map;
    }
    map.put("msg","商品已售罄");
    return map;
}

U2Lock锁注解详情

注解可以设置锁名称,所类型,默认为可重入排他锁InterProcessMutex,还有过期时间,代表在指定的时间内没有获取到锁,那么锁就过期了,将会自动删除锁。

/**
 * 锁注解
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Inherited
public @interface U2Lock {
    /**
     * 锁名字
     */
    String lockName() default "";
    /**
     * 锁类型-默认为可重入排他锁
     * @return
     */
    LockType lockType() default LockType.MUTEX_LOCK;
    /**
     * 过期时间
     */
    long requireTime() default 30000;
    /**
     * 单位
     */
    TimeUnit unit() default TimeUnit.MILLISECONDS;
}

U2Lock地址:

08-05 06:51