当前位置:   article > 正文

手撸Redis分布式锁(8个版本的渐进式源码实践解读)_手撸分布式锁

手撸分布式锁


前言

分布式锁相对应的是本地锁,像我们熟悉的synchronized和ReentrantLock都是本地锁,本地锁是作用于JVM内部,单个进程内的操作共享资源互斥。而现在主流都是分布式和微服务架构,会部署多个服务(多个JVM),为此分布式锁也就应运而生了。
分布式锁主流实现有3种:基于Redis、Zookeeper或Mysql等数据库。
Redis实现分布式锁使用得非常广泛,也是面试的重要考点之一,很多同学都知道这个知识,也大致知道分布式锁的原理,但是具体到细节的掌握上,往往并不完全正确。所以下面就让我们手写Redis分布式锁,以版本迭代的方式,渐进式的解读遇到的问题和对应的解决方案,帮你彻底理解Reids分布式锁。
在这里插入图片描述


一、v1 初出茅庐

setnx (SET if Not eXists) 命令在指定的 key 不存在时,为 key 设置指定的值。

两个客户端进程可以执行这个命令,达到互斥,就可以实现一个分布式锁。

基本语法:setnx key value 例如两个进程同时加锁,只能成功1个:
在这里插入图片描述
在这里插入图片描述
释放锁,直接使用 DEL 命令删除这个 key 即可:
在这里插入图片描述
基于这个思路,使用RedisTemplate我写下了v1版本的实现代码:
在这里插入图片描述

模拟减库存调用如下:

在这里插入图片描述


二、v2 小心死锁

1. 业务逻辑异常导致死锁

解决方案:当lock成功后,对执行的业务逻辑加try finally,保证即使业务逻辑异常,也可以unlock。
在这里插入图片描述

2. 服务宕机导致死锁

在执行业务逻辑时,虽然我们加了try finally,但假设获得锁的服务宕机,还没有来的及解锁,那么这个锁将一直被占有,其它客户端也将永远拿不到这个锁了。
解决方案设置过期时间,服务宕机的话key也会在指定时间内自动过期,不会永远占有锁。

我们修改lock方法,对key加上expire 10s:

public boolean lock() {
    // 等价于原生命令 setnx key 1
    Boolean ok = stringRedisTemplate.opsForValue().setIfAbsent(key, "1");
    boolean res = Boolean.TRUE.equals(ok);
    if (res) {
        // 等价于原生命令 expire key 10
        stringRedisTemplate.expire(key, 10, TimeUnit.SECONDS);
    }
    return res;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

三、v3 彻底搞定死锁

v2的思路是对的,但是这里还有个小问题,如果setnx成功但expire失败呢? 依然会有死锁的可能,这个问题的根源在于setnx和expire是两条指令而不是原子指令,所以解决原子性问题我们可以采用lua脚本,详见我写的 Redis使用Lua脚本:保证原子性【项目案例分享】
另外,在Redis2.8版本中,作者加入了set指令的扩展参数ex nx,使得setnx和expire指令可以一起执行:

基本语法: set key value ex secords nx 例如:
在这里插入图片描述
我们修改lock方法如下:

public boolean lock() {
    // 等价于原生命令 set key 1 ex 10 nx
    Boolean ok = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
    return Boolean.TRUE.equals(ok);
}
  • 1
  • 2
  • 3
  • 4
  • 5

这样就彻底解决了死锁问题

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/751911
推荐阅读
相关标签