写点什么

Spring Boot Redis 实现分布式锁,真香,kalilinux 入侵教程

作者:Java高工P7
  • 2021 年 11 月 10 日
  • 本文字数:1621 字

    阅读完需:约 5 分钟

return?new?RedisLockRegistry(redisConnectionFactory,?"lock");


}


使用示例


@GetMapping("/redis/lock")


public?String?lock(@RequestParam("key")?String?key)?{


for?(int?i?=?0;?i?<?10;?i++)?{


new?Thread(()?->?{


redisLockService.lock(key);????????????try?{


Thread.sleep(3000L);


}?catch?(InterruptedException?e)?{


e.printStackTrace();????????????}????????????System.out.println(DateFormatUtils.format(new?Date(),?"yyyy-MM-dd?HH:mm:ss"));


redisLockService.unlock(key);????????}????????).start();????}????return?"OK";


}


RedisLockService 是我封装了的一个 Redis 锁服务,代码有点多,这里就不贴了,完整的代码示例在 Github 上,大家可以 Star 一下:


https://github.com/javastacks/spring-boot-best-practice


测试:


http://localhost:8080/redis/lock?key=yeah


输出:


2020-06-23?11:15:34


2020-06-23?11:15:37


2020-06-23?11:15:40


2020-06-23?11:15:43


2020-06-23?11:15:46


2020-06-23?11:15:49


2020-06-23?11:15:52


2020-06-23?11:15:55


2020-06-23?11:15:58


2020-06-23?11:16:01


可以看到每个线程需要等上一个线程休眠 3 秒后才能获取到锁。


源码分析




集成完了,会使用了,还得研究下 RedisLockRegistry 的源码,不然遇到什么坑还得再踩一遍。


RedisLockRegistry 有两个类构造器:



  • connectionFactory:Redis 连接工厂

  • registryKey:锁前缀

  • expireAfter:失效时间(非必须项,默认 60 秒)


所以,我们要注册 expireAfter 这个选项,默认 60 秒是否满足业务需要,如果超过默认的 60 少时间,否则将导致锁失效。


还有两个和 RedisLockRegistry 相关且很重要的成员变量:


private?final?String?clientId?=?UUID.randomUUID().toString();


private?final?Map<String,?RedisLock>?locks?=?new?ConcurrentHashMap<>();


  • clientId


首先用来标识当前 RedisLockRegistry 实例 ID,并且在设置、移除锁的时候都会要用到,用来判断是不是当前的锁注册实例。


  • locks


用来在内存中缓存当前锁注册实例所有锁对象。


获取锁对象


如下面获取锁对象源码所示:



每个 key 在内存中(ConcurrentHashMap)都对应一个锁对象,锁对象有生成过就直接返回,没有就生成再返回,有了这个锁对象才能进行上锁和解锁操作。


这个锁对象(RedisLock)其实也是实现了 Java 中的 java.util.concurrent.locks.Lock 锁接口:



锁对象(RedisLock)也有两个很重要的成员变量:


private?final?ReentrantLock?localLock?=?new?ReentrantLock();


private?volatile?long?lockedAt;


  • localLock


localLock 是一个本地内存可重入锁,每次去 Redis 上锁前,都要用本地 localLock 上锁先,这样能做到尽可能的少往 Redis 上锁,也能从一方面提升锁的性能。


  • lockedAt


lockedAt 上锁时间,移除过时锁会用到。


阻塞上锁


RedisLock#lock():


![](https://img-blog.csdnimg.cn/20200911202354302.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80ODg5MDA3NA==,size_16,color_FF


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


FFFF,t_70)


每隔 100 毫秒尝试获取一次锁,直到获取锁成功为止,不接受打断异常,遇到其他异常会释放本地锁返回异常并结束。


主要看下设置 Redis 锁的 Lua 脚本:



根据 key 查询其对应的值:clientId,如果和当前 clientId 一致则延长失效时间,如果 clientId 不存在就直接上锁,以上都不成立返回 false。


这样做的好处是,可以将整个 Redis Lua 脚本作为一个原子执行,而不用考虑并发及事务影响。


好了,核心的源码分析完了,其实也很简单,大家还不懂的或者有兴趣的可以再研究下。本文完整示例源代码和上篇 Spring Boot 快速集成 Redis 的示例代码一起上传到了 Github,欢迎大家 Star 关注学习。


https://github.com/javastacks/spring-boot-best-practice 所以,你还在手写分布式锁吗?赶快 Get 起来吧!

用户头像

Java高工P7

关注

还未添加个人签名 2021.11.08 加入

还未添加个人简介

评论

发布
暂无评论
Spring Boot Redis 实现分布式锁,真香,kalilinux入侵教程