news 2026/10/16 3:26:12

若依Spring Boot 3集成Redisson:分布式锁与序列化实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
若依Spring Boot 3集成Redisson:分布式锁与序列化实战

1. 写在前面:若依框架里Redis那点事

先说明白,这个标题里的“破若依”,说的是开源后台管理框架RuoYi(若依)的Spring Boot 3版本。做Java后端的朋友应该都不陌生,若依系框架在国内企业级项目里的出现频率相当高,代码生成器一拖,CRUD页面直接落地,省掉大量重复劳动。但用得多了,问题也暴露得越来越明显,尤其是它内置的那套Redis客户端,在实际业务里越来越不够用。

如果你用的是若依Spring Boot 3版本,打开pom.xml看一眼就会发现,它默认集成的是Spring Data Redis,底层连接器是Lettuce。这个组合在简单缓存场景下没什么毛病,set一个值、get一个值、存个验证码、放个登录token,跑得稳稳当当。可一旦项目进入分布式阶段,需要分布式锁、需要发布订阅、需要信号量控制并发、需要公平锁处理排队任务,Lettuce这套就有点力不从心了。Lettuce本身是异步非阻塞客户端,性能数据其实不差,但它只解决“连接Redis”的问题,不解决“用Redis做分布式协调”的问题。

而Redisson,则是站在另一个高度来解决这件事的。它不只是一个Redis客户端,更是一个基于Redis的分布式工具箱。分布式锁、分布式队列、分布式原子操作、限流器、延迟队列、信号量、布隆过滤器,几乎你能想到的分布式场景工具,Redisson都内置了。关键的是,这些工具使用起来极其简单,比如分布式锁,几行代码就能拿到一把可重入锁,底层自动处理锁续期、自动释放、看门狗机制,这些细节如果自己用Lettuce底层的SETNX命令去实现,坑能踩到怀疑人生。

这篇文章的目标很直接:把若依Spring Boot 3框架里默认的Redis方案,平滑切换到Redisson,并且保留原框架所有相关功能不受影响。适合正在用若依做项目、且项目到了需要分布式协调能力的阶段,或者单纯想给框架换个更趁手的Redis工具的开发者。

整个过程会涉及依赖冲突处理、配置类改造、序列化方案对比、分布式锁实操、以及我实际踩过的几个非常有意思的坑。下面从为什么我要动这个手开始讲。

2. 为什么非换不可:Lettuce的痛和Redisson的痒

2.1 若依默认Redis方案的实际短板

先看一下若依Spring Boot 3默认的Redis使用方式是怎样的。框架里封装了一个RedisCache类,内部注入的是StringRedisTemplate和RedisTemplate,操作方式大致就是redisCache.setCacheObject(key, value),底层走Lettuce连接池。

这意味着什么?意味着你在若依里如果想用分布式锁,只能自己撸代码:

// 自己基于 SETNX 实现的简易锁,问题一堆 public boolean tryLock(String key, long timeout) { Boolean result = redisTemplate.opsForValue() .setIfAbsent(key, Thread.currentThread().getId(), timeout, TimeUnit.SECONDS); return Boolean.TRUE.equals(result); }

这段代码看着没问题,但真实项目中你会遇到这些场景:

第一个问题是锁续期。业务执行超过锁的超时时间,锁自动释放了,另一个线程拿到锁进来了,两个线程同时操作共享资源,分布式锁形同虚设。第二个问题是业务异常时锁释放逻辑容易漏,一漏就是死锁,后面的线程全部卡死。第三个问题是不可重入,同一个线程的方法A调方法B,B也要拿同一把锁,直接死锁。

这些只是分布式锁这一个场景。再比如你需要做一个简单的抢购限流,或者需要延迟队列来处理超时订单,用Lettuce手写?

我见过有个项目用Redis做延迟队列,开发人员自己用ZSet存数据、起定时任务轮询,结果时间精度差、数据多了以后性能惨不忍睹,最后日夜排查还找不出问题在哪。其实Redisson内部封装好的延迟队列已经是生产级方案,直接拿来用就没这些破事。

2.2 Redisson到底带来了什么

Redisson解决的正是“在Redis之上做分布式应用”这件事。它把Redis当成一个分布式计算基础设施,而不是简单的KV存储。

简单列几个我实际用到的功能:

  • 分布式锁:可重入锁、公平锁、读写锁、红锁(多节点场景),自带看门狗自动续期,解锁时自动判断线程身份,防止误删别人的锁。
  • 分布式对象:RBucket、RMap、RSet、RList、RQueue,直接对标JDK集合框架,用法几乎一模一样。
  • 分布式队列:普通队列、阻塞队列、延迟队列、优先队列,延迟队列在订单超时取消场景非常好用。
  • 分布式原子操作:RAtomicLong、RRateLimiter,解决计数器、限流问题。
  • 分布式信号量:RSemaphore,控制多线程并发访问共享资源的数量。
  • 布隆过滤器:RBloomFilter,缓存穿透场景的经典解法。

更重要的是,Redisson在Spring Boot 3环境下对分布式锁提供了注解式支持,@Lock注解一加就完事,代码干净程度和手写分布式锁完全两个水平。

我个人的体会是,如果你的项目已经明确存在多个服务实例、需要共享资源协调,或者未来必然走上分布式这条路,早换早省心。这就像你住的小区已经装了智能门禁,你却坚持每天用钥匙开单元门,能用但完全没有享受到技术带来的便利。

2.3 换Redisson对若依框架有什么风险

坦率说,任何框架替换都有风险。若依本身有大量代码依赖它自己的RedisCache工具类,直接全局替换会引发连锁反应。在实际操作中需要评估好替换范围。

核心风险主要有三个:

  1. 依赖冲突:Redisson自带的Spring Boot Starter和若依里的Spring Data Redis如果配置不当,会导致Bean加载冲突。需要处理好两者共存的关系。
  2. 序列化方案不一致:若依默认Redis存储的对象用JDK序列化,Redisson默认使用的是自研的编解码器。如果两种方式往同一个Redis库里写数据,读出来可能是一堆乱码,甚至反序列化报错。
  3. 配置属性冲突:Spring Boot的spring.data.redis.*配置和Redisson的独立配置体系如果同时生效,可能出现连接参数不一致的问题。

所以替换不是简单加个依赖就完事,必须搞清楚若依的Redis到底在哪里被使用,以及新的连接方式是独立还是共存。

下面进入正题,讲具体怎么操作。

3. 依赖引入与配置拆分:共存的艺术

3.1 版本选择:Spring Boot 3必须配Redisson 3.2x以上

如果你用Spring Boot 2.x,Redisson官方提供了一个redisson-spring-boot-starter,版本对应关系比较清晰。但Spring Boot 3一出来,很多东西底层变了,比如javax改成了jakarta,自动配置的加载机制也有调整,Redisson在3.20.0之前对Spring Boot 3的支持是不完整的。

实际写这篇文章的时候,若依Spring Boot 3版本搭配Redisson 3.23.0以上是比较稳妥的选择。这里有一个硬性条件,Redisson版本要跟Spring Boot版本匹配,否则启动的时候可能报NoSuchBeanDefinitionException,因为Redisson的自动配置类引用了Spring Boot内部的一些组件。

我的建议是直接用3.23.0以上,如果你用的Spring Boot版本非常新,Redisson版本也尽量跟着新一点走。

<dependency> <groupId>org.redisson</groupId> <artifactId>redisson-spring-boot-starter</artifactId> <version>3.23.0</version> </dependency>

3.2 依赖排除:解决与Spring Data Redis的冲突

这里有个极其关键的细节要讲清楚。

Redisson的redisson-spring-boot-starter内部也引入了Spring Data Redis相关的依赖,因为它需要同时操作RedisTemplate和RedissonClient。但问题是,若依框架自己已经引入了Spring Data Redis,两者版本如果不同,Maven会仲裁出其中一个版本,非常容易导致运行时方法签名不匹配。

我实际遇到过的情况是:项目启动时报ClassNotFoundException: org.springframework.data.redis.connection.RedisConnectionFactory,排查一圈发现是版本被Maven仲裁成了不兼容的版本。解决方案是在引入Redisson Starter时,排除它自带的Spring Data Redis依赖,强制使用若依那套版本。

<dependency> <groupId>org.redisson</groupId> <artifactId>redisson-spring-boot-starter</artifactId> <version>3.23.0</version> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </exclusion> </exclusions> </dependency>

不要觉得这个排除多此一举。Spring Boot 3的依赖管理有一套自己的周期表,Redisson Starter的传递依赖如果跟它不匹配,轻则启动告警,重则直接起不来。排除后,若依自己的Redis相关功能保持原样,Redisson作为增强平行接入,互不干扰。

3.3 配置方式:独立配置优先

Redisson在Spring Boot环境下支持两种配置方式:一种是利用Spring Boot的application.yml直接写spring.data.redis相关的配置,让Redisson自动读取;另一种是提供独立的redisson.yaml或RedissonClient配置类。

这里我强烈推荐第二种,独立配置类方案。原因在于:

若依框架里已经配置了spring.data.redis.*参数给Lettuce连接池用,如果你同时也让Redisson去读同一套配置,两者会争抢连接资源,尤其在高并发场景下,Lettuce和Redisson的线程模型不一样,互相干扰非常明显。独立配置可以让Redisson走自己的连接池管理,互不干扰。

我在实际项目中的做法是新建一个RedissonConfig配置类:

@Configuration public class RedissonConfig { @Value("${redisson.host}") private String host; @Value("${redisson.port}") private int port; @Value("${redisson.password:}") private String password; @Bean(destroyMethod = "shutdown") public RedissonClient redissonClient() { Config config = new Config(); config.useSingleServer() .setAddress("redis://" + host + ":" + port) .setPassword(password.isEmpty() ? null : password) .setConnectionPoolSize(64) .setConnectionMinimumIdleSize(16) .setIdleConnectionTimeout(10000) .setConnectTimeout(5000) .setTimeout(3000) .setRetryAttempts(3) .setRetryInterval(1500); return Redisson.create(config); } }

对应的application.yml里增加一段独立的配置:

redisson: host: 127.0.0.1 port: 6379 password:

这样做的核心思路是:若依的默认Redis功能继续走Spring Data Redis的配置,Redisson走自己的连接池,二者共用同一个Redis实例,但互不干扰。如果你的Redis里同时存在若依存储的业务数据和Redisson的分布式数据,也能正常读写,因为键空间是分开设计的。

注意:如果Redis服务设置了密码但密码为空字符串,传null不代表无密码,Redisson对密码的处理逻辑是“传了密码就启用AUTH”。所以一定要用条件判断区分是否配置了密码。

3.4 保留若依原有RedisCache,不动的理由

很多人在集成Redisson之后的第一反应是:把若依的RedisCache类改成Redisson实现,这样所有调用方都统一走Redisson。我的建议是别这么做。

原因有两方面:

第一,改造成本高且影响面大。若依框架里RedisCache不仅被后端业务代码使用,还被Spring Security的权限模块、验证码模块等大量内部代码引用。一旦替换,所有涉及的地方都要回归测试,风险不可控。

第二,没有意义。若依的RedisCache做的是常规KV缓存,这种场景走Spring Data Redis完全够用,性能上两者差距不大。Redisson的价值在分布式工具层面,不是用来替代单机KV缓存的。新业务场景直接用Redisson,旧功能维持不动,改造成本最低且收益最大化。

4. 序列化方案:一个90%的人会遇到的天坑

4.1 若依默认的JDK序列化问题

若依框架的RedisTemplate在配置类RedisConfig里面设置了序列化器:

redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());

什么意思呢?key用字符串存,value用Java原生序列化后二进制数组存。这种方式有几个问题:

第一个,跨语言不友好。如果将来有其他技术栈(例如Python、Go)的服务要读同样的数据,JDK序列化的二进制格式根本没法解析。

第二个,数据膨胀严重。JDK序列化会在二进制流里写入大量的类描述信息,一个小对象序列化后比JSON大了好几倍,浪费Redis内存。

第三个,反序列化漏洞风险。JDK序列化在Java历史上出现过不少反序列化漏洞,虽然Redis场景相对封闭,但属于潜在风险点。

4.2 Redisson默认的编解码器说明

Redisson默认使用的编解码器是Kryo5Codec(特定版本默认值可能略有不同),Kryo是一种高性能序列化框架,它的特点是序列化体积小、速度快,但它的输出格式同样不是跨语言的。而且有个更麻烦的问题,Kryo不知道类定义时,反序列化需要依赖正确的类注册或类名信息,如果配置不当会报ClassNotFoundException。

在实践过程中,我会建议你在配置Redisson时显式指定序列化方案,而不是依赖默认值:

config.setCodec(new JsonJacksonCodec());

为什么选JsonJacksonCodec?

第一,JSON格式可读性好,在Redis客户端里直接能看到数据内容,排查问题方便得多。如果用了Kryo,你在Redis命令行里看到的是一堆乱码,根本没法人工排查。

第二,Jackson是Spring Boot自带的JSON库,版本兼容性有保障。

第三,跨语言友好,因为JSON是通用格式。

当然这里也有一个坑需要留意。JsonJacksonCodec在序列化时,需要目标类有默认构造函数和getter/setter方法,否则Jackson会报错。如果你在Redis里存的对象是内部类、匿名类或没有无参构造的类,反序列化就会失败。

4.3 旧数据处理策略:清扫还是兼容

引入Redisson后,Redis里旧的若依数据不会自动损坏,因为若依用了自己的序列化器,Redisson不会主动读取那些key。但如果你未来在Redisson环境下读取若依写入的数据,比如想用Redisson的RBucket读取一个若依存的对象,就会因为序列化器不同而失败。

处理这种旧数据有两种策略,简单描述一下:

  1. 清扫策略:在低峰期清理掉Redis中所有缓存数据,让系统重新积累。适合缓存可丢失的场景,比如验证码、临时token。
  2. 兼容策略:如果数据不能丢,只能分场景处理。比如若依的登录token,通过StringRedisTemplate读出来解析,走一把710的落体。但这对数据量大的项目很不现实。

我个人的实践是:先在测试环境把所有功能过一遍,确认没有数据依赖问题,然后上生产前在低峰期执行一次彻底的Redis清理。注意,这个操作一定要提前和运维确认好,Redis里如果还有别的业务数据,千万不要一把梭全清了。

下面给一个通用的RedissonConfig完整代码,带序列化配置:

@Configuration public class RedissonConfig { @Value("${redisson.host}") private String host; @Value("${redisson.port}") private int port; @Bean(destroyMethod = "shutdown") public RedissonClient redissonClient() { Config config = new Config(); config.useSingleServer() .setAddress("redis://" + host + ":" + port); config.setCodec(new JsonJacksonCodec()); return Redisson.create(config); } }

JsonJacksonCodec的包名是org.redisson.codec.JsonJacksonCodec。如果你的项目里同时存在其他Jackson依赖,注意版本不要冲突。

5. 核心功能实操:分布式锁与延迟队列

5.1 分布式锁的三种用法对比

说到引入Redisson最直接的收益,分布式锁是避不开的核心功能。下面列三种实际用法,从最基础到进阶。

第一种:手动加锁解锁

@Autowired private RedissonClient redissonClient; public void doSomething() { RLock lock = redissonClient.getLock("order:pay:" + orderId); lock.lock(30, TimeUnit.SECONDS); try { // 业务逻辑 } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); } } }

这里有个重要细节:lock.lock(30, TimeUnit.SECONDS)表示最多持锁30秒,超过自动释放。这个方式的优点是简单直观,缺点是如果忘了在finally里解锁,还是会有锁泄漏风险。

第二种:tryLock尝试获取锁

RLock lock = redissonClient.getLock("inventory:deduct"); boolean locked = false; try { locked = lock.tryLock(3, 10, TimeUnit.SECONDS); if (locked) { // 拿到锁,执行扣库存 } else { // 没拿到锁,返回操作频繁 } } finally { if (locked && lock.isHeldByCurrentThread()) { lock.unlock(); } }

tryLock的经典使用方式。第一个参数是等待时间,第二个参数是锁自动释放时间。这种模式适合秒杀场景,拿不到锁直接返回活动太火爆,避免大量线程阻塞等待。

第三种:注解方式(推荐)

@Lock(value = "order:pay:", leaseTime = 30) public void payOrder(Long orderId) { // 业务逻辑 }

Redisson Spring Boot Starter支持@Lock注解,通过AOP自动加锁和解锁,代码最简洁,且不需要手动处理异常。这个注解在接口级别使用非常顺手。如果你的项目还没用到注解式锁,强烈建议试着引入,代码质量会有一个明显提升。

实际项目中我推荐能用注解就不手写锁,因为手写锁容易在业务代码异常、事务回滚等边缘条件下出错,而注解方式的锁生命周期由框架统一管理,出错概率要低很多。

5.2 看门狗机制:自动续期到底怎么起作用

Redisson分布式锁里最有名也最容易被人误解的是“看门狗”机制。很多人以为它是JVM里的一个守护线程,实际上Redisson在获取锁之后,会定时(默认每10秒)检查锁是否还持有,如果持有且剩余过期时间还在,就自动续期到30秒。这个续期动作一直持续到锁被显式释放,或者持有锁的JVM进程崩溃,看门狗也随之消失,锁最终在超时后自动释放。

这对开发者意味着什么?意味着你不用担心业务执行时间超过锁的超时时间而导致的锁提前释放问题。锁不会因为业务慢而被别人抢走,也不会因为进程崩溃而永久不释放。这个机制是手写SETNX方案根本不可能实现的,也是Redisson分布式锁被广泛使用的最重要原因之一。

需要特别提示的是:如果你在加锁时显式指定了leaseTime(例如lock.lock(30, TimeUnit.SECONDS)),看门狗机制就不再生效,因为这句话告诉Redisson“我就要锁固定30秒,不用你自动续期”。这本身没错,但你要清楚,一旦业务执行超过30秒,锁一定会被释放。如果业务耗时不可控,建议不加leaseTime参数,让看门狗接管。

5.3 延迟队列:订单超时场景的优雅解法

分布式锁之外,另一个值得优先体验的功能是延迟队列。在订单系统里,经常有这种需求:下单30分钟未支付,自动取消订单。传统方案是定时任务批量扫描订单表,根据下单时间和当前时间差判断是否超时,这个方案有三个缺点:一是扫描频率影响时效性,二是数据量大了数据库压力大,三是存在重复处理问题。

Redisson的延迟队列实现起来是这样的:

// 生产者:下单后把订单加入延迟队列 RBlockingQueue<OrderMessage> blockingQueue = redissonClient.getBlockingQueue("order:delay:queue"); RDelayedQueue<OrderMessage> delayedQueue = redissonClient.getDelayedQueue(blockingQueue); OrderMessage message = new OrderMessage(); message.setOrderId(orderId); delayedQueue.offer(message, 30, TimeUnit.MINUTES); delayedQueue.destroy();

消费者端:

// 消费者:循环从队列里拿到期消息 RBlockingQueue<OrderMessage> blockingQueue = redissonClient.getBlockingQueue("order:delay:queue"); while (true) { OrderMessage message = blockingQueue.take(); // 检查订单是否已支付,未支付则关闭 }

这个方案有两个关键优势:

第一,不依赖数据库轮询,时效性由Redis的ZSet底层结构精确控制。

第二,消费者只需要循环take(),消息到期后自动从延迟队列转移到阻塞队列,不需要自己判断时间。

实际使用中有几个小细节值得注意:

  • delayedQueue是围绕blockingQueue创建的,取消息要通过blockingQueue。
  • 生产者创建RDelayedQueue后,用完建议destroy(),否则会在Redis里留下一个后台任务,增加无谓的负担。
  • 消费者如果是微服务多实例部署,要注意均衡部署问题,避免多个实例同时从同一个阻塞队列取消息导致消息分配不均。
  • 生产环境需要保证消费者进程的健壮性,最好做一个消费者监听器,统一管理启动、异常处理和优雅退出。

5.4 限流器:接口防刷的另一种解法

除了锁和队列,Redisson的限流器也是一种很实用的工具。在若依框架里,默认没有限流组件,如果要限制某个接口的访问频率,通常得自己写拦截器。用Redisson则非常简单:

@Autowired private RedissonClient redissonClient; public boolean isAllowed(String userId, int limit, int windowSeconds) { RRateLimiter rateLimiter = redissonClient.getRateLimiter("rate:user:" + userId); rateLimiter.trySetRate(RateType.OVERALL, limit, windowSeconds, RateIntervalUnit.SECONDS); return rateLimiter.tryAcquire(); }

这套方案的厉害之处在于:它不只是做简单的计数器,底层实现了令牌桶算法,突发流量下能平滑处理。比如设置每秒允许10次请求,即使一瞬间涌入100个请求,也只有10个能立即通过,其余的被平滑到后续的秒级窗口内处理。

这里有个细节要注意:trySetRate只会在第一次创建限流器时生效,如果后续调用时传入了不同的参数,不会覆盖原有设置。所以限流参数应该在工具类初始化时统一配置,或者确保每次调用参数一致。

6. 实战集成过程中的坑与排查经验

6.1 自动配置失效:RedissonClient没有被Spring管理

新人在引入redisson-spring-boot-starter后,经常遇到明明加了依赖,启动也正常,但@Autowired RedissonClient却报注入失败。原因多半是自动配置没生效。

排查思路如下:

先看启动类是否被Spring Boot扫描到。默认情况下,Spring Boot会扫描启动类所在包及其子包,如果RedissonConfig放在其他包路径下,就需要在启动类的@ComponentScan或@MapperScan注解里显式指定扫描范围。

另一种可能是你手动创建了RedissonClient的@Bean,但又同时保留了Starter的自动配置。两个同类型的Bean出现,Spring Boot会根据优先级选择,如果自动配置的Bean抢先注册,你的Bean就不会生效。这种情况最简单的解法就是:既然用了Starter就不要自己写RedissonConfig,二选一即可。

6.2 启动时连接超时但项目却能正常跑

这个问题特别有迷惑性。现象是项目启动时日志里出现Redisson连接Redis超时的警告,但应用最后还是起来了,业务也没明显异常。

根本原因是Redisson默认在启动时会预创建初始连接,如果Redis负载较高或者网络抖动,连接初始化就会超时。但Spring的Bean创建不会因此失败,因为Redisson的连接订阅是异步的,后续执行操作时会自动重新连接。

解决思路是调大连接超时时间和重试次数。参考配置前面已经给过,setConnectTimeout(5000)配合setRetryAttempts(5)基本能解决。另外要检查Redis服务器的maxclients配置,如果连接数已经跑满,新的连接必然排队超时。

6.3 分布式锁偶尔失效:是时钟跳跃还是主从切换

如果你发现分布式锁在极端情况下失效了,比如多个线程同时进入了临界区,优先排查Redis的主从架构。

Redisson的单节点锁模式在Redis主从场景下有个经典问题:如果Master节点写入锁成功,但还没同步到Slave节点时Master宕机,Slave被提升为新Master,此时锁信息已经丢了,其他线程就可以重新拿到锁。这是CAP理论在分布式锁场景下的客观限制。

解决方式有两个:

第一,使用Redisson的RedLock多节点锁。向多个独立Redis节点同时加锁,大多数节点成功才算成功锁。

第二,如果你的业务场景对锁的可靠性要求不是极高(比如只是防止重复提交),单节点锁加上业务层的幂等校验也够用了。只有和资金、库存强相关,才建议引入RedLock。

需要明确的是:RedLock本身也有争议,但它是Redisson官方提供的最强一致性方案,比单节点锁要可靠得多。

6.4 事务和锁的先后顺序:过早持锁反而无效

这是个实战经验,很容易被忽略。在Spring里,事务的提交是在方法返回后、由AOP的afterReturning触发的。如果你在方法内部先加了锁,然后在锁内执行数据库操作,这个事务是在锁释放之后才提交的。

这就产生了严重问题:A线程释放锁后,事务还没提交;B线程立刻拿到锁,读到的是A还没提交的数据,产生脏读。这等于锁白加了。

正确方式强依赖事务边界和锁边界的对齐。具体来说:

  1. 把事务放在外层,锁放在内层,这样事务提交发生在锁释放之前。
  2. 或者更推荐的方式:用编程式事务,在锁内部主动提交事务,然后再释放锁。

举一个实际中的代码调整:

// 错误示例:锁在外层,事务在内层 @Transactional public void handleOrder() { RLock lock = redissonClient.getLock("order:" + orderId); lock.lock(); try { // 数据库操作,但此时事务提交晚于锁释放 } finally { lock.unlock(); } }

锁释放了,事务可能还没提交。这个顺序要是用错了,并发问题排查起来极其隐蔽。

6.5 Redisson和Spring Cache注解共存问题

若依框架本身用@Cacheable是走Spring Cache抽象层,底层如果绑定的是RedisCacheManager,那么你引入Redisson后,@Cacheable注解的缓存数据默认还是由Spring Data Redis写入。很多人误以为引入Redisson后,Spring Cache就会自动走Redisson,这是错误的。

如果你希望@Cacheable也走Redisson,需要手动定义RedisCacheManager并将CacheManager的底层换成Redisson的RedissonSpringCacheManager:

@Bean public CacheManager cacheManager(RedissonClient redissonClient) { return new RedissonSpringCacheManager(redissonClient); }

这个配置只影响Spring Cache注解的缓存行为,不影响若依的RedisCache类。如果你的业务代码没有大量使用Spring Cache注解,建议不要动这块,保持若依默认即可。

7. 改造完成后如何验证

7.1 功能回归清单

集成完成后,不能只测Redisson的新功能,还要确认若依原有功能没有被破坏。建议按下面的清单跑一遍:

  • 登录流程:验证码获取和校验是否正常。若依的验证码存储走的是RedisCache,重点确认设置和读取的key和value格式没有被Redisson影响。
  • 登录token刷新:用户登录后token存入Redis,多次请求刷新token,确认token不会因为序列化器问题导致丢失或校验失败。
  • 字典缓存:若依的字典数据有缓存机制,确认缓存命中后返回的数据结构正常。核心点是确认RedisCache的key在Redis中仍然存在且可读。
  • 权限缓存:Spring Security的权限信息缓存,确认权限变更后能正常刷新,没有缓存穿透问题。

如果这些功能都正常,说明你的改造是成功的,没有引入破坏性影响。

7.2 Redisson功能验证

验证分布式锁时,比较简单的方式是写一个测试接口,循环加锁累加计数,然后用多线程并发请求,观察计数是否正确。如果不正确,优先怀疑锁配置问题。

验证延迟队列时,可以先设置一个10秒的延迟,观察消费者是否在10秒后收到消息,同时确认消息只被消费一次。

验证限流器时,用一个循环快速请求接口,观察超出阈值的请求是否被拒绝。

7.3 性能对比与连接池监控

集成完成后,建议至少做一次Redis连接数的对比观察。若依默认Lettuce连接池配置可能比较保守,Redisson的连接池如果开得比较大,连接数会明显上升。你需要观察Redis服务器端client list的连接数是否在合理范围内。

如果连接数飙升明显(比如超过1000),检查Redisson的连接池配置是否过大,适当调低connectionPoolSize和connectionMinimumIdleSize。同时注意,Redisson的看门狗机制会保持一批空闲连接,这是正常现象,不是连接泄漏。

8. 维护与扩展建议

8.1 将Redisson操作封装为独立工具类

直接在业务代码里注入RedissonClient然后操作,虽然可行,但随着调用点越来越多,后期维护成本会上升。建议封装一个统一工具类,比如RedissonService,把锁操作、队列操作、限流操作都封装成方法。

举个例子:

@Service public class RedissonService { private final RedissonClient redissonClient; public RedissonService(RedissonClient redissonClient) { this.redissonClient = redissonClient; } public void executeWithLock(String lockKey, long waitTime, long leaseTime, Runnable action) { RLock lock = redissonClient.getLock(lockKey); boolean locked = false; try { locked = lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS); if (locked) { action.run(); } else { throw new RuntimeException("获取锁失败,请稍后重试"); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException("获取锁过程被中断", e); } finally { if (locked && lock.isHeldByCurrentThread()) { lock.unlock(); } } } }

这样业务代码就简化为:

redissonService.executeWithLock("order:" + orderId, 3, 30, () -> { // 处理订单逻辑 });

好处有两个:一是锁的获取、释放逻辑收敛到一处,不会在业务代码里出现大量重复的lock和unlock;二是后续如果要调整锁参数或切换锁模式,只改一个类就行。

8.2 配置中心化:不要硬编码连接参数

多环境部署时(开发、测试、生产),Redis地址和密码都不一样。如果RedissonConfig里直接硬编码这些值,每次发布都要改代码重新打包,非常痛苦。

建议把Redis连接参数纳入配置中心统一管理,比如Nacos或Spring Cloud Config,所有服务实例从配置中心拉取参数,环境切换只需修改配置中心的配置,不需要重新构建镜像。如果你的项目还没有配置中心,那么在application-{profile}.yml里按环境配置好不同的地址和密码,比硬编码要灵活得多。

8.3 监控告警:锁异常不能只靠日志

Redisson集成后,建议同步接入Redis的监控告警。至少监控三个指标:Redis内存使用率、连接数、慢查询日志。锁异常往往不会直接报错,而是以“业务逻辑进入异常分支”的形式出现,比如获取锁失败后业务返回提示信息。如果没有监控提前发现,可能导致业务在用户侧表现为功能异常。

可以打的告警包括:

  • Redis内存超过80%:需要清理数据或扩容。
  • 连接数超过设定阈值:检查是否有连接泄漏。
  • 锁等待时间过长:说明存在高并发竞争,需要优化业务逻辑或调整锁粒度。

这些告警可以通过Spring Boot Actuator暴露Redis连接池指标,然后配合监控系统采集告警。

9. 最后的几个小技巧

声明一下,这是我在多个项目里实际摸索出来的经验,不一定适用所有场景,但大概率能帮你少踩几次坑。

第一,Redisson的依赖包体积不小,如果你的项目以轻量化为目标,可以考虑只引入核心包而不是Starter,手动管理Bean配置。但代价是少了一些自动配置的便利,自己权衡。

第二,所有锁的key,建议统一加业务前缀,比如order:pay:、inventory:deduct:。一个项目里如果存在多个业务模块共用Redis,前缀是防冲突最有效的手段。

第三,锁的粒度尽量细。能用订单号做锁key,就不要用用户ID做锁key;能用用户ID就不要用全局锁。锁粒度越粗,并发能力越差,这个道理在分布式场景下会被放大。

第四,如果Redis是集群部署,Redisson的useClusterServers()配置和单节点的配置差异不小,连接地址格式要改为redis://host1:6379,redis://host2:6379这种形式。在动手改造前,先确认你的Redis是单机还是集群模式,这直接影响配置代码的写法。

第五,别忘了在代码里做好Redis不可用的降级处理。即使Redisson连接超时或Redis宕机,也要保证主流程能正常返回,而不是直接把异常抛给用户。分布式锁获取失败的降级策略可以是“快速失败返回提示”,限流器在Redis不可用时应直接放行还是拒绝,要根据业务性质决定。这些都是生产环境必须考虑的细节,否则Redis一抖,整个业务全挂。

这次若依Spring Boot 3和Redisson的集成过程,看起来只是加了一个依赖,实际上涉及了序列化方案的选择、配置体系的协调、锁机制的引入、以及一系列并发场景的边缘情况。把这些都走通之后,你手里的若依项目在分布式能力上会明显上了一个台阶。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/16 3:26:08

数学建模国赛2020C题信贷决策高分论文写作与代码实战指南

简介&#xff1a;这份资源是2020年全国大学生数学建模竞赛C题的一等奖获奖论文及配套完整实现&#xff0c;面向正在备赛国赛、希望提升建模实战能力的高校学生与指导教师。包内共13个文件&#xff0c;以6个xlsx数据表、4个m脚本、2个py程序与1份pdf论文为主&#xff0c;压缩包约…

作者头像 李华
网站建设 2026/10/16 3:26:07

Nacos对接人大金仓:国产数据库深度适配实战指南

简介&#xff1a;本资源是一套面向Java微服务开发者与国产化数据库运维人员的Nacos人大金仓数据源集成实践方案&#xff0c;聚焦国产数据库在配置中心场景下的适配落地&#xff0c;解决服务治理中动态管理金仓数据源的核心痛点。压缩包共15个文件&#xff0c;含4个关键SQL脚本&…

作者头像 李华
网站建设 2026/10/16 3:25:51

Forward 2.71 安装避坑指南:从环境配置到跑通验证

简介&#xff1a;Forward 2.71 安装程序面向网络管理员、IT 运维人员及网络技术学习者&#xff0c;用于网络数据包捕获、协议解析与故障排查&#xff0c;可帮助定位高延迟、丢包、带宽占用异常等问题。压缩包共 1194 个文件&#xff0c;约 69.78MB&#xff0c;以 dll、exe、hlp…

作者头像 李华
网站建设 2026/10/16 3:25:43

Excel配置表转protobuf二进制:类型安全与跨端共用的工程实践

简介&#xff1a;面向Unity游戏开发者的xls数据转换工具&#xff0c;基于Google Protocol Buffers序列化协议&#xff0c;借助其体积小、速度快、跨平台等特性&#xff0c;将Excel表格解析并生成C#类文件&#xff0c;使策划人员可直接修改xls调整角色属性、物品参数等游戏数值&…

作者头像 李华
网站建设 2026/10/16 3:25:19

Android自绘树形控件:递归布局与显式栈清除的实战方案

简介&#xff1a;面向Android初中级开发者的思维导图实现项目&#xff0c;围绕递归算法与堆栈数据结构解决节点绘制与清除问题&#xff0c;覆盖自定义View绘图、触摸事件交互、树形数据模型、布局管理、动画效果及性能优化等核心知识点。项目为完整Android Studio工程&#xff…

作者头像 李华
网站建设 2026/10/16 3:24:47

告别AI信息焦虑:OpenClaw如何用每日一份简报帮你高效获取全网AI资讯

每天早上醒来第一件事&#xff0c;就是摸手机刷各种AI资讯。公众号推文、行业群消息、技术论坛热帖、大佬的推文转发——一口气刷下来半小时过去&#xff0c;真正记住的没几条&#xff0c;焦虑倒是增加了不少&#xff1a;总觉得自己错过了什么重要信息&#xff0c;总怕别人都知…

作者头像 李华