☰
Redisson操作Redis实战:分布式锁、延迟队列与限流
2026/10/4 1:26:47 网站建设 项目流程

提到用Java操作Redis,很多人的第一反应是Jedis或者Spring Data Redis。但一旦你的业务里出现“分布式锁”“分布式限流”“可靠延迟队列”这类字眼,光靠这些底层客户端就有点吃力了——你得自己拼Lua脚本、自己处理续期、自己封装重试逻辑,踩坑踩到怀疑人生。Redisson就是为这个场景而生的:它以Redis为基础,把锁、集合、队列、信号量、发布订阅、限流器这些分布式场景里的常见组件,全部封装成了Java API,让你感觉像是在操作一个本地对象,底层其实全部跑在Redis上。这篇是“Redis入门到精通”系列的第十一篇,专门聊Redisson操作Redis的实战玩法,内容包括环境搭建、分布式锁的完整原理与手写对比、常用高级组件、Spring Cache整合、序列化问题以及我实际项目里踩过的坑,适合有一定Redis基础、又想把Redisson用进真实项目的同学。

1. 为什么是Redisson:解决Jedis不完全够用的那部分需求

1.1 三代客户端演进,Redisson为什么是最终选择

Redis官方最早推荐的Java客户端是Jedis,特点是轻、直连、API简单,很多人的第一段Redis代码都是拿Jedis写的。Jedis的问题在于它只是一个“连接器”:你发命令、收结果,剩下的并发控制、分布式协调逻辑都要自己写。比如一个最简单的分布式锁,用Jedis至少得写SET NX、EXPIRE、Lua释放脚本三套逻辑,还要考虑原子性、误删锁、主从同步延迟这些边界问题。

后来Spring Boot默认集成了Lettuce,性能好、支持异步,但它也还停留在“命令客户端”这个层级。真正的业务痛点不是“怎么发送一个Redis命令”,而是“怎么用Redis解决分布式场景下的业务问题”。Redis官方后来也意识到这一点,从3.0版本开始重点扶持Redisson这个项目。Redisson的定位明显更高一层:它提供的是RLock、RMap、RQueue、RTopic、RRateLimiter这样的分布式组件,开箱即用,内部自己处理线程安全、连接管理、重试机制和底层数据结构映射。

实际用下来,我的感受是:Jedis像螺丝刀,Lettuce像电动螺丝刀,Redisson则像一套已经组装好的工具箱,里面连钻孔定位器都给你配好了。你自己拧螺丝固然可以,但工程量大了以后效率差距非常明显。

1.2 Redisson的核心理念:把分布式能力“本地化”

Redisson的设计哲学很有意思,它把Redis的数据结构尽量往Java集合框架上靠。你会发现Redisson里有RMap对应Java的Map、RSet对应Set、RScoredSortedSet对应带排序的Set、RBlockingQueue对应BlockingQueue。这带来的直接收益是:你原来写单机并发代码用的API,几乎可以平移到分布式环境里。

举个例子,单机环境下你写一个ConcurrentHashMap就能完成多线程数据共享,分布式环境下就崩溃了。换成Redisson的RMap,代码长得很像,但数据实际存到了Redis里,多个应用节点共享的是同一份数据。这种“低成本迁移”是Redisson能火起来的根本原因——它不逼你改变编程习惯,而是把分布式复杂度藏在框架内部。

这种设计也决定了Redisson适合什么人用:不想关心底层Redis命令细节、希望把精力放在业务逻辑上的Java开发。反过来,如果你对分布式锁原理一点都不懂就上Redisson,也很容易踩坑,因为框架能帮你干活,但不会帮你理解“这个锁为什么不能解决所有一致性难题”。所以这篇文章我会把原理部分也展开讲。

1.3 Redisson组件全景图

在进入实操之前,先看一遍Redisson的组件家族,对后面阅读很有帮助。Redisson官方文档把能力分成了几大类:

  • 分布式锁类:RLock、公平锁FairLock、读写锁ReadWriteLock、联锁MultiLock、红锁RedLock。
  • 分布式集合类:RMap、RSet、RList、RQueue、RBlockingQueue、RDelayedQueue、RLexSortedSet等。
  • 分布式同步器类:RSemaphore、RCountDownLatch、RPermitExpirableSemaphore。
  • 分布式发布订阅:RTopic、RPatternTopic。
  • 分布式限流:RRateLimiter。
  • 分布式原子类:RAtomicLong、RAtomicDouble。
  • 分布式服务:RExecutorService、RScheduledExecutorService,可以在Redis集群上编排远程执行任务。

这些组件不是每个都会用到,但锁、队列、并发控制、缓存这四处是高频场景。下面的章节我会按“能直接抄走”的标准来写,保证你读完能上手。

2. 快速接入:依赖、配置文件与连接模式

2.1 依赖引入与版本选择

最省事的做法是引入Redisson的Spring Boot Starter。这里有一个非常关键的版本选择问题:Redisson 3.x的官方Starter内部依赖了Spring Boot版本。如果你的项目是Spring Boot 2.x,建议用redisson-spring-boot-starter的3.16.x~3.19.x线;如果是Spring Boot 3.x,建议用3.23.x及以上版本,或者直接切到Redisson 4.x线。我去年把一个老项目从Spring Boot 2.5升级到2.7,Redisson还锁在3.16.0,跑得没问题;另一个新项目用的Spring Boot 3.2,Redisson直接上3.27.2,也没有兼容性问题。总之别随手下最新版,先去Maven仓库看一眼release notes再定。

Maven依赖写法:

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

如果你不用Spring Boot,只依赖核心包也行:

<dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.27.2</version> </dependency>

这里有个小经验:如果项目里同时存在Spring Data Redis和Redisson,两者可以共存,不会冲突。Redisson的Starter只负责创建RedissonClient,不会动Spring Data Redis的自动配置。

2.2 单机、主从、哨兵、集群的连接配置差异

Redisson支持Redis全拓扑模式,这大大减少了运维层的割裂感。以最常用的YAML配置为例,单机模式直接指向地址和端口:

singleServerConfig: address: "redis://127.0.0.1:6379" password: null database: 0 connectionPoolSize: 64 connectionMinimumIdleSize: 16 idleConnectionTimeout: 10000 connectTimeout: 10000 timeout: 3000

注意password不能写成空字符串,如果Redis没有密码,直接写null,否则Redisson会尝试AUTH导致连接失败。这个坑我踩过一次,当时配置了“password: ''”,结果启动就报ERR Client sent AUTH, but no password is set。

主从配置则要指定master与slave,同时可以设置readMode走从库读:

masterSlaveServersConfig: masterAddress: "redis://127.0.0.1:6379" slaveAddresses: - "redis://127.0.0.1:6380" - "redis://127.0.0.1:6381" readMode: "SLAVE" loadBalancer: !<org.redisson.connection.balancer.RoundRobinLoadBalancer> {}

哨兵模式则需要指向sentinel地址:

sentinelServersConfig: sentinelAddresses: - "redis://127.0.0.1:26379" masterName: "mymaster" readMode: "MASTER"

集群模式更简单,只要列出所有节点地址:

clusterServersConfig: nodeAddresses: - "redis://127.0.0.1:7001" - "redis://127.0.0.1:7002" - "redis://127.0.0.1:7003" scanInterval: 1000

生产环境我一般建议把timeout和connectTimeout调低到3000~5000毫秒,避免Redis故障时业务线程大面积卡死。connectionPoolSize则以业务并发数为参考,通常64够用,没必要开很大。

2.3 Spring Boot集成与自定义Config Bean

使用Starter后,默认会读取spring.redis配置。如果你不想用YAML描述Redisson专属配置,也可以直接注册一个RedissonClient的Bean。这样便于在代码里动态拼接节点地址,适合配置中心下发场景。

@Configuration public class RedissonConfig { @Value("${redis.address:redis://127.0.0.1:6379}") private String address; @Bean(destroyMethod = "shutdown") public RedissonClient redissonClient() { Config config = new Config(); config.useSingleServer() .setAddress(address) .setConnectionPoolSize(64) .setConnectionMinimumIdleSize(16); return Redisson.create(config); } }

注意destroyMethod必须写成shutdown,否则Spring容器关闭时不会释放Redisson内部线程池和连接池,容易造成长时间运行的进程释放不了句柄。实际项目中我还见过有人把RedissonClient注入到Service里,项目停机时Tomcat先关了,然后Redisson的watch dog线程还在跑,日志一直刷异常。自定义Bean并指定shutdown后,这种情况基本能避免。

3. 分布式锁实战:加锁、续期、释放与锁类型选择

3.1 手写分布式锁的经典缺陷:别自己造轮子

很多“Redis分布式锁”教程会教你先用SETNX实现加锁,再用EXPIRE设置过期时间。这种写法最早期版本有很大问题:SETNX和EXPIRE不是原子操作,如果设置完SETNX、进程刚好挂掉,锁永远不释放。后来演进成一条原子命令:

SET lockKey lockValue NX PX 30000

这条命令可以解决原子性问题,但又带来一个问题:“锁的value到底存什么?”如果所有线程都用同一个value,线程A释放锁时可能把线程B的锁给释放掉。所以value需要存一个全局唯一标识,释放锁的时候先比较value是否匹配,只有匹配才删除。这个比较和删除又必须是原子的,只能用Lua脚本。

到了这一步,你已经发现手写分布式锁要考虑的点实在太多:加锁原子性、释放原子性、锁续期、重入、公平性、防误删、主从切换时的锁丢失。如果这是面试题,你能把上述逻辑说清楚已经不错了;如果是生产代码,我更建议直接用Redisson的RLock,因为这些问题框架全都内置了。

3.2 watch dog自动续期原理:30秒的锁,为什么不会提前失效

Redisson最惊艳的设计就是watch dog看门狗机制。默认情况下,RLock加锁成功后,如果没有指定leaseTime(锁租赁时间),Redisson会给这个锁一个默认30秒的过期时间,然后启动一个后台定时任务,每10秒执行一次续期,把锁的过期时间重新拉回30秒。这个续期过程是通过Lua脚本完成的:先判断锁是否还是当前线程持有,是就重新设置expire。

RLock lock = redissonClient.getLock("order:pay:1001"); try { boolean locked = lock.tryLock(3, TimeUnit.SECONDS); if (locked) { // 业务逻辑,执行时间可以超过30秒 } } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); } }

上面的tryLock只传了一个waitTime,没有传leaseTime,所以watch dog才会生效。它的存在意义是:当你的业务方法因为慢SQL、第三方接口超时等原因执行得比预期久时,锁不会因为到期而自动释放,从而避免并发线程同时进入临界区。

注意一个小细节:lock.unlock()必须在finally里调用,并且最好先判断isHeldByCurrentThread()。Redisson的锁是可重入的,同一个线程多次加锁后,必须对应调用相同次数的unlock,否则锁不会被真正释放。这是个特别容易忽略的坑。

3.3 tryLock与lock的选择:公平锁、读写锁怎么用

Redisson的RLock继承自JUC的Lock接口,基本用法和ReentrantLock几乎一致。lock()是阻塞加锁,拿不到锁就一直阻塞,一般不推荐在分布式环境下使用,因为会长时间占用线程;tryLock(waitTime, leaseTime, TimeUnit)限制等待时间,拿不到就快速失败,更适合大多数业务场景。

如果你需要锁竞争按照申请顺序公平分配,可以用FairLock:

RLock fairLock = redissonClient.getFairLock("anyFairLock");

公平锁底层通过Redis的ZSet记录请求顺序,性能比普通锁差。实测在高并发抢锁场景下,公平锁的吞吐量大概是普通锁的60%~70%,所以除非业务真的有顺序要求,否则不建议开。

读写锁也很常用,适合读多写少的业务,例如商品详情配置:

RReadWriteLock rwLock = redissonClient.getReadWriteLock("product:info:1001"); RLock readLock = rwLock.readLock(); RLock writeLock = rwLock.writeLock();

读锁和读锁之间不互斥,多个线程可以同时读;写锁与读锁、写锁与写锁之间互斥。这样在设计缓存更新策略时,读接口加读锁、写接口加写锁,能显著降低无谓的互斥等待。

3.4 Redis集群下锁可靠吗?MultiLock的现实处境

这里不得不提一个被面试官反复追问的话题:在主从复制架构下,如果master节点突然宕机,锁数据还没来得及同步到slave节点,新master上锁就丢了,两个线程可能同时拿到同一把锁,分布式锁直接失效。Redisson官方为了解决这个问题,提供了MultiLock,也就是把锁同时加在多个独立Redis实例上,只有全部加锁成功才算拿到锁。

RLock lock1 = redissonClient1.getLock("lockKey"); RLock lock2 = redissonClient2.getLock("lockKey"); RLock lock3 = redissonClient3.getLock("lockKey"); RLock multiLock = redissonClient1.getMultiLock(lock1, lock2, lock3);

但说实话,在我的实际项目接触来看,绝大多数公司不会为了几把分布式锁去部署5个相互独立的Redis实例。这个成本太高了。更常见的做法是直接用主从或哨兵架构下的RLock,接受极端情况下的锁丢失风险,同时在业务层做幂等兜底。这本质上是一种工程权衡,面试时能讲清楚RedLock的原理就够了,生产选择上绝大多数还是单点RLock+业务幂等。

4. 不只是锁:分布式集合、队列、发布订阅与限流

4.1 RMap/RSet/RList:把Redis当Java内存集合用

RLock是Redisson的明星功能,但Redisson并不是只有锁。RMap是很实用的分布式Map,它比直接用Hash操作更顺手,因为方法名和Java的Map完全一致:

RMap<String, UserInfo> userMap = redissonClient.getMap("cache:user"); userMap.put("1001", user); UserInfo user = userMap.get("1001"); userMap.remove("1001");

默认情况下RMap不会自动过期,如果你希望每个key单独过期,应该使用RMapCache:

RMapCache<String, UserInfo> userCache = redissonClient.getMapCache("cache:user"); userCache.put("1001", user, 30, TimeUnit.MINUTES, 10, TimeUnit.MINUTES);

第四个参数是TTL,整体30分钟过期;第五个参数是maxIdleTime,也就是10分钟内没有被读取就会删除。这两个过期策略在“登录态”这类场景很实用:用户连续操作一直保活,停止操作10分钟后失效。

RSet和RList使用方式类似,底层分别对应Redis的Set和List,天然支持分布式去重。RScoredSortedSet则对应ZSet,适合做排行榜,但要注意的是Redisson的ZSet元素的score是Double类型,大金额场景下精度有限,需要提前转换单位。

4.2 RBlockingQueue与RDelayedQueue:可靠延迟队列

分布式队列是订单超时未支付、任务延迟处理等场景的基础设施。Redisson的RBlockingQueue实现了JUC的BlockingQueue接口,消费者可以阻塞式获取消息:

RBlockingQueue<String> queue = redissonClient.getBlockingQueue("queue:order"); // 生产者 queue.offer(orderId); // 消费者 String orderId = queue.take();

RDelayedQueue则更高级,它给队列里的元素设置延迟时间,到期后元素才进入真正可消费的队列。实现一个订单超时关闭功能可以这么写:

RBlockingQueue<String> blockingQueue = redissonClient.getBlockingQueue("order:timeout"); RDelayedQueue<String> delayedQueue = redissonClient.getDelayedQueue(blockingQueue); delayedQueue.offer(orderId, 30, TimeUnit.MINUTES);

消费者通过blockingQueue.take()拿到元素,说明订单已经超时。这里最需要注意的一点是:RDelayedQueue和RBlockingQueue是同一个Redis key关联的,生产者在向delayedQueueoffer数据后,消费者应当从blockingQueue里take。如果搞混了,消息就取不出来。

4.3 RTopic与RPatternTopic:发布订阅的注意事项

Redisson的RTopic是对Redis Pub/Sub的封装。Redis的Pub/Sub结构是“即发即失”:如果消费者不在线,消息直接丢失,而且消息不会持久化。所以RTopic只适用于通知类、实时类消息,例如配置变更、缓存刷新提醒,不适合作为业务消息队列。

基本用法:

RTopic topic = redissonClient.getTopic("channel:config:change"); topic.addListener(String.class, (channel, msg) -> { // 收到频道消息 }); topic.publish("reload");

这里序列化坑比较多。addListener里的Class参数,决定了Redisson用什么解码器反序列化消息体。如果发布端使用JSON序列化,监听端就必须指定对应的Class类型并使用相同的Codec,否则会抛ClassCastException或反序列化异常。后面第6章我会专门讲Codec问题。

RPatternTopic支持通配符订阅,比如订阅“order:*”开头的所有频道,适合业务频道按订单号拆分的小型通知场景。

4.4 RRateLimiter:基于令牌桶的分布式限流

限流是一个老话题。单机限流可以用Guava的RateLimiter,分布式限流就必须把计量状态放到Redis里。Redisson提供的RRateLimiter底层使用令牌桶算法,API比手动写Lua脚本友好太多:

RRateLimiter limiter = redissonClient.getRateLimiter("rate:api:query"); // 每秒放10个令牌,最多缓存10个 limiter.trySetRate(RateType.OVERALL, 10, 1, RateIntervalUnit.SECONDS); if (limiter.tryAcquire()) { // 通过 } else { // 拒绝 }

RateType.PER_CLIENT表示每个客户端单独计数,OVERALL表示所有客户端共享同一个令牌桶。日常接口限流建议用OVERALL,防止某个并发调用方把整个服务的配额全吃光。还有一点需要提醒:trySetRate并不是每次调用都要执行,它是幂等配置的意思,实际调用一次后,后续再调用不会重置已消耗的令牌。但对限流器变化敏感的场景,建议在启动时预置好RateLimit配置,而不是运行中频繁修改。

5. Spring Cache与Redisson整合:注解式缓存落地

5.1 用RedissonSpringCacheManager管理缓存TTL

Redisson官方提供了Spring Cache的集成方案:RedissonSpringCacheManager。配置好缓存管理器后,你可以直接使用Spring的@Cacheable、@CacheEvict注解,而Cache底层则落到Redis上。这个组合比Spring Data Redis默认的缓存方案更好的一点是:Redisson天然支持value过期和LRU淘汰等策略。

基础配置如下:

@Bean public CacheManager cacheManager(RedissonClient redissonClient) { Map<String, CacheConfig> config = new HashMap<>(); // "userCache"缓存,TTL 30分钟 config.put("userCache", new CacheConfig(30 * 60 * 1000, 10 * 60 * 1000)); return new RedissonSpringCacheManager(redissonClient, config); }

CacheConfig构造函数里的两个参数,第一个是TTL,单位毫秒,第二个是maxIdleTime。如果只传一个参数,另一个可以给0,表示不限制。这里有个经验:Redis缓存最容易出现的问题不是TTL太长,而是缓存Key异常膨胀。所以命名上必须带业务前缀,比如“userCache::1001”,不然排查问题时你会面对一片不可读的Key列表。

5.2 缓存与DB一致性的实战配置

Spring Cache注解本身解决不了缓存与数据库的一致性,只解决了“读写缓存”的代码复杂度。最常见的问题就是:更新了数据库,缓存还是旧值,导致用户看到脏数据。我建议在写接口里做“先更新DB,再删除缓存”,删除缓存用@CacheEvict,而不是更新缓存。为什么?因为并发场景下,先更新DB再更新缓存,如果两个请求并发执行,可能出现“后更新的DB配了早更新的缓存”这种错位;而删除缓存则简单粗暴,下次读取自然回源数据库。

@CacheEvict(value = "userCache", key = "#userId") public void updateUser(Long userId, UserUpdateDTO dto) { // 先更新数据库 userMapper.update(userId, dto); }

另外,如果服务是集群部署,还要注意一个“缓存击穿”的问题:热点缓存失效的一瞬间,所有请求同时打到数据库。Redisson官方提供了分布式锁可以和Spring Cache配合,但更简单的做法是给热点数据缓存加上逻辑过期时间,或者对空结果做短暂缓存。这部分不是Redisson的核心功能,但和“Redisson操作Redis”一起用的时候,缓存的一致性体验会好很多。

6. 序列化、性能调优与那些年踩过的坑

6.1 Codec选型:StringCodec、JacksonCodec、Kryo5Codec

Redisson的Codec负责Java对象和Redis存储数据之间的互相转换,这个选择决定了数据的可读性、兼容性和性能。Redisson默认使用Kryo5Codec,序列化体积小、速度快,但缺点很明显:数据是二进制格式,在Redis客户端里看到一堆乱码,而且对类结构变更很敏感,字段增删以后老数据可能反序列化失败。

如果你只需要用Redis存普通字符串,直接指定StringCodec:

Config config = new Config(); config.setCodec(new StringCodec()); config.useSingleServer().setAddress("redis://127.0.0.1:6379");

用StringCodec时,RMap的key和value都必须可能是String类型,适合做纯KV缓存、计数器、分布式锁key这类场景。如果你需要把对象存进去,并且希望Redis里的人眼可读,推荐JacksonCodec:

config.setCodec(new JsonJacksonCodec());

JacksonCodec会把对象序列化成JSON字符串,排查数据、对接别的系统时都非常方便。它的缺点是体积比Kryo大,性能略低。我的一般建议是:面向纯缓存且对象结构简单的场景,用JsonJacksonCodec;面向高频访问、追求极致内存占用的场景,保留默认Kryo5Codec;面向分布式锁Key或简单字符串,直接StringCodec。三种Codec不能混用,否则会出现“写入时用的是Kryo,读取时用Jackson,直接反序列化失败”这种事。

6.2 常见异常与排查速查表

我整理了一份自己在生产环境排查过程中遇到的Redisson异常速查表,可以直接收藏:

异常现象原因分析解决方案
ClassCastException读写两边用了不同的Codec统一配置Codec,并在配置中心下发时保持一致
RLock is not owned by current thread锁超时或线程不同导致unlock失败检查是否显式传了leaseTime且业务时间过长;确保unlock和lock在同一个线程
Failed to submit a listener notification taskRedisson事件线程池被占满调大threads或nettyThreads,检查是否有长时间阻塞的回调
Connect to redis server failed网络不通或连接池耗尽检查Redis地址、防火墙、timeout配置,适当调大connectionPoolSize
WrongType同一Redis key被不同类型命令写入通常是同一个key既被当String又被当List或Hash使用,规范Key命名
java.lang.ClassNotFoundException反序列化时加载不到业务类用StringCodec或JsonJacksonCodec,避免跨服务直接传Class对象
Moved异常使用了单机连接访问集群Redis改为clusterServersConfig连接集群

6.3 独家避坑心得

我个人操作Redisson过程中最深的几个体会,这里一次性分享出来。

第一,不要在锁内做远程IO或者睡眠。分布式锁的目的是保护短临界区,如果一个线程在临界区内调第三方接口等了5秒,其他所有线程都在等锁,系统吞吐量会断崖式下跌。锁的粒度能细就细,把不必要共享的操作移到锁外面。

第二,RedissonClient的初始化要发生在Spring容器启动早期。如果在@PostConstruct里就尝试注入RedissonClient做缓存预热,而Bean还没有实例化完成,容易拿到空指针。我习惯把缓存预热逻辑放在ApplicationReadyEvent事件里,确保容器完全就绪后再执行。

第三,连接池不是越大越好。我见过有人把connectionPoolSize写到512的,结果是Redis连接数瞬间打满,直接拖垮了Redis。实际经验里,业务QPS在1000附近,connectionPoolSize=64已经足够,因为Redisson是异步非阻塞连接,64条连接能撑住的并发比你想象的高得多。

第四,不要指望Redisson解决所有问题。比如RLock在主从故障切换时可能失效、RDelayedQueue在网络抖动时可能出现消息延迟、RRateLimiter的令牌桶在时钟跳跃时不受影响但Redis重启后限流状态会重置。这些都不是Bug,而是分布式环境的物理边界。理解这些边界,才能正确地设计业务兜底逻辑。比如,订单超时关闭这种场景,即使延迟队列偶尔晚执行几分钟,只要后续有对账任务做补偿,业务上就是可以接受的。

第五,尽量用独立的RedissonClient管理“内部组件数据”和“业务缓存数据”。如果项目规模不大,可以共用一个Client;但如果其中一个业务模块疯狂写入大对象,造成连接池占用,会影响分布式锁的获取速度。我当时在网关层和业务服务里各建了一个RedissonClient,通过配置项区分连接池大小,后期排查问题会清爽很多。

7. 从入门到精通的最后一段路

Redis系列写到这里,其实你已经有能力在真实项目里把Redisson用起来了。从分布式锁的watch dog续期,到RMapCache的精准过期,再到RRateLimiter的令牌桶限流,这些能力组合起来,基本覆盖了Redis在Java服务端90%的常见需求。我的建议是先在一两个非核心模块里引入Redisson,把锁、缓存、限流各跑一遍,感受一下数据结构从本地到分布式的迁移过程,再逐步扩大使用规模。

最后再分享一个小技巧:排查Redisson问题不要太依赖看日志,先在Redis客户端里观察对应Key的TTL变化。比如你可以用Redis Desktop Manager监控一个分布式锁Key的TTL,如果TTL一直稳定在30秒左右跳动,说明watch dog在正常工作;如果TTL出现了归零,那基本是业务线程已经走了unlock,或者传了leaseTime导致看门狗没启动。这种“从数据反推代码行为”的方式,在分布式环境里定位问题会比翻代码高效得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询