Redisson在Spring Boot项目中的集成与实战
在Spring Boot项目中使用Redisson实现分布式锁,不仅能保留Redisson的强大功能,还能借助Spring的依赖注入、AOP等特性简化开发。本文将详细介绍Redisson在Spring Boot中的集成方式、配置优化及实战用法。
一、集成Redisson到Spring Boot项目
1. 添加依赖
在pom.xml
中添加Redisson和Redis客户端依赖:
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.23.1</version> <!-- 根据实际情况选择最新稳定版本 -->
</dependency>
Redisson的starter会自动引入Redis客户端(如Lettuce或Jedis),无需额外添加。
2. 配置Redisson客户端
在application.yml
中配置Redis连接信息:
spring:
redis:
host: localhost
port: 6379
password: 123456 # 如果有密码
timeout: 3000ms # 连接超时时间
# 集群模式配置(如果使用集群)
# cluster:
# nodes:
# - 192.168.1.1:6379
# - 192.168.1.2:6379
# - 192.168.1.3:6379
创建Redisson配置类:
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RedissonConfig {
/**
* 创建 Redisson 客户端实例,用于连接 Redis 服务器
* 使用 Spring 的 @Bean 注解将其注册为 Spring 容器中的单例 Bean
* destroyMethod 指定在 Spring 容器关闭时调用 shutdown 方法释放资源
*/
@Bean(destroyMethod = "shutdown")
public RedissonClient redissonClient() {
// 创建 Redisson 配置对象
Config config = new Config();
// 配置 Redis 单节点模式连接
// 从系统属性中获取 Redis 服务器地址、端口、密码和连接超时时间
// 如果系统属性中未设置,则使用默认值(localhost:6379,无密码,超时3000毫秒)
config.useSingleServer()
.setAddress("redis://" +
System.getProperty("spring.redis.host", "localhost") + ":" +
System.getProperty("spring.redis.port", "6379"))
.setPassword(System.getProperty("spring.redis.password", null))
.setConnectTimeout(Integer.parseInt(
System.getProperty("spring.redis.timeout", "3000")));
// 注释掉的集群模式配置示例
// 如需使用集群模式,取消注释以下代码并注释掉上面的单节点配置
// config.useClusterServers()
// .addNodeAddress("redis://192.168.1.1:6379", "redis://192.168.1.2:6379")
// .setScanInterval(2000); // 集群状态扫描间隔,单位为毫秒
// 根据配置创建 Redisson 客户端实例并返回
return Redisson.create(config);
}
}
二、在Spring Boot中使用Redisson分布式锁
1. 基础用法:手动获取和释放锁
通过注入RedissonClient
获取锁对象,用法与原生Redisson一致:
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
@Service
public class StockService {
private final RedissonClient redissonClient;
public StockService(RedissonClient redissonClient) {
this.redissonClient = redissonClient;
}
public void deductStock(String productId) {
String lockKey = "lock:stock:" + productId;
RLock lock = redissonClient.getLock(lockKey);
// 方式1:阻塞获取锁(默认看门狗30秒自动续命)
lock.lock();
try {
// 执行业务逻辑:扣减库存
System.out.println("扣减库存,产品ID:" + productId);
} finally {
// 确保锁释放
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
// 方式2:带超时的获取锁(推荐)
try {
// 尝试10秒,获取到锁后持有30秒(无需看门狗)
boolean isLocked = lock.tryLock(10, 30, TimeUnit.SECONDS);
if (isLocked) {
try {
// 执行业务逻辑
System.out.println("扣减库存,产品ID:" + productId);
} finally {
lock.unlock();
}
} else {
// 获取锁失败处理
System.out.println("获取锁失败,稍后重试");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
2. 高级用法:基于AOP实现分布式锁注解
为了简化代码,可以通过自定义注解和AOP实现方法级的分布式锁:
定义注解:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RedissonLock {
/**
* 锁的名称(支持SpEL表达式)
*/
String value() default "lock:default";
/**
* 等待获取锁的最大时间
*/
long waitTime() default 10;
/**
* 锁的持有时间
*/
long leaseTime() default 30;
/**
* 时间单位
*/
TimeUnit timeUnit() default TimeUnit.SECONDS;
}
AOP切面实现:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
@Aspect
@Component
public class RedissonLockAspect {
private final RedissonClient redissonClient;
private final ExpressionParser parser = new SpelExpressionParser();
private final ParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
@Autowired
public RedissonLockAspect(RedissonClient redissonClient) {
this.redissonClient = redissonClient;
}
@Around("@annotation(redissonLock)")
public Object around(ProceedingJoinPoint joinPoint, RedissonLock redissonLock) throws Throwable {
// 解析锁名称(支持SpEL表达式)
String lockName = parseSpel(joinPoint, redissonLock.value());
RLock lock = redissonClient.getLock(lockName);
boolean isLocked = false;
try {
// 根据配置获取锁
if (redissonLock.waitTime() == -1) {
// 不设置等待时间,一直等待
lock.lock(redissonLock.leaseTime(), redissonLock.timeUnit());
isLocked = true;
} else {
// 带超时的获取锁
isLocked = lock.tryLock(
redissonLock.waitTime(),
redissonLock.leaseTime(),
redissonLock.timeUnit()
);
}
if (isLocked) {
// 获取锁成功,执行方法
return joinPoint.proceed();
} else {
// 获取锁失败,抛出异常或自定义处理
throw new RuntimeException("获取锁失败,请稍后重试");
}
} finally {
// 释放锁
if (isLocked && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
// 解析SpEL表达式
private String parseSpel(ProceedingJoinPoint joinPoint, String expression) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
Object[] args = joinPoint.getArgs();
// 获取方法参数名
String[] parameterNames = discoverer.getParameterNames(method);
if (parameterNames == null || parameterNames.length == 0) {
return expression;
}
// 构建上下文
EvaluationContext context = new StandardEvaluationContext();
for (int i = 0; i < parameterNames.length; i++) {
context.setVariable(parameterNames[i], args[i]);
}
// 解析表达式
return parser.parseExpression(expression).getValue(context, String.class);
}
}
使用注解:
@Service
public class StockService {
@RedissonLock(value = "lock:stock:#{productId}", waitTime = 5, leaseTime = 20)
public void deductStock(String productId) {
// 无需手动管理锁,方法执行时自动加锁解锁
System.out.println("扣减库存,产品ID:" + productId);
}
}
3. 读写锁示例:优化读多写少场景
@Service
public class CacheService {
private final RedissonClient redissonClient;
public CacheService(RedissonClient redissonClient) {
this.redissonClient = redissonClient;
}
// 读取缓存(允许多个线程同时读)
public String getCache(String key) {
RReadWriteLock rwLock = redissonClient.getReadWriteLock("lock:cache:" + key);
RLock readLock = rwLock.readLock();
readLock.lock();
try {
// 从缓存读取数据
return "cache-data";
} finally {
readLock.unlock();
}
}
// 更新缓存(写时互斥)
public void updateCache(String key, String value) {
RReadWriteLock rwLock = redissonClient.getReadWriteLock("lock:cache:" + key);
RLock writeLock = rwLock.writeLock();
writeLock.lock();
try {
// 更新缓存数据
System.out.println("更新缓存,key=" + key + ", value=" + value);
} finally {
writeLock.unlock();
}
}
}
4. 红锁(RedLock)在集群环境中的使用
@Service
public class PaymentService {
private final RedissonClient redissonClient1;
private final RedissonClient redissonClient2;
private final RedissonClient redissonClient3;
public PaymentService(RedissonClient redissonClient1,
RedissonClient redissonClient2,
RedissonClient redissonClient3) {
this.redissonClient1 = redissonClient1;
this.redissonClient2 = redissonClient2;
this.redissonClient3 = redissonClient3;
}
public void transfer(String fromAccount, String toAccount, double amount) {
// 获取多个节点的锁
RLock lock1 = redissonClient1.getLock("lock:transfer:" + fromAccount);
RLock lock2 = redissonClient2.getLock("lock:transfer:" + fromAccount);
RLock lock3 = redissonClient3.getLock("lock:transfer:" + fromAccount);
// 创建红锁(需要多数节点成功)
RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
try {
// 尝试获取红锁(等待10秒,持有30秒)
boolean isLocked = redLock.tryLock(10, 30, TimeUnit.SECONDS);
if (isLocked) {
try {
// 执行转账操作
System.out.println("执行转账:从" + fromAccount + "到" + toAccount + ",金额:" + amount);
} finally {
redLock.unlock();
}
} else {
throw new RuntimeException("获取红锁失败,转账操作稍后重试");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
三、生产环境配置优化
1. 连接池配置
在RedissonConfig中增加连接池配置:
@Configuration
public class RedissonConfig {
@Bean(destroyMethod = "shutdown")
public RedissonClient redissonClient() {
Config config = new Config();
// 单节点配置
config.useSingleServer()
.setAddress("redis://localhost:6379")
// 连接池配置
.setConnectionPoolSize(64) // 连接池最大容量
.setConnectionMinimumIdleSize(10) // 连接池最小空闲连接数
.setConnectTimeout(3000) // 连接超时时间
.setTimeout(5000) // 命令超时时间
.setRetryAttempts(3) // 重试次数
.setRetryInterval(1500); // 重试间隔(毫秒)
return Redisson.create(config);
}
}
2. 自定义序列化器
默认使用Jackson序列化,可替换为更高效的序列化方式(如Kryo):
@Configuration
public class RedissonConfig {
@Bean(destroyMethod = "shutdown")
public RedissonClient redissonClient() {
Config config = new Config();
// 使用Kryo序列化
config.setCodec(new org.redisson.codec.Kryo5Codec());
// 其他配置...
return Redisson.create(config);
}
}
3. 健康检查配置
添加Redis健康检查,确保连接正常:
@Configuration
public class RedissonHealthConfig {
@Autowired
private RedissonClient redissonClient;
@Bean
public HealthIndicator redissonHealthIndicator() {
return () -> {
try {
// 检查Redis连接
redissonClient.getBucket("health-check").set("ok");
return Health.up().build();
} catch (Exception e) {
return Health.down(e).build();
}
};
}
}
四、监控与调优
1. 启用Redisson监控
在application.yml
中配置监控端点:
management:
endpoints:
web:
exposure:
include: redisson
访问/actuator/redisson
查看Redisson连接状态、锁统计等信息。
2. 性能调优建议
- 减少锁粒度:只在关键操作上加锁,避免锁范围过大;
- 降低锁持有时间:耗时操作(如IO、远程调用)尽量放在锁外;
- 使用读写锁:对读多写少场景,用
RReadWriteLock
替代普通锁; - 合理设置超时参数:根据业务特性调整
waitTime
和leaseTime
,避免长时间等待。
五、总结:Spring Boot + Redisson = 高效分布式锁
通过Spring Boot的自动配置和Redisson的强大功能,我们可以轻松实现高性能、高可用的分布式锁:
- 基础集成:通过starter依赖和配置类快速搭建Redisson客户端;
- 灵活使用:支持手动锁、注解锁、多种锁类型(可重入锁、读写锁、红锁等);
- 生产优化:通过连接池、序列化器、健康检查等提升性能和可靠性。
合理使用Redisson分布式锁,能有效解决分布式系统中的资源竞争问题,让你的微服务架构更加稳定可靠。