Redis系列--实现一个简单的redis分布式锁

分布式锁常见的实现方式主要有三种:

  1. 数据库乐观锁
  2. 基于 Redis 的分布式锁
  3. 基于 ZooKeeper 的分布式锁

为了确保分布式锁可用,至少要满足以下四个条件:

  1. 互斥性:任意时刻只有一个客户端能持有锁。
  2. 不会死锁:即使客户端在持有锁期间崩溃而未解锁,其他客户端依然可以继续加锁。
  3. 容错性:只要大部分 Redis 节点正常运行,客户端就可以加锁和解锁。
  4. 解铃还须系铃人:加锁和解锁必须是同一个客户端,不能解掉别人加的锁。

正确的 Redis 分布式锁实现

1. 正确的加锁代码

java 复制代码
public class RedisTool {
    private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    private static final String SET_WITH_EXPIRE_TIME = "PX";

    /**
     * 尝试获取分布式锁
     * @param jedis Redis客户端
     * @param lockKey 锁
     * @param requestId 请求标识
     * @param expireTime 超期时间(毫秒)
     * @return 是否获取成功
     */
    public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
        String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
        return LOCK_SUCCESS.equals(result);
    }
}

2. 错误的加锁示例

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

❌ 这个实现的问题在于 setnxexpire 不是原子操作,如果程序崩溃,就可能造成锁永远无法释放。


正确的解锁代码

typescript 复制代码
public class RedisTool {
    private static final Long RELEASE_SUCCESS = 1L;

    /**
     * 释放分布式锁
     * @param jedis Redis客户端
     * @param lockKey 锁
     * @param requestId 请求标识
     * @return 是否释放成功
     */
    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));
        return RELEASE_SUCCESS.equals(result);
    }
}

这段 Lua 脚本会先获取锁对应的 value,判断是否和 requestId 一致,如果一致才删除锁(解锁)。

使用 eval() 可以保证操作的原子性,避免误解锁。


错误的解锁示例

1. 不判断是否为同一客户端

typescript 复制代码
public static void wrongReleaseLock1(Jedis jedis, String lockKey) {
    jedis.del(lockKey);
}

❌ 问题:直接删除锁,可能误删别人加的锁。

2. 非原子操作

typescript 复制代码
public static void wrongReleaseLock2(Jedis jedis, String lockKey, String requestId) {
    // 判断加锁与解锁是否是同一个客户端
    if (requestId.equals(jedis.get(lockKey))) {
        // 若在此时,这把锁突然过期,客户端B已经加锁
        // 执行 del() 就可能把客户端B的锁误删
        jedis.del(lockKey);
    }
}

❌ 问题:get + del 分两步执行,可能导致锁被错误释放。


总结

  • 加锁 :一定要保证原子操作,可以直接用 SET key value NX PX expireTime
  • 解锁:必须保证原子性,并且只允许锁的拥有者解锁,推荐使用 Lua 脚本。
  • 避免错误 :不要拆分加锁和设置过期时间,不要直接 del 锁。

在生产环境中,也可以使用 Redisson 这样的开源库,它封装好了 Redis 分布式锁的各种细节,避免踩坑。

相关推荐
戴西软件9 分钟前
戴西软件入选2026年安徽省制造业数智化转型服务商名单
java·大数据·服务器·前端·人工智能
爱棋笑谦9 分钟前
springboot—数据源相关配置
java·spring boot·spring
budingxiaomoli8 小时前
Spring IoC &DI
java·spring·ioc·di
Spider Cat 蜘蛛猫8 小时前
Springboot SSO系统设计文档
java·spring boot·后端
未若君雅裁8 小时前
MySQL高可用与扩展-主从复制读写分离分库分表
java·数据库·mysql
月落归舟9 小时前
一篇文章了解Redis内存淘汰机制与过期Key清理
数据库·redis·mybatis
学习中.........9 小时前
从扰动函数的变化,感受红黑树带来的性能提升
java
phltxy9 小时前
Redis 事务
数据库·redis·缓存
计算机安禾9 小时前
【c++面向对象编程】第24篇:类型转换运算符:自定义隐式转换与explicit
java·c++·算法
weixin1997010801610 小时前
【保姆级教程】淘宝/天猫商品详情 API(item_get)接入指南:Python/Java/PHP 调用示例与 JSON 返回值解析
java·python·php