基于setnx,lua脚本和Redisson详解Redis分布式锁实现的三种方式

 更新时间:2026年03月02日 10:02:27   作者:造轮子的猪  
分布式锁是解决分布式系统中多节点并发访问共享资源的核心方案,本文从原理层面拆解Redis分布式锁的核心逻辑,并详细分析三种常见实现方式的代码逻辑、优缺点及生产环境注意事项,感兴趣的朋友跟随小编一起看看吧

分布式锁是解决分布式系统中多节点并发访问共享资源的核心方案,Redis凭借高性能、原子性操作等特性,成为实现分布式锁的主流选择。本文从原理层面拆解Redis分布式锁的核心逻辑,并详细分析三种常见实现方式的代码逻辑、优缺点及生产环境注意事项。

一、Redis分布式锁核心原理

1.1 核心设计目标

一个可靠的分布式锁需满足以下特性:

  • 互斥性:同一时刻只能有一个客户端持有锁,避免并发操作共享资源;
  • 安全性:锁只能由持有者释放,不能被其他客户端误删;
  • 超时释放:避免客户端持有锁后宕机,导致锁永久无法释放(死锁);
  • 原子性:加锁、释放锁的核心操作需原子执行,避免并发场景下的逻辑漏洞;
  • 可重入(可选):同一客户端持有锁后,再次请求锁时无需重新获取(增强易用性)。

1.2 Redis实现锁的核心基础

Redis通过以下核心命令支撑分布式锁实现:

命令/特性作用
SET key value NX EX t原子执行“不存在则设置(NX)+ 过期时间(EX)”,避免加锁与设超时的拆分操作
DEL key删除锁(释放锁),需配合校验锁归属,避免误删
Lua脚本将“校验锁归属+释放锁”封装为原子操作,解决释放锁的并发安全问题
Redisson(客户端)基于Redis封装了可重入、自动续期、公平锁等高级特性,简化锁的使用

二、三种实现方式详解(逻辑+问题分析)

方式1:基础实现(SetNX + 手动校验释放)

2.1 代码逻辑拆解

@Resource
private StringRedisTemplate stringRedisTemplate;
/**
 * 示例:扣减库存(基础分布式锁实现)
 */
private void order(){
    // 1. 生成唯一锁值(用于校验锁归属,避免误删)
    String lockValue = UUID.randomUUID().toString();
    // 2. 加锁:SETNX + 过期时间(原子操作),30秒自动释放
    Boolean locked = stringRedisTemplate.opsForValue()
            .setIfAbsent("product:1001:lock", lockValue, 30, TimeUnit.SECONDS);
    try {
        // 3. 加锁成功则执行业务逻辑(扣减库存)
        if (locked) {
            Integer count = (Integer) stringRedisTemplate.opsForHash().get("product:1001","number");
            if (count > 0) {
                stringRedisTemplate.opsForHash().put("product:1001", "number", count - 1);
            }
        }
    } finally {
        // 4. 释放锁:先校验锁归属,再删除(非原子操作)
        if (lockValue.equals(stringRedisTemplate.opsForValue().get("product:1001:lock"))) {
            stringRedisTemplate.delete("product:1001:lock");
        }
    }
}

2.2 核心逻辑

  1. 加锁:通过setIfAbsent(底层是SET NX EX)实现原子加锁,同时设置30秒超时,避免死锁;
  2. 锁归属校验:用UUID生成唯一lockValue,释放锁前校验值是否匹配,防止误删其他客户端的锁;
  3. 释放锁:finally块中执行释放逻辑,确保业务执行完(或异常)后释放锁。

2.3 存在的核心问题

  • 释放锁非原子性:“校验锁归属 + 删除锁”是两步操作,若校验后锁恰好过期,此时其他客户端已加锁,当前客户端执行delete会误删新锁;
  • 无重试机制:加锁失败直接放弃,实际场景中需结合业务设置重试逻辑(如循环重试+休眠);
  • 无锁续期:若业务执行时间超过30秒,锁会自动过期,导致多个客户端同时执行业务,破坏互斥性;
  • Hash操作类型转换风险stringRedisTemplate.opsForHash().get()返回Object,强转Integer可能出现类型异常(需先判空+类型校验)。

方式2:优化版(Lua脚本保证释放锁原子性)

2.1 代码逻辑拆解

@Resource
private StringRedisTemplate stringRedisTemplate;
private static final String LOCK_KEY = "product:1001:lock";
private static final String STOCK_KEY = "product:1001:number";
private static final long LOCK_TIMEOUT = 30; // 锁超时时间(秒)
private static final long SLEEP_TIME = 100; // 重试间隔(毫秒)
private void order() {
    String lockValue = UUID.randomUUID().toString();
    try {
        // 1. 尝试获取锁(原子加锁)
        Boolean locked = tryAcquireLock(lockValue);
        if (!locked) {
            // 加锁失败可重试/返回失败(示例直接返回,实际可加循环重试)
            return;
        }
        // 2. 执行业务:获取并扣减库存(简化为String结构,避免Hash类型转换问题)
        String stockStr = stringRedisTemplate.opsForValue().get(STOCK_KEY);
        if (stockStr == null || Integer.parseInt(stockStr) <= 0) {
            return;
        }
        stringRedisTemplate.opsForValue().set(STOCK_KEY, String.valueOf(Integer.parseInt(stockStr) - 1));
    } finally {
        // 3. 释放锁:Lua脚本封装“校验+删除”,保证原子性
        releaseLock(lockValue);
    }
}
/**
 * 原子加锁:SET NX EX
 */
private Boolean tryAcquireLock(String lockValue) {
    return stringRedisTemplate.opsForValue()
            .setIfAbsent(LOCK_KEY, lockValue, LOCK_TIMEOUT, TimeUnit.SECONDS);
}
/**
 * 原子释放锁:Lua脚本
 */
private void releaseLock(String lockValue) {
    String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
            "return redis.call('del', KEYS[1]) " +
            "else " +
            "return 0 " +
            "end";
    stringRedisTemplate.execute(
            new DefaultRedisScript<>(script, Long.class),
            Arrays.asList(LOCK_KEY),
            lockValue
    );
}

2.2 核心优化点

  1. 释放锁原子化:将“校验锁归属(get)+ 删除锁(del)”封装为Lua脚本,Redis会原子执行脚本内容,彻底解决方式1的“误删锁”问题;
  2. 简化库存存储:将库存从Hash改为String结构,避免类型转换异常,降低业务复杂度;
  3. 代码分层:抽离tryAcquireLockreleaseLock方法,提升代码复用性。

2.3 仍存在的问题

  • 无锁续期:核心问题未解决!若业务执行时间(如扣减库存需40秒)超过LOCK_TIMEOUT(30秒),锁会提前过期,导致并发安全问题;
  • 重试逻辑缺失:示例中加锁失败直接返回,实际场景需增加“循环重试+最大重试次数”,避免因瞬时并发导致加锁失败;
  • 无异常处理Integer.parseInt(stockStr)未做异常捕获,若库存值非数字会抛出运行时异常;
  • 单点风险:依赖单个Redis节点,若节点宕机,锁数据丢失,可能导致多个客户端同时加锁。

方式3:生产级实现(Redisson客户端)

Redisson是Redis官方推荐的Java客户端,内置了分布式锁的完整实现,解决了手动实现的诸多痛点。

2.1 代码逻辑拆解

@Resource
private RedissonClient redissonClient;
@Resource
private StringRedisTemplate stringRedisTemplate;
private void order() {
    // 1. 获取分布式锁对象(可重入锁)
    RLock lock = redissonClient.getLock("product:1001:lock");
    try {
        // 2. 加锁:最多等待10秒,锁30秒后自动释放;获取锁成功则执行业务
        if (lock.tryLock(10, 30, TimeUnit.SECONDS)) {
            try {
                // 3. 扣减库存业务逻辑
                Integer count = (Integer) stringRedisTemplate.opsForHash().get("product:1001","number");
                if (count != null && count > 0) {
                    stringRedisTemplate.opsForHash().put("product:1001", "number", count - 1);
                }
            } finally {
                // 4. 手动释放锁(若业务执行完未超时,主动释放)
                lock.unlock();
            }
        }
    } catch (InterruptedException e) {
        // 5. 中断异常处理,恢复线程中断状态
        Thread.currentThread().interrupt();
    }
}

2.2 核心优势(Redisson解决的痛点)

  1. 自动锁续期(看门狗机制)
    • 若业务执行时间超过锁超时时间,Redisson会启动后台线程(默认每10秒)自动将锁超时时间续期至30秒;
    • 只有当客户端正常释放锁或宕机时,续期才会停止,彻底解决“锁提前过期”问题。
  2. 可重入性:基于Redis的Hash结构存储锁的持有次数,同一客户端多次tryLock不会导致死锁;
  3. 优雅的重试与等待tryLock(waitTime, leaseTime, unit)支持“最大等待时间”,加锁失败时会阻塞等待,直到超时或获取到锁;
  4. 原子性加锁/释放锁:底层封装了Lua脚本,保证加锁、释放锁的原子性;
  5. 集群适配:支持Redis主从、哨兵、集群模式,解决单点风险(需配置Redisson的集群模式)。

2.3 需注意的细节

  • 解锁时机:必须在finally块中执行unlock(),但需先判断lock.isHeldByCurrentThread(),避免未持有锁时执行解锁抛出异常;
  • 异常处理tryLock会抛出InterruptedException,需捕获并恢复线程中断状态,避免线程状态异常;
  • Redisson配置:生产环境需正确配置RedissonClient(如连接池、超时时间、集群节点),否则会导致锁性能下降或失效;
  • 锁粒度:避免使用过大的锁粒度(如“product:lock”),应细化到具体资源(如“product:1001:lock”),减少锁竞争。

三、三种实现方式对比与生产建议

实现方式优点缺点适用场景
方式1(基础版)代码简单、无额外依赖释放锁非原子、无续期、易误删锁测试环境、低并发非核心业务
方式2(Lua版)释放锁原子化、代码结构清晰无续期、重试逻辑需手动实现、单点风险中小并发、核心逻辑简单场景
方式3(Redisson)自动续期、可重入、集群适配引入Redisson依赖、配置稍复杂生产环境、高并发核心业务

生产环境核心建议

  1. 优先使用Redisson:手动实现分布式锁易遗漏边界条件(如续期、原子性、集群),Redisson封装了成熟的解决方案,是生产首选;
  2. 锁超时时间合理设置:结合业务平均执行时间设置(如业务平均执行5秒,设置超时30秒),避免过短导致续期频繁,过长导致死锁风险;
  3. 避免长时间持有锁:分布式锁应“快进快出”,执行业务逻辑时避免耗时操作(如数据库慢查询、远程调用),必要时拆分锁粒度;
  4. 集群模式适配:若Redis为集群/哨兵模式,Redisson需配置RedissonNodeClusterServersConfig,避免主从切换导致锁丢失;
  5. 兜底方案:分布式锁失效时,需有兜底逻辑(如数据库乐观锁),避免数据一致性问题。

四、总结

Redis分布式锁的核心是原子加锁+安全释放+超时兜底

  1. 基础实现(方式1)仅适用于测试,核心问题是释放锁非原子、无续期;
  2. Lua脚本优化版(方式2)解决了释放锁原子性问题,但仍需手动处理续期、重试等逻辑;
  3. Redisson(方式3)是生产级方案,通过看门狗机制、可重入性、集群适配,解决了手动实现的所有核心痛点。

生产环境中,除非有特殊定制需求,否则优先基于Redisson实现分布式锁,既保证可靠性,又降低开发和维护成本。

到此这篇关于Redis分布式锁实现的三种方式-基于setnx,lua脚本和Redisson的文章就介绍到这了,更多相关redis分布式锁内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • redis实现的四种常见限流策略

    redis实现的四种常见限流策略

    因为在网站运行期间可能会因为突然的访问量导致业务异常、也有可能遭受别人恶意攻,所以我们对网站要进行限流,本文主要介绍了redis四种常见限流策略,感兴趣的可以了解一下
    2021-06-06
  • Redis中跳表的实现原理分析

    Redis中跳表的实现原理分析

    Redis中的跳表是一种高效的多层链表结构,通过随机概率算法决定节点的层数,从而实现快速的插入、删除和查询操作,跳表的平均时间复杂度为O(logn),最差情况为O(n),每个节点包含值和指向更高层节点的指针,以及回退指针以提高操作效率
    2025-02-02
  • 详解Redis SCAN命令实现有限保证的原理

    详解Redis SCAN命令实现有限保证的原理

    这篇文章主要介绍了Redis SCAN命令实现有限保证的原理,本文通过实例代码给大家介绍的非常详细,具有一定的参考借鉴价值 ,需要的朋友可以参考下
    2019-07-07
  • Redis 常见缓存问题总结

    Redis 常见缓存问题总结

    这篇文章主要给大家总结了一些Redis 常见缓存问题,并介绍了解决办法,文中的图文示例介绍的非常仔细,感兴趣的同学可以参考阅读下
    2023-06-06
  • Redis 有序集合的使用场景

    Redis 有序集合的使用场景

    在Redis的学习中,有序集合是一种非常实用的数据结构,本文就来介绍一下Redis 有序集合的使用场景,具有一定的参考价值,感兴趣的可以了解一下
    2024-03-03
  • Redis连接与查询的实用操作指南

    Redis连接与查询的实用操作指南

    当我们需要深入排查缓存问题或验证数据一致性时,直接使用 redis-cli 是最有效的手段,但在此之前,你需要知道服务在哪、如何认证以及使用哪些命令,本文精简了冗余理论,直接提供可落地的操作步骤,跟着步骤走,快速解决你的 Redis 连接与查询难题,需要的朋友可以参考下
    2026-03-03
  • Redis+AOP+自定义注解实现限流

    Redis+AOP+自定义注解实现限流

    这篇文章主要为大家详细介绍了如何利用Redis+AOP+自定义注解实现个小功能:自定义拦截器限制访问次数,也就是限流,感兴趣的可以了解一下
    2022-06-06
  • 深入理解Redis被覆写后的失效时间

    深入理解Redis被覆写后的失效时间

    Redis覆写已存在的键会导致其旧的失效时间被新的键值对所取代,本文详细解析了在键被覆写时,其失效时间的变化,具有一定的参考价值,感兴趣的可以了解一下
    2024-09-09
  • 浅谈Redis缓存击穿、缓存穿透、缓存雪崩的解决方案

    浅谈Redis缓存击穿、缓存穿透、缓存雪崩的解决方案

    这篇文章主要介绍了浅谈Redis缓存击穿、缓存穿透、缓存雪崩的解决方案,缓存是分布式系统中的重要组件,主要解决在高并发、大数据场景下,热点数据访问的性能问题,需要的朋友可以参考下
    2023-03-03
  • Redis中BigKey与MoreKey优化详解

    Redis中BigKey与MoreKey优化详解

    这篇文章主要介绍了Redis中BigKey与MoreKey优化,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2026-03-03

最新评论