黄东旭解析 TiDB 的核心优势
1166
2023-05-22
Java 从零实现属于你的 Redis 分布式锁
redis分布式锁
为什么需要分布式锁
在 jdk 中为我们提供了加锁的方式:
(1)synchronized 关键字
(2)volatile + CAS 实现的乐观锁
(3)ReadWriteLock 读写锁
(4)ReenTrantLock 可重入锁
等等,这些锁为我们变成提供极大的便利性,保证在多线程的情况下,保证线程安全。
但是在分布式系统中,上面的锁就统统没用了。
我们想要解决分布式系统中的并发问题,就需要引入分布式锁的概念。
java 代码实现
创作动机
首先是对锁实现原理的一个实现,理论指导实践,实践完善理论。
晚上关于 redis 分布式锁的文章一大堆,但是也都稂莠不齐。
redis 分布式锁工具有时候中间件团队不见得会提供,提供了也不见得经常维护,不如自己实现一个,知道原理,也方便修改。
接口定义
为了便于和 JDK 复用,我们让接口继承自 jdk 的 Lock 接口。
方法我们只添加了三个比较常用的核心方法,作为第一个版本,简单点。
后续陆续添加即可。
抽象实现
为了便于后期添加更多的所实现,这里首先实现了一个公用的抽象父类。
最核心的实际上是 public boolean tryLock(long time, TimeUnit unit, String key) throws InterruptedException 方法。
这个方法会调用 this.tryLock(key) 获取锁,如果成功,直接返回;如果不成功,则循环等待。
这里设置了超时时间,如果超时,则直接返回 true。
redis 锁实现
我们实现的 redis 分布锁,继承自上面的抽象类。
这里就是 redis 锁的核心实现了,如果不太理解,建议回顾一下原理篇:
redis 分布式锁原理详解
加锁
加锁部分,这里会生成一个 id 标识,用于区分当前操作者。
为了安全也设置了默认的超时时间。
当然这里是为了简化调用者的使用成本,开发在使用的时候只需要关心自己要加锁的 key 即可。
当然,甚至连加锁的 key 都可以进一步抽象掉,比如封装 @DistributedLock 放在方法上,即可实现分布式锁。这个后续有时间可以拓展,原理也不难。
解锁
解锁的时候,就会获取当前进程的持有标识。
凭借当前线程持有的 id 标识,去解锁。
IOperator
我们对 redis 的操作进行了抽象,为什么抽象呢?
因为 redis 服务种类实际很多,可以是 redis 单点,集群,主从,哨兵。
连接的客户端也可以很多,jedis,spring redisTemplate, codis, redisson 等等。
这里为了后期拓展方便,就对操作进行了抽象。
接口
定义接口如下:
jedis 实现
我们实现一个 jedis 单点版本的:
这里时最核心的部分。
别看简单几行代码,需要注意的点还是很多的。
加锁
加锁时附带 requestId,用来标识自己为锁的持有者。
SETNX 当 key 不存在时才进行加锁。
设置加锁的过期时间,避免因异常等原因未释放锁,导致锁的长时间占用。
解锁
使用 lua 脚本,保证操作的原子性。
为了证明为锁的持有者,传入 requestId。
测试验证
maven 引入
测试代码
Jedis jedis = new Jedis("127.0.0.1", 6379); IOperator operator = new JedisOperator(jedis); // 获取锁 ILock lock = LockRedisBs.newInstance().operator(operator).lock(); try { boolean lockResult = lock.tryLock(); System.out.println(lockResult); // 业务处理 } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); }
小结
到这里,一个简单版本的 redis 分布式锁就实现完成了。
当然还有很多可以改进的地方:
(1)比如引入递增的 sequence,避免分布式锁中的 GC 导致的问题
(2)对于更多 redis 服务端+客户端的支持
(3)对于注解式 redis 分布式锁的支持
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。