如何保证 Redis 分布式锁的高可用和高性能?
2026/7/23 4:44:59 网站建设 项目流程

做后端开发时,分布式锁是一个绕不开的话题。

比如:

  • 秒杀时,同一个商品库存不能被多个请求同时扣成负数
  • 定时任务部署了多台机器,但同一时刻只能有一台执行
  • 用户重复点击支付按钮,不能生成多笔订单
  • 多个服务同时更新同一份缓存,不能互相覆盖

这些场景里,我们都希望有一把“锁”。

在单机程序里,可以用synchronizedReentrantLock。但服务一旦部署到多台机器上,本地锁就不够用了。因为 A 机器加的锁,B 机器根本不知道。

这时候,Redis 分布式锁就很常见了。

不过,Redis 分布式锁不是简单写个setnx就完事了。真正上线时,我们还要考虑两个问题:

怎么保证高可用?
怎么保证高性能?

这篇文章就用比较直白的方式聊一聊。


一、Redis 分布式锁的基本写法

最常见的加锁方式是:

SET lock_key unique_value NX EX 30

这条命令里有几个关键点:

  • lock_key:锁的名字
  • unique_value:锁的唯一标识,通常用 UUID
  • NX:只有 key 不存在时才设置成功
  • EX 30:锁 30 秒后自动过期

为什么不用普通的setnx再单独设置过期时间?

因为这两步不是原子操作。

如果代码刚执行完setnx,服务突然宕机,还没来得及设置过期时间,这把锁就可能永远不释放。

所以加锁一定要用 Redis 的原子命令:

SET key value NX EX seconds

二、释放锁不能直接 delete

很多新手会这样释放锁:

DEL lock_key

看起来没问题,但这里有个坑。

假设线程 A 拿到了锁,锁过期时间是 10 秒。结果 A 执行业务执行了 15 秒,锁已经自动过期了。

这时线程 B 又拿到了同一把锁。

然后 A 执行完了,直接DEL lock_key

问题来了:A 删除的是谁的锁?

其实删掉的是 B 的锁。

所以释放锁时,必须先判断这把锁是不是自己的。

一般用 Lua 脚本保证“判断 + 删除”是原子操作:

if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end

也就是说:

谁加的锁,谁才能释放。

这里的unique_value就派上用场了。


三、锁过期时间怎么设置?

这是分布式锁里很容易被忽略的问题。

锁时间太短,业务还没执行完,锁就过期了,可能导致多个线程同时处理。

锁时间太长,服务宕机后,其他请求要等很久才能重新拿到锁。

所以一般建议:

  1. 先估算业务正常执行时间
  2. 过期时间设置为正常耗时的 2 到 3 倍
  3. 对耗时不稳定的业务,考虑自动续期

比如一个扣库存接口,正常 200ms 完成,那锁设置 3 到 5 秒就够了。

但如果是导入 Excel、生成报表、批量处理数据这种任务,耗时可能波动很大,就不能简单写死 30 秒。


四、自动续期:别让锁半路过期

如果业务执行时间不确定,可以用“看门狗”机制。

简单理解就是:

线程拿到锁之后,后台开一个续期任务。
只要业务还没执行完,就定期延长锁的过期时间。

Redisson 就内置了这个机制。

比如:

RLock lock = redissonClient.getLock("order:lock:" + orderId); try { lock.lock(); // 业务逻辑 } finally { lock.unlock(); }

默认情况下,Redisson 会帮你自动续期,避免业务还没执行完锁就过期。

当然,这不是说用了 Redisson 就万事大吉。我们还是要控制业务耗时,不要把锁持有得太久。

分布式锁的原则是:

锁的范围越小越好,持有时间越短越好。


五、如何保证高性能?

Redis 本身性能很高,但分布式锁用不好,也会拖慢系统。

1. 锁粒度要小

不要动不动就锁一个大 key。

比如扣库存时,不要写成:

product_lock

这样所有商品都会抢同一把锁。

更好的方式是:

product_lock:1001 product_lock:1002 product_lock:1003

不同商品用不同的锁,互不影响。

锁粒度越细,并发能力越好。


2. 加锁失败不要疯狂重试

有些代码加锁失败后,会立刻 while 循环重试。

这很危险。

如果并发量很大,大量请求一直打 Redis,会让 Redis 压力变大。

更好的方式是:

  • 加锁失败后短暂休眠
  • 设置最大重试次数
  • 使用随机退避时间,避免请求同时再次冲上来

比如:

for (int i = 0; i < 3; i++) { boolean locked = tryLock(); if (locked) { break; } Thread.sleep(50 + new Random().nextInt(50)); }

别小看这几十毫秒,它能明显降低 Redis 的瞬时压力。


3. 锁内代码越少越好

不要把无关逻辑都塞进锁里。

比如下面这种就不太好:

如果真正需要保护的只是“扣库存”,那就只锁扣库存那一小段。

锁内代码越多,其他线程等待越久,系统吞吐量越差。


4. 能不用锁就不用锁

分布式锁不是银弹。

有些场景可以用数据库唯一索引、乐观锁、消息队列来解决。

比如防止重复下单,可以用订单表唯一索引:

UNIQUE(user_id, product_id)

这样即使并发请求进来,数据库也能拦住重复数据。

能用业务模型解决的问题,不一定非要上分布式锁。


六、如何保证高可用?

Redis 分布式锁依赖 Redis。如果 Redis 挂了,锁自然也会受影响。

常见方案有几种。

1. Redis 主从 + 哨兵

这是比较常见的部署方式。

Redis 主节点挂了以后,哨兵会自动选举新的主节点。

优点是简单、成熟,很多公司都在用。

但它也有一个问题:

Redis 主从复制是异步的。

假设客户端在主节点加锁成功,但这条数据还没同步到从节点,主节点突然挂了。哨兵把从节点提升为主节点后,新主节点上可能没有这把锁。

这时其他客户端可能再次加锁成功。

所以主从哨兵能提高 Redis 可用性,但不能完全保证锁在极端故障下绝对安全。


2. Redis Cluster

Redis Cluster 可以把数据分片到多个节点,提高整体容量和可用性。

但对分布式锁来说,通常一把锁只会落到某一个 Redis 节点上。

所以它更多解决的是 Redis 集群扩展问题,不是锁语义的绝对可靠问题。


3. RedLock

RedLock 是 Redis 官方提出的一种分布式锁算法。

它的思路是:
客户端向多个独立 Redis 节点尝试加锁,只有超过半数节点加锁成功,才认为锁获取成功。

比如有 5 个 Redis 节点,至少 3 个加锁成功才算成功。

这样单个 Redis 节点挂掉时,不会直接影响锁的判断。

不过 RedLock 在业界也有一些争议,主要是实现复杂度、时钟漂移、网络延迟等问题。

我的建议是:

  • 普通业务:Redisson + Redis 主从/哨兵基本够用
  • 金融、支付、强一致场景:不要只依赖 Redis 锁,要结合数据库事务、唯一索引、状态机等机制兜底

一句话:

Redis 分布式锁可以提高并发控制能力,但不要把系统正确性全部压在它一个人身上。


七、实际开发中的推荐写法

如果是 Java 项目,比较推荐直接使用 Redisson。

它帮我们处理了很多细节:

  • 原子加锁
  • 自动续期
  • 可重入锁
  • 释放锁校验
  • 等待时间控制

示例代码:

RLock lock = redissonClient.getLock("stock:lock:" + productId); boolean locked = false; try { locked = lock.tryLock(3, 10, TimeUnit.SECONDS); if (!locked) { throw new RuntimeException("系统繁忙,请稍后再试"); } // 执行业务逻辑 deductStock(productId); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException("获取锁失败", e); } finally { if (locked && lock.isHeldByCurrentThread()) { lock.unlock(); } }

这里有几个细节:

  • tryLock(3, 10, TimeUnit.SECONDS)表示最多等待 3 秒,锁 10 秒后过期
  • finally里释放锁,避免异常导致锁不释放
  • isHeldByCurrentThread()防止误删其他线程的锁

总结

Redis 分布式锁看起来简单,真正用好却需要注意很多细节。

高性能主要靠:

  • 小粒度锁
  • 短时间持有
  • 合理重试
  • 减少锁内逻辑

高可用主要靠:

  • Redis 主从、哨兵或集群部署
  • Redisson 这类成熟客户端
  • 必要时考虑 RedLock
  • 核心业务用数据库事务、唯一索引、状态机兜底

最后记住一句话:

分布式锁解决的是“谁先执行”的问题,业务兜底解决的是“数据最终不能错”的问题。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询