一文讲透redis实现分布式锁里面的坑

一.前提

相信大家在使用分布式锁的时候都会选择redis或者zookeeper来实现。今天我们来讲一讲使用Redis实现分布式锁里面的坑。大家要避免

二.错误案例

1.jedis.setnx方法和jedis.expire组合实现加锁

上代码:

java 复制代码
        Long result = jedis.setnx(lockKey, requestId);
        if (result == 1) {
            // 若在这里程序突然崩溃,则无法设置过期时间,将发生死锁
            jedis.expire(lockKey, expireTime);
        }

上述代码 如果在setnx之后程序突然崩溃,那废了,没有设置上过期时间,会产生死锁。为什么会出现问题呢,因为setnx()和expire()不具备原子性。

2.使用jedis.setnx()命令

其中key是锁,value是过期时间

java 复制代码
public static boolean wrongGetLock2(Jedis jedis, String lockKey, int expireTime) {

    long expires = System.currentTimeMillis() + expireTime;
    String expiresStr = String.valueOf(expires);

    // 如果当前锁不存在,返回加锁成功
    if (jedis.setnx(lockKey, expiresStr) == 1) {
        return true;
    }
    // 如果锁存在,获取锁的过期时间
    String currentValueStr = jedis.get(lockKey);
    if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) {
        // 锁已过期,获取上一个锁的过期时间,并设置现在锁的过期时间
        String oldValueStr = jedis.getSet(lockKey, expiresStr);
        if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {
            // 考虑多线程并发的情况,只有一个线程的设置值和当前值相同,它才有权利加锁
            return true;
        }
    }
    // 其他情况,一律返回加锁失败
    return false;}

想想会出现什么问题呢? 答案就是如果分布式系统中的的多个系统时间可能不一致,比如A机器比其他机器早了一分钟,过期时间设置为一分钟,那对于其他机器来说,当A上锁的时候,其他机器并不会被影响。第二个问题锁不具备拥有者标识,即任何客户端都可以解锁。正常我们应该保证分布式锁中谁加锁谁就要解锁,你家的锁头别人的钥匙要是能开岂不是很严重。

三.正确姿势

回想以上问题,我们其实就是要保证加锁与添加过期时间需要原子性。那我们应该怎么办呢?使用Lua脚本啊

java 复制代码
public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
        if (RELEASE_SUCCESS.equals(result)) {
            return true;
        }
        return false;
    }}

我们使用eval()命令来实现让redis执行lua脚本保证原子性。

四.解锁的错误示范

1.直接使用jedis.del()命令

还是上面说到的问题,这种不先判断锁的拥有者而直接解锁的方式,会导致任何客户端都可以随时进行解锁,即使这把锁不是它的。

2.程序突然崩溃

java 复制代码
public static void wrongReleaseLock2(Jedis jedis, String lockKey, String requestId) {

    // 判断加锁与解锁是不是同一个客户端
    if (requestId.equals(jedis.get(lockKey))) {
        // 若在此时,这把锁突然不是这个客户端的,则会误解锁
        jedis.del(lockKey);
    }}

如代码注释,问题在于如果调用jedis.del()方法的时候,这把锁已经不属于当前客户端的时候会解除他人加的锁。那么是否真的有这种场景?答案是肯定的,比如客户端A加锁,一段时间之后客户端A解锁,在执行jedis.del()之前,锁突然过期了,此时客户端B尝试加锁成功,然后客户端A再执行del()方法,则将客户端B的锁给解除了。

五.结尾

当然,我们可以使用专门的redis分布式锁组件redission.

相关推荐
老苏畅谈运维30 分钟前
PostgreSQL的dblink扩展模块使用方法
数据库·postgresql
sinat_262292111 小时前
Java面试实战:音视频场景下的微服务架构与缓存技术剖析
java·spring boot·redis·微服务·kafka·分布式系统·面试技巧
Fanche4041 小时前
MySQL 8 自动安装脚本(CentOS-7 系统)
linux·运维·数据库·mysql·centos
草海桐2 小时前
NoSQL 简单讲解
数据库·nosql
聪明的墨菲特i3 小时前
SQL进阶知识:四、索引优化
数据库·sql·mysql·database·索引·db2
Harbor Lau3 小时前
IDEA连接达梦数据库
数据库
jack_xu6 小时前
经典大厂面试题——缓存穿透、缓存击穿、缓存雪崩
java·redis·后端
我该如何取个名字7 小时前
Mac mini 安装mysql数据库以及出现的一些问题的解决方案
数据库·mysql·macos
悻运7 小时前
Spark论述及其作用
大数据·分布式·spark
曹弘毅8 小时前
doris/clickhouse常用sql
数据库·sql·clickhouse·doris