首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Redis分布式锁的N种实现与性能对比:架构师教你选择最优方案,避免踩坑!

Redis分布式锁的N种实现与性能对比:架构师教你选择最优方案,避免踩坑!

作者头像
格姗知识圈
发布2025-06-16 21:53:46
发布2025-06-16 21:53:46
3110
举报
文章被收录于专栏:格姗知识圈格姗知识圈

凌晨2点,手机疯狂震动。运维同事在群里艾特所有人:"订单系统炸了,大量重复订单!"我瞬间清醒,脑子里闪过一个念头——完了,分布式锁又出幺蛾子了。

排查到最后发现,问题出在我们用的那个"看起来很简单"的Redis分布式锁实现上。那一刻我深刻意识到,分布式锁这个看似基础的组件,水其实深得很

单机时代的美好回忆

以前单机应用多简单,一个synchronized关键字就能搞定所有并发问题。但微服务时代,多个实例同时运行,JVM级别的锁根本管不了其他机器上的线程。

你有没有遇到过这种场景:用户疯狂点击下单按钮,结果生成了好几个订单?或者库存扣减时出现超卖?这些都是典型的分布式环境下缺乏有效锁机制导致的。

最naive的实现——SETNX的坑

刚开始接触Redis分布式锁时,大家都会写出这样的代码:

代码语言:javascript
复制
public boolean tryLock(String key) {
    // 看起来很简单对吧?
    return jedis.setnx(key, "locked") == ;
}

public void unlock(String key) {
    jedis.del(key);
}

我敢打赌,十个新手里有九个会这么写。但这玩意儿有个致命问题:如果业务逻辑执行过程中服务挂了,锁永远不会被释放

生产环境就这么被我搞挂过一次,所有请求都卡在获取锁的地方,系统彻底僵死。那种绝望的感觉,说多了都是泪。

加上过期时间就安全了?

吃一堑长一智,马上想到给锁加个过期时间:

代码语言:javascript
复制
public boolean tryLock(String key, int expireSeconds) {
    if (jedis.setnx(key, "locked") == ) {
        jedis.expire(key, expireSeconds);
        return true;
    }
    return false;
}

但这里又有个原子性问题:如果setnx成功了,但还没来得及expire服务就挂了呢?锁还是会永远存在。

SET命令的优雅解决方案

Redis 2.6.12版本开始,SET命令支持更多参数,可以原子性地设置值和过期时间:

代码语言:javascript
复制
public class RedisDistributedLock {
    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";

    public boolean tryLock(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);
    }

    public boolean releaseLock(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 result.equals(1L);
    }
}

注意这里用requestId作为锁的值,释放锁时会验证,防止误删别人的锁。这个细节很重要,我见过不少因为没做这个校验导致的bug。

Redisson——拯救强迫症的神器

手写分布式锁确实容易出问题,Redisson这个库帮我们封装了很多细节:

代码语言:javascript
复制
@Service
public class OrderService {

    @Autowired
    private RedissonClient redissonClient;

    public void createOrder(String userId) {
        String lockKey = "order_lock_" + userId;
        RLock lock = redissonClient.getLock(lockKey);

        try {
            // 尝试获取锁,最多等待10秒,锁自动释放时间30秒
            if (lock.tryLock(, , TimeUnit.SECONDS)) {
                // 处理订单逻辑
                processOrder(userId);
            } else {
                throw new RuntimeException("获取锁失败,请稍后重试");
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            // 只释放自己持有的锁
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
}

Redisson还有个看门狗机制,如果你不指定锁的过期时间,它会每隔10秒检查一次,如果锁还被持有就自动续期。这解决了业务执行时间不可预估的问题。

性能对比:数据说话

我在压测环境跑了一轮对比,结果很有意思:

  • 原生SET+Lua脚本:QPS约8000,平均响应时间3ms
  • Redisson普通锁:QPS约6000,平均响应时间5ms
  • Redisson看门狗锁:QPS约5500,平均响应时间6ms

可以看出,功能越丰富,性能损耗越大。但对于大部分业务场景,这点性能差异完全可以接受,毕竟稳定性比那几毫秒更重要

选择建议:没有银弹,只有合适

根据我这些年的踩坑经验:

高并发、性能敏感的场景:手写SET+Lua脚本方案,控制精度,性能最优。

大部分业务场景:直接用Redisson,省心省力,功能完善。

对一致性要求极高的场景:考虑RedLock算法,或者干脆用ZooKeeper。

记住一点:技术选型没有标准答案,只有最适合当前业务的选择。我见过为了炫技选择复杂方案最后把自己坑惨的,也见过因为过度追求性能忽略可靠性导致线上事故的。

你在项目中用过哪种分布式锁方案?踩过什么坑?欢迎留言分享你的经验。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-06-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 格姗知识圈 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 单机时代的美好回忆
  • 最naive的实现——SETNX的坑
  • 加上过期时间就安全了?
  • SET命令的优雅解决方案
  • Redisson——拯救强迫症的神器
  • 性能对比:数据说话
  • 选择建议:没有银弹,只有合适
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档