释放锁时未做身份校验,直接执行 DEL 命令删除键

:释放锁时未做身份校验,直接执行 DEL 命令删除键。典型场景:服务 A 持有锁后,业务逻辑耗时超过锁过期时间,锁被自动释放;服务 B 趁机加锁成功,此时服务 A 执行完业务,直接 DEL 锁就会误删服务 B 持有的锁,导致互斥性失效。

表现:多个服务实例同时持有同一把锁,操作同一资源,出现数据不一致(如超卖、重复订单)。

解决方案:加锁时存入全局唯一的随机值(如 UUID+线程 ID)作为 value,释放锁前先验证 value 是否与自身持有一致,一致才释放。关键是用 Lua 脚本保证“验证+删除”的原子性,避免验证后锁过期被他人持有。

-- 安全释放锁的 Lua 脚本if redis.call('get', KEYS[1]) == ARGV[1] then
    return redis.call('del', KEYS[1])else
    return 0


成因:锁的过期时间设置过短,而业务逻辑执行耗时过长,导致锁在业务完成前就自动过期释放,其他服务可趁机加锁,引发并发冲突。比如锁设为 30 秒过期,但数据库复杂查询、第三方接口调用耗时 40 秒,就会出现锁提前失效。

表现:业务执行中锁被释放,多个服务同时操作资源,出现数据错误,且问题具有随机性(取决于业务耗时是否超过过期时间)。

解决方案:引入“锁续约(Watch Dog)”机制。服务成功加锁后,启动后台守护线程,每隔锁过期时间的 1/3 (如 10 秒)检查锁是否仍被自身持有,若持有则延长锁的过期时间(重置为 30 秒),直到业务完成主动释放锁。

实际开发中无需手动实现,Redisson 框架内置 Watch Dog 机制,加锁后自动续约,彻底解决锁提前释放问题。


  redis:     # Redlock 专用多组独立节点配置     redlock:       # 第一组节点(可部署主从+哨兵)       node1:         host: 192.168 .1 .101         port: 6379         password: 123456         database: 0         timeout: 5000   # 连接超时时间(毫秒)       # 第二组节点(独立服务器,与第一组无关联)       node2:         host: 192.168 .1 .102         port: 6379         password: 123456         database: 0         timeout: 5000       # 第三组节点(独立服务器,建议跨机房)       node3:         host: 192.168 .1 .103         port: 6379


    // 第一组 Redlock 节点客户端     @Bean(name = "redlockClient1")     public RedissonClient redlockClient1 (             @Value("${spring.redis.redlock.node1.host}") String host,             @Value("${spring.redis.redlock.node1.port}") int port,             @Value("${spring.redis.redlock.node1.password}") String password,             @Value("${spring.redis.redlock.node1.database}") int database,             @Value("${spring.redis.redlock.node1.timeout}") int timeout) {         Config config = new Config ();         // 单节点模式(若为集群,可改用 useSentinelServers 配置哨兵)        config.useSingleServer()                .setAddress( "redis://" + host + ":" + port)                .setPassword(password)                .setDatabase(database)                .setTimeout(timeout);         return Redisson.create(config);    }     // 第二组 Redlock 节点客户端     @Bean(name = "redlockClient2")     public RedissonClient redlockClient2 (             @Value("${spring.redis.redlock.node2.host}") String host,             @Value("${spring.redis.redlock.node2.port}") int port,             @Value("${spring.redis.redlock.node2.password}") String password,             @Value("${spring.redis.redlock.node2.database}") int database,             @Value("${spring.redis.redlock.node2.timeout}") int timeout) {         Config config = new Config ();        config.useSingleServer(remenzhibo.net)                .setAddress( "redis://" + host + ":" + port)                .setPassword(password)                .setDatabase(database)                .setTimeout(timeout);         return Redisson.create(config);    }     // 第三组 Redlock 节点客户端     @Bean(name = "redlockClient3")     public RedissonClient redlockClient3 (             @Value("${spring.redis.redlock.node3.host}") String host,             @Value("${spring.redis.redlock.node3.port}") int port,  ttzbapp.com           @Value("${spring.redis.redlock.node3.password}") String password,             @Value("${spring.redis.redlock.node3.database}") int database,             @Value("${spring.redis.redlock.node3.timeout}") int timeout) {         Config config = new Config ();        config.useSingleServer()                .setAddress( "redis://" + host + ":" + port)                .setPassword(password)                .setDatabase(database)                .setTimeout(timeout);

    @Autowired     @Qualifier("redlockClient1")     private RedissonClient redlockClient1;     @Autowired     @Qualifier("redlockClient2")nbczhibo.com     private RedissonClient redlockClient2;     @Autowired     @Qualifier("redlockClient3")     private RedissonClient redlockClient3;     @Autowired     private StockMapper stockMapper; ftzhibo.cn    public void deductStock (Long productId) {         // 1. 生成统一锁Key,获取多节点锁对象         String lockKey = "lock:stock:" + productId;lnzxyp.com       RLock lock1 = redlockClient1.getLock(lockKey);         RLock lock2 = redlockClient2.getLock(lockKey);         RLock lock3 = redlockClient3.getLock(lockKey);         // 2. 组合为Redlock锁,触发多节点投票         RedissonRedLock redLock = new RedissonRedLock (lock1, lock2, lock3);         try {             // 3. 加锁:1秒内等待节点响应,锁过期时间30秒(内置续约)             boolean locked = redLock.tryLock( 1000 , 30000 , TimeUnit.MILLISECONDS);             if (locked) {                 // 4. 核心业务:库存扣减(仅保留锁内必要操作)                 Stock stock = stockMapper.selectById(productId);                 if (stock != null && stock.getCount() > 0 ) {                    stock.setCount(stock.getCount() - 1 );                    stockMapper.updateById(stock);                }            } else {                 // 加锁失败兜底                 throw new RuntimeException ( "系统繁忙,请稍后再试" );            }        } catch (InterruptedException e) {            Thread.currentThread().interrupt();             throw new RuntimeException ( "操作被中断,请重试" );        } finally { zq26.cn            // 5. 安全释放锁:仅当前线程持有锁时执行             if (redLock.isHeldByCurrentThread()) {                redLock.unlock();            }


成因:基础实现的锁不支持重入,即同一服务的同一线程在持有锁的情况下,再次请求加同一把锁会失败。典型场景:服务 A 加锁后,执行的方法中又调用了另一个需要加同一把锁的方法,第二次加锁失败,导致线程阻塞,引发死锁。

表现:业务线程阻塞,接口超时无响应,排查后发现是同一线程重复加锁被拒。

解决方案:实现可重入锁机制。锁的 value 存储“唯一标识 + 重入次数”,第一次加锁时存入标识和次数 1;同一线程再次加锁时,验证标识一致,将次数加 1;释放锁时,次数减 1,直到次数为 0 才删除键彻底释放锁。

手动实现逻辑复杂,推荐使用 Redisson 的 RLock 接口,天然支持可重入,用法与本地 synchronized 锁一致,无需额外开发。

问题 5:主从切换锁丢失(脑裂)——集群环境下的隐形坑

成因:Redis 主从集群中,主节点存储锁数据后,尚未同步到从节点就宕机;哨兵将从节点切换为主节点,新主节点无该锁数据,其他服务可重新加锁,导致原锁失效,出现多个服务持有锁的情况。这是主从 + 哨兵模式的固有风险。

表现:主从切换后,原持有锁的服务仍在执行业务,新服务却能加锁成功,引发数据冲突,且问题难以复现(仅发生在主从切换瞬间)。

解决方案

  • 低一致性场景:开启 Redis 主从同步的“持久化 + 等待同步确认”,主节点写入锁数据后,等待至少 1 个从节点同步完成再返回加锁成功,降低锁丢失概率(仍无法完全避免)。

  • 高一致性场景:放弃主从 + 哨兵模式,改用 Redlock 算法,通过多主节点投票机制,从根源上解决脑裂导致的锁丢失问题。

问题 6:加锁失败无重试策略——业务偶发失败

成因:加锁时仅尝试一次,若因网络波动、Redis 临时繁忙导致加锁失败,直接抛出异常,导致业务执行失败。分布式环境中,网络抖动、Redis 瞬时压力大是常见情况,无重试策略会放大这类问题的影响。

表现:部分用户操作失败(如提交订单提示“系统繁忙”),重试后可成功,问题具有随机性。

解决方案:实现带限制的重试机制,加锁失败后,间隔一定时间(如 100ms)重试,同时设置最大重试



public boolean lockWithRetry (String key, String value, long expireMs,lnzxyp.com int maxRetry, long retryIntervalMs) {     for ( int i = 0 ; i < maxRetry; i++) {         Boolean result = redisTemplate.opsForValue().setIfAbsent(key, value, expireMs, TimeUnit.MILLISECONDS); zggren.com        if (Boolean.TRUE.equals(result)) {             return true ;        }         try {            Thread.sleep(retryIntervalMs);        } catch (InterruptedException e) {            Thread.currentThread().interrupt();             return false ;

成因:分布式环境中出现网络分区,持有锁的服务与 Redis 集群隔离,无法主动释放锁,也无法接收锁续约信号;锁过期后,其他服务加锁成功;网络恢复后,原持有锁的服务误以为锁仍有效,继续操作资源,导致数据冲突。

表现:极端网络异常后,出现数据不一致,且问题难以排查(与网络分区时间、锁过期时间强相关)。

解决方案

  • 引入业务校验机制:操作资源前,再次校验资源状态(如扣减库存前,检查库存是否与预期一致),避免基于过期锁的无效操作。

  • 缩短锁过期时间:结合 Watch Dog 机制,将基础过期时间设短(如 10 秒),减少网络分区导致的锁状态不一致窗口。

  • 使用 Redlock 算法:多主节点投票机制,可降低网络分区对锁状态的影响,提升一致性。



  1. 优先使用成熟框架:放弃手动实现分布式锁,Redisson 已封装解决上述所有问题,开箱即用,稳定性远高于自定义实现。

  2. 匹配业务场景选型:高一致性、高可用场景用 Redlock 算法;一般场景用主从 + 哨兵模式;根据并发量设计锁粒度(精细化/分段锁)。

  3. 完善监控与兜底:监控锁持有时间、加锁成功率、Redis 集群状态,设置告警阈值;加锁失败、锁过期等场景,需有业务兜底策略(重试、返回友好提示、队列缓存)。

总之,Redis 分布式锁的核心是“兼顾互斥性与高可用”,避开上述问题后,才能真正成为分布式系统解决并发冲突的利器,而非系统的新瓶颈。



请使用浏览器的分享功能分享到微信等