分布式锁是微服务架构中解决并发资源竞争的核心组件。在高并发设计和分布式事务场景下,基于数据库的唯一约束或Redis的SETNX命令实现的锁方案存在可用性和可靠性短板。Redisson作为Java生态中最成熟的Redis客户端框架,提供了开箱即用的分布式锁实现,包含自动续期、可重入、公平锁和读写锁等完整能力。本文以Spring Boot 3项目为例,给出Redisson分布式锁的集成方案和接口防重复提交的工程化实践。
Redisson核心依赖与Spring Boot 3配置
Spring Boot框架集成Redisson需要引入redisson-spring-boot-starter依赖,并配置Redis连接参数。Redisson 3.x版本全面支持Spring Boot 3和Java 17+:
<!-- pom.xml -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.31.0</version>
</dependency>
<!-- Spring Boot 3.3.x -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
Redisson支持单节点、集群、哨兵和主从四种部署模式。生产环境推荐集群模式配置:
# application.yml
spring:
data:
redis:
host: 192.168.1.100
port: 6379
password: yourPassword
database: 0
# Redisson配置
redisson:
config: |
clusterServersConfig:
nodeAddresses:
- "redis://192.168.1.101:6379"
- "redis://192.168.1.102:6379"
- "redis://192.168.1.103:6379"
password: yourPassword
scanInterval: 1000
readMode: "SLAVE"
subscriptionMode: "SLAVE"
slaveConnectionMinimumIdleSize: 10
slaveConnectionPoolSize: 64
masterConnectionMinimumIdleSize: 10
masterConnectionPoolSize: 64
idleConnectionTimeout: 10000
connectTimeout: 10000
timeout: 3000
retryAttempts: 3
retryInterval: 1500
threads: 16
nettyThreads: 32
codec: !<org.redisson.codec.JsonJacksonCodec> {}
Redisson分布式锁的底层原理与RLock接口
Redisson的分布式锁基于Redis的Hash结构和Lua脚本实现,具备可重入性和自动续期能力。理解其底层机制有助于在服务治理层面正确使用:
// Redisson加锁的Lua脚本(简化版)
// KEYS[1] = 锁名称
// ARGV[1] = 锁超时时间
// ARGV[2] = 客户端ID:线程ID
// 1. 判断锁是否存在
if redis.call('exists', KEYS[1]) == 0 then
// 锁不存在,创建锁,设置重入计数为1
redis.call('hset', KEYS[1], ARGV[2], 1)
redis.call('pexpire', KEYS[1], ARGV[1])
return nil
end
// 2. 判断当前线程是否持有锁
if redis.call('hexists', KEYS[1], ARGV[2]) == 1 then
// 可重入,计数+1
redis.call('hincrby', KEYS[1], ARGV[2], 1)
redis.call('pexpire', KEYS[1], ARGV[1])
return nil
end
// 3. 锁被其他线程持有,返回剩余TTL
return redis.call('pttl', KEYS[1])
RLock接口的核心用法:
@Service
public class OrderService {
@Autowired
private RedissonClient redissonClient;
public void createOrder(String userId, String productId) {
// 构造锁Key
String lockKey = "order:lock:" + userId + ":" + productId;
RLock lock = redissonClient.getLock(lockKey);
try {
// 尝试加锁,最多等待5秒,锁自动30秒过期
boolean locked = lock.tryLock(5, 30, TimeUnit.SECONDS);
if (!locked) {
throw new BusinessException("操作过于频繁,请稍后重试");
}
// 执行业务逻辑:检查库存、创建订单、扣减库存
doCreateOrder(userId, productId);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new BusinessException("加锁被中断");
} finally {
// 确保释放锁
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
private void doCreateOrder(String userId, String productId) {
// 业务实现
}
}
Watchdog自动续期机制是Redisson的核心特性。当不指定leaseTime时,Redisson启动一个后台定时任务,每10秒(默认看门狗超时时间30秒的1/3)检查锁是否仍被当前线程持有,如果是则续期至30秒:
// 不指定leaseTime,启用Watchdog自动续期
RLock lock = redissonClient.getLock("myLock");
lock.lock(); // 默认30秒TTL,Watchdog每10秒续期
try {
// 长时间业务操作(超过30秒也不会丢锁)
Thread.sleep(60000);
} finally {
lock.unlock();
}
// 指定leaseTime则禁用Watchdog(锁在指定时间后自动释放)
lock.lock(10, TimeUnit.SECONDS); // 10秒后自动释放,不续期
// 适合业务确定在短时间内完成的场景
基于注解+AOP的防重复提交方案
接口防重复提交是API接口规范中的常见需求。通过自定义注解和AOP切面,将分布式锁逻辑从业务代码中解耦:
// 1. 定义防重注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PreventRepeat {
String key() default ""; // SpEL表达式,支持从参数提取Key
int waitTime() default 3; // 等待获取锁时间(秒)
int leaseTime() default 10; // 锁持有时间(秒)
String message() default "请勿重复提交";
}
// 2. AOP切面实现
@Aspect
@Component
public class PreventRepeatAspect {
@Autowired
private RedissonClient redissonClient;
@Autowired
private SpelExpressionParser parser;
@Around("@annotation(preventRepeat)")
public Object around(ProceedingJoinPoint joinPoint, PreventRepeat preventRepeat) throws Throwable {
// 构造锁Key
String lockKey = buildLockKey(joinPoint, preventRepeat);
RLock lock = redissonClient.getLock(lockKey);
boolean locked = lock.tryLock(
preventRepeat.waitTime(),
preventRepeat.leaseTime(),
TimeUnit.SECONDS
);
if (!locked) {
throw new BusinessException(preventRepeat.message());
}
try {
return joinPoint.proceed();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
private String buildLockKey(ProceedingJoinPoint joinPoint, PreventRepeat preventRepeat) {
String keyExpr = preventRepeat.key();
if (keyExpr.isEmpty()) {
// 默认使用方法签名+参数作为Key
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
String argsHash = Arrays.hashCode(joinPoint.getArgs()) + "";
return "prevent:" + signature.getDeclaringTypeName() + ":" + signature.getName() + ":" + argsHash;
}
// 解析SpEL表达式
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
EvaluationContext context = new StandardEvaluationContext();
String[] paramNames = signature.getParameterNames();
Object[] args = joinPoint.getArgs();
for (int i = 0; i < paramNames.length; i++) {
context.setVariable(paramNames[i], args[i]);
}
Expression expression = parser.parseExpression(keyExpr);
String keyValue = expression.getValue(context, String.class);
return "prevent:" + signature.getDeclaringTypeName() + ":" + method.getName() + ":" + keyValue;
}
}
在Controller中使用注解实现防重复提交:
@RestController
@RequestMapping("/api/orders")
public class OrderController {
// 基于用户ID和商品ID防重
@PreventRepeat(
key = "#request.userId + ':' + #request.productId",
waitTime = 0,
leaseTime = 5,
message = "订单正在处理中,请勿重复提交"
)
@PostMapping("/create")
public Result<OrderVO> createOrder(@RequestBody @Valid OrderRequest request) {
OrderVO order = orderService.createOrder(request);
return Result.success(order);
}
// 全局防重(同一方法+相同参数在5秒内不可重复调用)
@PreventRepeat(leaseTime = 5, message = "操作过于频繁")
@PostMapping("/query")
public Result<List<OrderVO>> queryOrders(@RequestBody OrderQueryRequest request) {
return Result.success(orderService.queryOrders(request));
}
}
读写锁与信号量在并发场景中的应用
Redisson提供RReadWriteLock和RSemaphore等高级同步工具,适用于更复杂的业务中台建设场景:
// 读写锁:缓存重建场景
@Service
public class CacheService {
@Autowired
private RedissonClient redissonClient;
public String getDataFromCache(String key) {
RReadWriteLock rwLock = redissonClient.getReadWriteLock("cache:rw:" + key);
// 读锁:允许多个线程同时读
RLock readLock = rwLock.readLock();
readLock.lock();
try {
String cachedValue = redisTemplate.opsForValue().get(key);
if (cachedValue != null) {
return cachedValue;
}
} finally {
readLock.unlock();
}
// 缓存未命中,获取写锁重建
RLock writeLock = rwLock.writeLock();
writeLock.lock();
try {
// 双重检查
String cachedValue = redisTemplate.opsForValue().get(key);
if (cachedValue != null) {
return cachedValue;
}
// 从数据库加载
String dbValue = loadFromDatabase(key);
redisTemplate.opsForValue().set(key, dbValue, 30, TimeUnit.MINUTES);
return dbValue;
} finally {
writeLock.unlock();
}
}
// 信号量:限流控制
public void executeWithLimit(String resource, int maxConcurrent, Runnable task) {
RSemaphore semaphore = redissonClient.getSemaphore("semaphore:" + resource);
semaphore.trySetPermits(maxConcurrent);
try {
// 尝试获取许可
boolean acquired = semaphore.tryAcquire(3, TimeUnit.SECONDS);
if (!acquired) {
throw new BusinessException("系统繁忙,请稍后重试");
}
task.run();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
semaphore.release();
}
}
}
Redisson锁的异常处理与生产级注意事项
分布式锁在高并发设计中的可靠性与异常处理直接相关。几个关键的工程化注意事项:
// 1. 锁释放前检查持有者,避免释放非自身持有的锁
RLock lock = redissonClient.getLock(key);
try {
if (lock.tryLock(5, 30, TimeUnit.SECONDS)) {
doBusiness();
}
} finally {
// 锁可能已过期被其他线程获取,直接unlock会抛IllegalMonitorStateException
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
// 2. 处理Redis宕机场景的降级策略
@Configuration
public class LockFallbackConfig {
@Bean
@ConditionalOnProperty(name = "lock.strategy", havingValue = "database")
public DistributedLock databaseLock(DataSource dataSource) {
// 降级到数据库行锁方案
return new DatabaseDistributedLock(dataSource);
}
}
// 3. 监控锁等待和持有时间(消息中间件告警)
@Aspect
@Component
public class LockMonitorAspect {
@Around("execution(* org.redisson.api.RLock.tryLock(..))")
public Object monitorLock(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
Object result = pjp.proceed();
long elapsed = System.currentTimeMillis() - start;
if (elapsed > 2000) {
metricsClient.recordTimer("distributed.lock.wait", elapsed);
if (elapsed > 5000) {
alertService.sendAlert("锁等待超时: " + elapsed + "ms");
}
}
return result;
}
}
// 4. 避免锁粒度过大导致性能瓶颈
// 反模式:单个全局锁
String lockKey = "global:order:lock"; // 所有订单操作共享一把锁
// 正确模式:细粒度锁
String lockKey = "order:lock:" + userId; // 按用户维度加锁
Redisson的RedLock算法在多节点Redis场景下提供了更高可用性,但在实际使用中需权衡一致性和可用性。对于多数业务场景,单节点Redisson锁配合合理的超时配置和降级策略已能满足需求。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/springboot3-ji-cheng-redisson-fen-bu-shi-suo-shi-xian-yu/