在微服务架构中,分布式锁是解决并发资源竞争、数据一致性 的核心工具,Redis 凭借高性能、高可用特性成为分布式锁的主流选型。但原生 Redis 锁存在死锁、释放别人的锁、单点故障、锁超时等问题,无法直接适配生产环境。
本文从 Redis 分布式锁底层原理出发,基于 Redisson 框架实现高可用分布式锁,同时解决死锁自动释放、锁重入、公平锁、主从切换一致性等生产痛点,提供锁超时续约、集群部署、故障降级方案,打造适配高并发场景的生产级分布式锁体系。
一、核心认知:Redis 分布式锁原理与生产痛点
1. Redis 分布式锁核心原理
分布式锁需满足四大特性:互斥性、原子性、高可用、可重入,Redis 基于以下命令实现基础锁逻辑:
-
加锁:
SET key value NX EX timeout(NX:仅不存在时设置,保证互斥;EX:自动过期,避免死锁); -
解锁:需先判断锁归属(避免释放别人的锁),再删除锁,需通过 Lua 脚本保证原子性: lua
-- 解锁Lua脚本:仅当value匹配时才删除锁 if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) else return 0 end -
重试:加锁失败时,通过自旋或阻塞重试,避免立即返回失败。
2. 原生 Redis 锁生产痛点
原生实现的分布式锁在生产场景中存在以下致命问题:
- 死锁风险:虽然设置了过期时间,但业务执行耗时超过过期时间,锁自动释放后,其他线程加锁成功,原线程执行完后可能释放别人的锁;
- 主从切换不一致:Redis 主从异步复制,主节点加锁成功后未同步到从节点就宕机,从节点切换为主节点后,锁丢失,导致并发安全问题;
- 锁不可重入:同一线程无法重复获取同一把锁,适配递归、嵌套场景时受限;
- 无自动续约:业务执行耗时不确定,过期时间设置过短会导致锁提前释放,过长则浪费资源;
- 公平性问题:非公平锁可能导致线程饥饿,高并发下部分线程长期无法获取锁。
二、生产级方案:基于 Redisson 实现高可用分布式锁
Redisson 是 Redis 官方推荐的 Java 客户端,内置分布式锁实现,解决了原生锁的所有痛点,支持可重入锁、公平锁、联锁、红锁等多种锁类型,同时提供自动续约、主从切换防护等能力。
1. 环境准备:集成 Redisson
(1)引入依赖
xml
<!-- Redisson依赖(适配Redis 6.x+) -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.23.5</version>
</dependency>
(2)配置 Redisson(Spring Boot)
yaml
# application.yml
spring:
redis:
host: 127.0.0.1
port: 6379
password: 123456
database: 0
timeout: 3000ms
redisson:
# 单节点配置(生产建议集群/哨兵模式)
singleServerConfig:
address: redis://127.0.0.1:6379
password: 123456
database: 0
connectionMinimumIdleSize: 5 # 最小空闲连接数
connectionPoolSize: 20 # 连接池大小
idleConnectionTimeout: 10000 # 空闲连接超时时间
connectTimeout: 3000 # 连接超时时间
# 锁默认配置
lockWatchdogTimeout: 30000 # 看门狗自动续约时间(30秒)
2. 核心锁类型实战
(1)可重入锁(最常用)
解决同一线程重复加锁问题,底层通过 Redis Hash 结构存储锁信息(key:锁名称,field:线程 ID,value:重入次数):
java
运行
package com.example.redisson.service;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
@Service
public class StockService {
@Resource
private RedissonClient redissonClient;
// 扣减库存(可重入锁示例)
public void deductStock(Long productId) {
// 1. 获取锁(锁名称:按资源维度定义,如product_stock_1001)
RLock lock = redissonClient.getLock("product_stock_" + productId);
try {
// 2. 加锁:最多等待5秒,获取锁后10秒自动过期(看门狗会自动续约)
boolean locked = lock.tryLock(5, 10, TimeUnit.SECONDS);
if (!locked) {
throw new RuntimeException("获取锁失败,请稍后重试");
}
// 3. 业务逻辑:扣减库存(可调用其他需要同一把锁的方法,实现重入)
doDeductStock(productId);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("扣减库存失败");
} finally {
// 4. 解锁:仅持有锁的线程能解锁,自动处理重入次数
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
// 嵌套方法,同一线程可重入锁
private void doDeductStock(Long productId) {
RLock lock = redissonClient.getLock("product_stock_" + productId);
try {
boolean locked = lock.tryLock(5, 10, TimeUnit.SECONDS);
if (!locked) {
throw new RuntimeException("获取锁失败");
}
// 实际库存扣减逻辑(数据库操作)
System.out.println("库存扣减成功,productId=" + productId);
} catch (InterruptedException e) {
throw new RuntimeException("扣减库存失败");
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}
核心特性:看门狗机制会在锁过期前 1/3 时间(默认 10 秒)自动续约,确保业务未执行完时锁不失效。
(2)公平锁
保证线程获取锁的顺序,避免线程饥饿,适用于对公平性要求高的场景(如队列任务处理):
java
运行
// 获取公平锁
RLock fairLock = redissonClient.getFairLock("fair_lock_" + productId);
try {
// 加锁逻辑与可重入锁一致
boolean locked = fairLock.tryLock(5, 10, TimeUnit.SECONDS);
if (!locked) {
throw new RuntimeException("获取公平锁失败");
}
// 业务逻辑
} finally {
if (fairLock.isHeldByCurrentThread()) {
fairLock.unlock();
}
}
注意:公平锁性能略低于非公平锁,高并发场景需权衡公平性与性能。
(3)红锁(RedLock):解决主从切换一致性问题
针对 Redis 主从架构锁丢失问题,红锁通过同时在多个独立 Redis 节点(至少 3 个)加锁,仅当超过半数节点加锁成功时,才认为锁获取成功,彻底解决主从切换导致的锁失效:
java
运行
import org.redisson.api.RRedLock;
import org.redisson.api.RedissonClient;
// 红锁示例
public void deductStockWithRedLock(Long productId) {
// 1. 获取多个独立节点的锁(需配置多个Redis节点,非主从关系)
RLock lock1 = redissonClient.getLock("product_stock_" + productId + "_node1");
RLock lock2 = redissonClient.getLock("product_stock_" + productId + "_node2");
RLock lock3 = redissonClient.getLock("product_stock_" + productId + "_node3");
// 2. 构建红锁
RRedLock redLock = new RRedLock(lock1, lock2, lock3);
try {
// 3. 加锁:等待5秒,过期时间10秒,需半数以上节点加锁成功
boolean locked = redLock.tryLock(5, 10, TimeUnit.SECONDS);
if (!locked) {
throw new RuntimeException("获取红锁失败");
}
// 4. 业务逻辑
doDeductStock(productId);
} catch (InterruptedException e) {
throw new RuntimeException("扣减库存失败");
} finally {
// 5. 解锁:自动释放所有节点的锁
if (redLock.isHeldByCurrentThread()) {
redLock.unlock();
}
}
}
适用场景:对数据一致性要求极高,可接受一定性能损耗的场景(红锁需多节点通信,性能略低)。
3. 生产级优化:锁超时续约 + 故障降级
(1)自定义锁超时续约策略
针对超长耗时业务,可自定义看门狗续约逻辑,避免锁提前释放:
java
运行
// 自定义续约线程
private void renewLock(RLock lock, long renewInterval, TimeUnit unit) {
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
executor.scheduleAtFixedRate(() -> {
// 仅当线程持有锁时才续约
if (lock.isHeldByCurrentThread() && !lock.isExpired()) {
lock.expire(10, TimeUnit.SECONDS); // 续约10秒
} else {
executor.shutdown();
}
}, 0, renewInterval, unit);
}
// 使用自定义续约
public void deductStockWithRenew(Long productId) {
RLock lock = redissonClient.getLock("product_stock_" + productId);
try {
boolean locked = lock.tryLock(5, 10, TimeUnit.SECONDS);
if (!locked) {
throw new RuntimeException("获取锁失败");
}
// 启动续约线程,每5秒续约一次
renewLock(lock, 5, TimeUnit.SECONDS);
// 超长耗时业务逻辑
longTimeDeductStock(productId);
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
(2)锁获取失败降级方案
高并发场景下,加锁失败时避免直接抛出异常,通过降级策略保证服务可用性:
java
运行
public void deductStockWithFallback(Long productId) {
RLock lock = redissonClient.getLock("product_stock_" + productId);
try {
boolean locked = lock.tryLock(5, 10, TimeUnit.SECONDS);
if (locked) {
// 加锁成功:正常扣减库存
doDeductStock(productId);
} else {
// 加锁失败:降级处理(如返回排队提示、异步重试)
fallbackDeductStock(productId);
}
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
// 降级逻辑:异步重试扣减库存
private void fallbackDeductStock(Long productId) {
// 发送消息到MQ,异步重试扣减
mqTemplate.send("stock-deduct-fallback", productId);
throw new RuntimeException("当前请求过多,请稍后重试");
}
三、Redis 分布式锁集群部署规范
1. 部署架构选择
| 架构类型 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 单节点 | 测试 / 非核心业务 | 部署简单、性能高 | 单点故障,锁完全不可用 |
| 主从 + 哨兵 | 核心业务、追求性能 | 高可用、性能优异 | 主从切换可能导致锁丢失(需红锁补偿) |
| 红锁(多独立节点) | 超高一致性需求 | 彻底解决锁丢失问题 | 部署复杂、性能略低 |
2. 生产部署核心规范
- 锁名称设计:按 "资源类型 + 资源 ID" 命名(如
product_stock_1001),避免锁粒度过大 / 过小; - 过期时间设置:默认 10-30 秒,结合业务耗时调整,避免过长导致锁占用资源;
- 重试策略:加锁失败时采用 "自旋 + 休眠" 重试(如重试 3 次,每次休眠 100ms),避免频繁重试;
- 监控告警:通过 Redis 监控锁的持有时间、获取失败次数,超过阈值触发告警;
- 资源隔离:不同业务的锁使用不同 Redis 数据库或前缀,避免锁冲突。
四、生产常见问题排查与解决方案
1. 锁提前释放导致并发问题
- 原因:业务执行耗时超过锁过期时间,看门狗续约失败,或解锁逻辑错误;
- 解决方案:1. 优化业务逻辑缩短耗时;2. 调整看门狗超时时间,开启自定义续约;3. 检查解锁逻辑,确保仅持有锁的线程解锁。
2. 主从切换后锁丢失
- 原因:主节点加锁成功后未同步到从节点,主节点宕机后从节点无锁信息;
- 解决方案:1. 采用红锁架构;2. 主从同步改为半同步复制,确保锁信息同步完成后再返回加锁成功。
3. 锁竞争激烈导致服务响应缓慢
- 原因:锁粒度过大,多个线程竞争同一把锁;
- 解决方案:1. 缩小锁粒度(如按用户 ID 哈希分片锁);2. 采用公平锁避免线程饥饿;3. 加锁失败降级,避免阻塞等待。
4. Redisson 连接池耗尽
- 原因:连接池大小不足,高并发下获取连接超时;
- 解决方案:1. 调大连接池大小(根据并发量调整);2. 优化锁持有时间,减少连接占用;3. 开启连接池监控,动态调整参数。
五、总结
Redis 分布式锁的生产级落地,核心是解决 "一致性、高可用、稳定性" 三大问题。Redisson 框架通过封装锁逻辑、提供看门狗续约、红锁等特性,彻底解决了原生 Redis 锁的痛点,其可重入锁、公平锁等类型能适配不同业务场景。
生产落地时需注意:1. 结合业务场景选择合适的锁类型与部署架构;2. 合理设置锁过期时间与重试策略;3. 做好锁监控与降级方案,确保高并发下的服务可用性;4. 针对主从切换风险,核心业务建议采用红锁架构,平衡一致性与性能。