Redisson在Spring Boot项目中的集成与实战

Redisson在Spring Boot项目中的集成与实战

在Spring Boot项目中使用Redisson实现分布式锁,不仅能保留Redisson的强大功能,还能借助Spring的依赖注入、AOP等特性简化开发。本文将详细介绍Redisson在Spring Boot中的集成方式、配置优化及实战用法。

一、集成Redisson到Spring Boot项目

1. 添加依赖

pom.xml中添加Redisson和Redis客户端依赖:

xml 复制代码
<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连接信息:

yaml 复制代码
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配置类:

java 复制代码
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一致:

java 复制代码
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实现方法级的分布式锁:

定义注解:
java 复制代码
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切面实现:
java 复制代码
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);
    }
}
使用注解:
java 复制代码
@Service
public class StockService {
    
    @RedissonLock(value = "lock:stock:#{productId}", waitTime = 5, leaseTime = 20)
    public void deductStock(String productId) {
        // 无需手动管理锁,方法执行时自动加锁解锁
        System.out.println("扣减库存,产品ID:" + productId);
    }
}

3. 读写锁示例:优化读多写少场景

java 复制代码
@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)在集群环境中的使用

java 复制代码
@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中增加连接池配置:

java 复制代码
@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):

java 复制代码
@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健康检查,确保连接正常:

java 复制代码
@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中配置监控端点:

yaml 复制代码
management:
  endpoints:
    web:
      exposure:
        include: redisson

访问/actuator/redisson查看Redisson连接状态、锁统计等信息。

2. 性能调优建议

  • 减少锁粒度:只在关键操作上加锁,避免锁范围过大;
  • 降低锁持有时间:耗时操作(如IO、远程调用)尽量放在锁外;
  • 使用读写锁 :对读多写少场景,用RReadWriteLock替代普通锁;
  • 合理设置超时参数 :根据业务特性调整waitTimeleaseTime,避免长时间等待。

五、总结:Spring Boot + Redisson = 高效分布式锁

通过Spring Boot的自动配置和Redisson的强大功能,我们可以轻松实现高性能、高可用的分布式锁:

  1. 基础集成:通过starter依赖和配置类快速搭建Redisson客户端;
  2. 灵活使用:支持手动锁、注解锁、多种锁类型(可重入锁、读写锁、红锁等);
  3. 生产优化:通过连接池、序列化器、健康检查等提升性能和可靠性。

合理使用Redisson分布式锁,能有效解决分布式系统中的资源竞争问题,让你的微服务架构更加稳定可靠。

相关推荐
c_zyer29 分钟前
FreeSWITCH与Java交互实战:从EslEvent解析到Spring Boot生态整合的全指南
spring boot·netty·freeswitch·eslevent
郝学胜-神的一滴31 分钟前
Spring Boot Actuator 保姆级教程
java·开发语言·spring boot·后端·程序人生
剪刀石头布啊1 小时前
数据口径
前端·后端·程序员
剪刀石头布啊1 小时前
http状态码大全
前端·后端·程序员
jiangxia_10241 小时前
面试系列:什么是JAVA并发编程中的JUC并发工具类
java·后端
用户1512905452201 小时前
踩坑与成长:WordPress、MyBatis-Plus 及前端依赖问题解决记录
前端·后端
A_氼乚1 小时前
JVM运行时数据区相关知识,这篇文档会勘正你的许多理解!(本周会补上更详细的图式)
后端
斜月1 小时前
Springboot 项目加解密的那些事儿
spring boot·后端
草莓爱芒果1 小时前
Spring Boot中使用Bouncy Castle实现SM2国密算法(与前端JS加密交互)
java·spring boot·算法
慕y2742 小时前
Java学习第九十三部分——RestTemplate
java·开发语言·学习