一文讲透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.

相关推荐
Arbori_262156 分钟前
获取oracle表大小
数据库·oracle
王强你强13 分钟前
MySQL 高级查询:JOIN、子查询、窗口函数
数据库·mysql
草巾冒小子14 分钟前
brew 安装mysql,启动,停止,重启
数据库·mysql
用户62799471826221 分钟前
南大通用GBase 8c分布式版本gha_ctl 命令-HI参数详解
数据库
斯汤雷29 分钟前
Matlab绘图案例,设置图片大小,坐标轴比例为黄金比
数据库·人工智能·算法·matlab·信息可视化
SQLplusDB36 分钟前
Oracle 23ai Vector Search 系列之3 集成嵌入生成模型(Embedding Model)到数据库示例,以及常见错误
数据库·oracle·embedding
喝醉酒的小白1 小时前
SQL Server 可用性组自动种子设定失败问题
数据库
chem41111 小时前
Conmon lisp Demo
服务器·数据库·lisp
爱的叹息1 小时前
Java 连接 Redis 的驱动(Jedis、Lettuce、Redisson、Spring Data Redis)分类及对比
java·redis·spring
m0_555762901 小时前
QT 动态布局实现(待完善)
服务器·数据库·qt