得不到你的心,就用“分布式锁”锁住你的人 码农在囧途

本文涉及的产品
云原生网关 MSE Higress,422元/月
注册配置 MSE Nacos/ZooKeeper,118元/月
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
简介: 朋友,如果喜欢,就去表白吧,不要因为害羞,更不要因为自卑,如果现在你都还不敢表白,那么多年后,再回头来看的时候,你可能会为曾经的胆小而后悔,也可能会为错过一个人而心中久久不能释怀,所以,大胆一点,即使失败也无所谓,至少我们曾经做过,做过了就无怨无悔,在人生这条道路上,时光稍纵即逝,我们应该把握好眼前的一切,爱是一种力量,更是一种内心的慰藉,冲吧!不要因为钱不够,不要因为容貌不出中国,更不要因为身世不显赫,你只要足够勇敢,这一切都是附加品!

码农在囧途


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


前言


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


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


1.持久化节点(PERSISTENT)


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


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


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



3.临时节点(EPHEMERAL)


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



断开连接




临时顺序节点


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


Zookeeper分布式锁原理


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


获取锁


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



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



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



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



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



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



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


释放锁


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


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



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


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



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



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可是化工具中可以看出大量请求进行了排队,由此可以看出分布式锁使用成功。



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地址:https://gitee.com/steakliu/u2-lock/tree/master/zookeeper-distribute-lock-starter


关于zookeeper分布式锁的介绍和实战,就说到这里,感谢你的观看,我们下期见!



相关实践学习
基于MSE实现微服务的全链路灰度
通过本场景的实验操作,您将了解并实现在线业务的微服务全链路灰度能力。
目录
相关文章
|
2月前
|
NoSQL Java Redis
太惨痛: Redis 分布式锁 5个大坑,又大又深, 如何才能 避开 ?
Redis分布式锁在高并发场景下是重要的技术手段,但其实现过程中常遇到五大深坑:**原子性问题**、**连接耗尽问题**、**锁过期问题**、**锁失效问题**以及**锁分段问题**。这些问题不仅影响系统的稳定性和性能,还可能导致数据不一致。尼恩在实际项目中总结了这些坑,并提供了详细的解决方案,包括使用Lua脚本保证原子性、设置合理的锁过期时间和使用看门狗机制、以及通过锁分段提升性能。这些经验和技巧对面试和实际开发都有很大帮助,值得深入学习和实践。
太惨痛: Redis 分布式锁 5个大坑,又大又深, 如何才能 避开 ?
|
4月前
|
NoSQL Redis
基于Redis的高可用分布式锁——RedLock
这篇文章介绍了基于Redis的高可用分布式锁RedLock的概念、工作流程、获取和释放锁的方法,以及RedLock相比单机锁在高可用性上的优势,同时指出了其在某些特殊场景下的不足,并提到了ZooKeeper作为另一种实现分布式锁的方案。
130 2
基于Redis的高可用分布式锁——RedLock
|
4月前
|
缓存 NoSQL Java
SpringBoot整合Redis、以及缓存穿透、缓存雪崩、缓存击穿的理解分布式情况下如何添加分布式锁 【续篇】
这篇文章是关于如何在SpringBoot应用中整合Redis并处理分布式场景下的缓存问题,包括缓存穿透、缓存雪崩和缓存击穿。文章详细讨论了在分布式情况下如何添加分布式锁来解决缓存击穿问题,提供了加锁和解锁的实现过程,并展示了使用JMeter进行压力测试来验证锁机制有效性的方法。
SpringBoot整合Redis、以及缓存穿透、缓存雪崩、缓存击穿的理解分布式情况下如何添加分布式锁 【续篇】
|
16天前
|
存储 NoSQL Java
使用lock4j-redis-template-spring-boot-starter实现redis分布式锁
通过使用 `lock4j-redis-template-spring-boot-starter`,我们可以轻松实现 Redis 分布式锁,从而解决分布式系统中多个实例并发访问共享资源的问题。合理配置和使用分布式锁,可以有效提高系统的稳定性和数据的一致性。希望本文对你在实际项目中使用 Redis 分布式锁有所帮助。
47 5
|
20天前
|
NoSQL Java 数据处理
基于Redis海量数据场景分布式ID架构实践
【11月更文挑战第30天】在现代分布式系统中,生成全局唯一的ID是一个常见且重要的需求。在微服务架构中,各个服务可能需要生成唯一标识符,如用户ID、订单ID等。传统的自增ID已经无法满足在集群环境下保持唯一性的要求,而分布式ID解决方案能够确保即使在多个实例间也能生成全局唯一的标识符。本文将深入探讨如何利用Redis实现分布式ID生成,并通过Java语言展示多个示例,同时分析每个实践方案的优缺点。
38 8
|
1月前
|
NoSQL Redis
Redis分布式锁如何实现 ?
Redis分布式锁通过SETNX指令实现,确保仅在键不存在时设置值。此机制用于控制多个线程对共享资源的访问,避免并发冲突。然而,实际应用中需解决死锁、锁超时、归一化、可重入及阻塞等问题,以确保系统的稳定性和可靠性。解决方案包括设置锁超时、引入Watch Dog机制、使用ThreadLocal绑定加解锁操作、实现计数器支持可重入锁以及采用自旋锁思想处理阻塞请求。
57 16
|
29天前
|
缓存 NoSQL PHP
Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出
本文深入探讨了Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出。文章还介绍了Redis在页面缓存、数据缓存和会话缓存等应用场景中的使用,并强调了缓存数据一致性、过期时间设置、容量控制和安全问题的重要性。
39 5
|
2月前
|
缓存 NoSQL Java
大数据-50 Redis 分布式锁 乐观锁 Watch SETNX Lua Redisson分布式锁 Java实现分布式锁
大数据-50 Redis 分布式锁 乐观锁 Watch SETNX Lua Redisson分布式锁 Java实现分布式锁
69 3
大数据-50 Redis 分布式锁 乐观锁 Watch SETNX Lua Redisson分布式锁 Java实现分布式锁
|
2月前
|
NoSQL Redis 数据库
计数器 分布式锁 redis实现
【10月更文挑战第5天】
51 1
|
2月前
|
NoSQL 算法 关系型数据库
Redis分布式锁
【10月更文挑战第1天】分布式锁用于在多进程环境中保护共享资源,防止并发冲突。通常借助外部系统如Redis或Zookeeper实现。通过`SETNX`命令加锁,并设置过期时间防止死锁。为避免误删他人锁,加锁时附带唯一标识,解锁前验证。面对锁提前过期的问题,可使用守护线程自动续期。在Redis集群中,需考虑主从同步延迟导致的锁丢失问题,Redlock算法可提高锁的可靠性。
83 4