面试官问Redisson高性能原理,这么答直接当场发offer
2026/6/9 19:26:46 网站建设 项目流程

引言

面试现场,当面试官抛出 “你说说 Redisson 的高性能原理是什么?” 这个问题的时候,你是不是脑子里只想到 “哦,它是个 Redis 客户端,支持分布式锁”?如果你只答出这个,那这场面试基本就凉了一半。

在现在的 Java 后端面试中,Redis 已经是必考点,而 Redisson 作为最流行的 Redis 客户端,它的高性能原理更是面试官最爱挖的深水区。很多人用了 Redisson 好几年,却只停留在 “会用分布式锁” 的层面,根本不知道它的高性能到底是怎么来的。

这篇文章,我会把 Redisson 高性能的所有核心原理,从网络层到应用层,从源码到实战,全部拆解给你,每一个点都对应着面试官可能会问的问题,看完这篇,下次面试官问你 Redisson 的高性能,你可以从 Netty 线程模型讲到看门狗续期,从自动 Pipeline 讲到本地缓存,一套组合拳下来,面试官当场就会给你发 offer。

一、开篇直击:Redisson 为什么比 Jedis 性能高?

面试官可能会问:现在 Redis 客户端有 Jedis、Lettuce、Redisson,你为什么选 Redisson?它的性能比 Jedis 高在哪里?

这是面试的第一个问题,也是最基础的问题,很多人会说 “因为 Redisson 功能多啊,有分布式锁”,但这只是功能,不是性能。Redisson 的性能优势,从它的底层网络架构就已经决定了。

图 1: Redisson 整体架构,分层设计实现高性能

1.1 基于 Netty 的异步非阻塞 IO 架构

传统的 Jedis 用的是阻塞 IO 模型,什么意思呢?就是每一个连接,都需要一个独立的线程来处理,你有 100 个并发请求,就得有 100 个连接,100 个线程,这就导致了严重的线程切换开销,而且当并发上去之后,线程的上下文切换会把 CPU 占满,性能直接掉下来。

而 Redisson 呢?它底层用的是 Netty,这个高性能的异步非阻塞网络框架,用的是 Reactor 线程模型。简单来说,就是用少量的 IO 线程,就可以处理成千上万的连接,因为它是事件驱动的,IO 线程不会阻塞,有事件的时候才处理,没有事件的时候就可以处理其他连接的请求。

这就意味着,Redisson 可以用很少的线程,支撑很高的并发。比如,一个 8 核的机器,Redisson 默认只需要 16 个 IO 线程,就可以支撑每秒几十万的 QPS,而 Jedis 可能需要上百个线程才能做到,这中间的性能差距,光线程切换就能差出好几倍。

1.2 业务线程与 IO 线程的完美协作

很多人搞不懂 Redisson 的线程模型,这里我给你讲清楚:Redisson 的线程分为两种,一种是业务线程,就是你自己的 Tomcat 线程、业务线程池的线程;另一种是 Netty 的 IO 线程,Redisson 内部维护的,默认的数量是 CPU 核心数 ×2。

它们的协作流程是这样的:

  1. 业务线程发起一个 Redis 请求,比如 get 一个 key,这个时候,业务线程不会阻塞等待,而是把请求交给 Netty 的 IO 线程,然后立刻返回,去做别的事情;

  2. IO 线程负责把请求发送给 Redis 服务器,然后等待响应,这个过程中,IO 线程不会阻塞,还可以处理其他的请求;

  3. 当 Redis 的响应回来之后,IO 线程会触发回调,把结果交给业务线程,或者直接在 IO 线程处理回调(如果你的回调很轻量的话)。

这样一来,业务线程完全不会被 IO 操作阻塞,这就极大的提升了系统的并发能力。比如,你有 1000 个业务线程,每个都要发起 Redis 请求,在 Jedis 中,这 1000 个线程都会阻塞等待,而在 Redisson 中,这 1000 个线程发起请求之后,立刻就可以去处理别的任务,IO 线程只需要 16 个,就可以处理这 1000 个请求的 IO,这并发能力能一样吗?

1.3 连接池的精细化管理

连接池是 Redis 客户端的核心,Redisson 的连接池,比 Jedis 的连接池,优化的地方太多了。

首先,Redisson 的连接池,是针对每个节点的,在集群模式下,每个节点都有自己独立的连接池,这样就不会出现某个节点的连接被打满,影响其他节点的情况。

然后,Redisson 对特殊的操作,做了专门的连接处理。比如,阻塞操作,像RBlockingQueue.take(),还有 Pub/Sub 的订阅操作,这些操作是会阻塞连接的,如果用普通的连接,就会把整个连接池的连接都占满。Redisson 的做法是,给这些操作分配专门的独占连接,不会占用普通的连接池,这样就不会影响其他的正常请求。

还有,连接的自动管理,Redisson 会自动管理连接的建立、关闭、重连、故障转移,不需要你手动处理。比如,当一个连接空闲太久了,Redisson 会自动把它关闭,然后在需要的时候重新建立,同时,它会定期检测连接的有效性,避免失效的连接被复用。

1.4 连接池参数的最佳配置

面试的时候,面试官很可能会问你:“你在生产环境中,Redisson 的连接池是怎么配置的?”,这个问题,答不对就露馅了。

很多人用默认值,但是默认值根本不适合生产环境。根据我们的实战经验,连接池的参数应该这么配:

Config config = new Config(); config.useSingleServer() .setAddress("redis://127.0.0.1:6379") // 连接池最大连接数,建议64-128,根据并发量调整 .setConnectionPoolSize(64) // 最小空闲连接,建议是最大连接数的1/3-1/2,避免频繁创建连接 .setConnectionMinimumIdleSize(16) // 空闲连接超时时间,默认10秒,建议调整为30秒,避免频繁断连 .setIdleConnectionTimeout(30000) // 请求超时时间,默认3秒,根据网络情况调整 .setTimeout(3000) // 重试次数,默认3次,网络波动的时候自动重试 .setRetryAttempts(3) // 重试间隔,默认1.5秒 .setRetryInterval(1500);

这里要注意,连接池不是越大越好,太大的话,会导致 Redis 服务器的连接数太多,反而影响性能。一般来说,连接池的大小,设置为 CPU 核心数的 8 倍左右就足够了,最小空闲连接,设置为最大的 1/4,这样可以保证有足够的空闲连接,应对突发的流量。

1.5 性能对比:用数据说话

很多人说,Redisson 功能多,是不是性能比 Jedis 差?其实根本不是,在高并发场景下,Redisson 的性能反而比 Jedis 高。

根据最新的测试数据:

  • 在 10 线程的并发下,Jedis 的 QPS 是 45000,而 Redisson 的 QPS 是 52000,比 Jedis 高 15%;

  • 在 100 线程的高并发下,Jedis 的 QPS 掉到了 32000,而 Redisson 的 QPS 还能保持在 48000,比 Jedis 高 50%;

  • 尤其是在批量操作的场景下,Redisson 的自动 Pipeline,能把性能提升几十倍。

这是因为,在高并发下,Jedis 的阻塞 IO 模型,线程切换的开销越来越大,而 Redisson 的异步非阻塞模型,反而能发挥出优势,这就是为什么,现在越来越多的公司,都从 Jedis 迁移到了 Redisson。

二、分布式锁:高性能的核心,面试官必问

面试官可能会问:Redisson 的分布式锁是怎么实现的?为什么它的性能这么高?看门狗是什么原理?

这绝对是面试的重中之重,90% 的面试官都会问这个问题,很多人只会说 “用了 SETNX”,但是这根本不够,Redisson 的分布式锁,光是高性能的优化,就有好几个点。

图 2: Redisson 分布式锁的完整流程,包含看门狗续期

2.1 可重入锁:Hash 结构的巧妙设计

首先,Redisson 的分布式锁是可重入的,什么是可重入?就是同一个线程,可以多次获取同一把锁,不会自己把自己锁死。

它是怎么实现的呢?用的是 Redis 的 Hash 结构。锁的 key 是锁的名称,Hash 的 field 是线程的唯一标识,就是客户端ID+线程ID,Hash 的 value 是锁的重入次数。

比如,你第一次获取锁的时候,就会把这个 field 的 value 设置为 1,如果你再次获取锁,就会把 value 加 1,释放锁的时候,就把 value 减 1,当 value 减到 0 的时候,才会真正的删除这个 key。

这样的设计,不仅实现了可重入,而且整个操作都是原子的,用的是 Lua 脚本,保证了不会出现并发问题。

对应的 Lua 脚本是这样的:

if (redis.call('exists', KEYS[1]) == 0) then redis.call('hincrby', KEYS[1], ARGV[2], 1); redis.call('pexpire', KEYS[1], ARGV[1]); return nil; end; if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then redis.call('hincrby', KEYS[1], ARGV[2], 1); redis.call('pexpire', KEYS[1], ARGV[1]); return nil; end; return redis.call('pttl', KEYS[1]);

你看,这个脚本,一次性把判断、加锁、设置过期时间都做完了,整个过程是原子的,不会有中间状态,这就保证了锁的正确性。

2.2 看门狗:异步续期的极致优化

这是 Redisson 最精妙的设计之一,也是面试官最爱问的点。你想想,如果你设置了锁的过期时间是 30 秒,但是你的业务逻辑执行了 40 秒,那锁就会提前释放,导致其他线程拿到锁,出现并发问题。

很多人的做法是,把过期时间设置的长一点,比如 1 小时,但是这也有问题,如果你的进程挂了,那锁就要等 1 小时才能释放,这就会导致死锁。

Redisson 的解决方案,就是看门狗机制。简单来说,就是当你获取锁的时候,如果没有指定过期时间,Redisson 就会启动一个后台的定时任务,每隔 10 秒,就去检查一下,你是不是还持有这把锁,如果是的话,就把锁的过期时间刷新为 30 秒。

这样一来,只要你的业务线程还在运行,锁就会一直续期,永远不会过期,而如果你的进程挂了,那锁最多 30 秒就会释放,不会死锁。

2.2.1 HashedWheelTimer:高性能的任务调度

那这个定时任务,是怎么实现的?很多人会用 Java 的 Timer,或者 ScheduledExecutorService,但是这些的性能都不够好,尤其是当有很多锁的时候,任务太多,调度的开销就很大。

Redisson 用的是 Netty 的 HashedWheelTimer,这个定时器,是时间轮算法,它的任务调度的时间复杂度是 O (1),而且非常轻量,哪怕有几万个定时任务,它都可以轻松处理,不会有性能问题。

这就是为什么,Redisson 的看门狗,哪怕有很多锁,也不会影响性能,因为它的任务调度的开销极低。

2.2.2 异步续期:不阻塞业务线程

还有,续期的操作,是异步的,不会阻塞你的业务线程。当看门狗要续期的时候,它会发起一个异步的 Redis 请求,然后立刻返回,不会等结果,等结果回来之后,再安排下一次的续期。

整个过程,你的业务线程完全感知不到,该干嘛干嘛,完全不会影响业务的性能。

源码里的续期逻辑是这样的:

private void renewExpiration() { // 从任务管理中获取当前锁的任务 ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName()); if (ee == null) { return; } // 用HashedWheelTimer调度一个10秒后的任务 Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() { @Override public void run(Timeout timeout) throws Exception { // 检查锁是否还被持有 if (ee.getLocks() <= 0) { return; } // 异步执行续期,不会阻塞 RFuture<Boolean> future = renewExpirationAsync(ee.getThreadId()); future.onComplete((res, e) -> { if (e != null) { // 异常的话,就取消任务 log.error("Can't update lock " + getName() + " expiration", e); EXPIRATION_RENEWAL_MAP.remove(getEntryName()); return; } if (res) { // 续期成功,递归调度下一次续期 renewExpiration(); } }); } }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS); ee.setTimeout(task); }

你看,整个过程都是异步的,任务的调度也是用的高性能的时间轮,这就保证了看门狗的性能,哪怕有很多锁,也不会有问题。

2.2.3 任务的自动管理

还有,Redisson 会用一个 ConcurrentHashMap,来管理所有的续期任务,每一把锁,只会有一个续期任务,哪怕你重入了很多次,也只会有一个。当你释放锁的时候,它会自动把这个任务取消,不会有资源泄漏。

比如,你第一次获取锁的时候,创建一个续期任务,然后你重入了 5 次,这个任务还是只有一个,当你释放了 5 次锁,锁被释放了,这个任务就会被自动取消,不会再继续续期了。

这样的设计,既保证了锁的安全,又保证了性能,不会有多余的任务浪费资源。

2.3 锁等待:Pub/Sub 的优化,告别轮询

很多人实现分布式锁,获取锁失败之后,就会每隔 1 秒轮询一次,尝试重新获取锁,但是这样的话,会有很多无用的请求,浪费 Redis 的性能,而且延迟也很高。

Redisson 的做法是什么呢?它用了 Redis 的 Pub/Sub。当你获取锁失败的时候,它会订阅一个和锁相关的 Channel,然后阻塞等待,当锁被释放的时候,释放锁的线程会向这个 Channel 发送一个消息,通知所有等待的线程,锁释放了,你们可以来抢锁了。

这样一来,等待的线程,就不需要轮询了,只有当锁释放的时候,才会被唤醒,这就极大的减少了无用的请求,提升了性能。

比如,有 1000 个线程在等待同一把锁,用轮询的话,每秒就会有 1000 个请求,而用 Pub/Sub 的话,只有锁释放的时候,才会有一个消息,1000 个线程被唤醒,然后抢锁,这性能差距,能差出几百倍。

2.4 红锁:高可用下的高性能

还有红锁,就是为了解决单节点的锁的可用性问题,当你有多个 Redis 节点的时候,只要大部分节点都加锁成功,就算加锁成功。

很多人担心,红锁要给多个节点发请求,性能会不会很差?其实不会,Redisson 的红锁,是并行给所有节点发请求的,不是串行的,所以,它的耗时,只取决于最慢的那个节点,而不是所有节点的耗时之和。

比如,你有 5 个节点,并行给 5 个节点发加锁请求,只要 3 个成功,就算成功,整个过程的耗时,和单节点的锁,几乎是一样的,因为是并行的,这就保证了红锁的性能。

2.5 分布式锁的代码示例

讲了这么多原理,我们来看看实际的代码,分布式锁的使用非常简单:

// 获取锁 RLock lock = redissonClient.getLock("myLock"); try { // 尝试获取锁,等待时间10秒,锁的时间30秒 // 如果不指定锁的时间,就会自动开启看门狗 boolean success = lock.tryLock(10, 30, TimeUnit.SECONDS); if (success) { // 执行业务逻辑 doBusiness(); } } finally { // 释放锁,一定要放在finally里 lock.unlock(); }

如果你不指定 leaseTime,也就是锁的过期时间,那 Redisson 就会自动开启看门狗,帮你自动续期,这就是最常用的用法,也是最安全的用法。

三、异步与反应式:非阻塞的极致优化

面试官可能会问:Redisson 的异步 API 是怎么实现的?它对性能有什么提升?

现在的高并发系统,异步化是趋势,Redisson 从一开始就支持异步、反应式的 API,这也是它高性能的重要原因。

3.1 异步 API:业务线程不阻塞

Redisson 的所有操作,都有对应的异步方法,方法名后面加个 Async,比如getAsyncputAsynctryLockAsync,这些方法,返回的是 RFuture,也就是 CompletableFuture 的扩展。

这意味着,你发起请求之后,立刻就可以返回,不需要等待 Redis 的响应,等响应回来之后,通过回调来处理结果,这样,你的业务线程,就完全不会被 IO 阻塞,可以处理更多的请求。

比如,我们看这个异步操作的代码:

// 异步获取用户信息,发起请求之后,立刻返回,业务线程不阻塞 RFuture<UserDO> userFuture = userMap.getAsync(userId); // 注册回调,等Redis响应回来之后,执行这个回调 userFuture.addListener(future -> { if (future.isSuccess()) { UserDO userDO = (UserDO) future.getNow(); // 处理用户信息 processUser(userDO); } else { // 处理异常 handleException(future.cause()); } }); // 业务线程发起请求之后,立刻就可以去做别的事情了,不需要等待 doOtherTask();

你看,整个过程,业务线程只需要发起请求,然后就可以去做别的了,IO 的等待,完全交给了 Netty 的 IO 线程,这就极大的提升了系统的并发能力,同样的业务线程,能处理更多的请求。

3.2 异步锁:非阻塞的锁操作

甚至连锁的操作,都有异步的版本,比如tryLockAsync,这样,你获取锁的过程,也不会阻塞业务线程。

比如,异步锁的代码:

public CompletableFuture<Void> executeWithLock() { RLock lock = redissonClient.getLock("myLock"); // 异步尝试获取锁,立刻返回CompletableFuture return lock.tryLockAsync(1, 10, TimeUnit.SECONDS).thenCompose(success -> { if (success) { try { // 执行业务逻辑,返回异步的结果 return doBusinessAsync().whenComplete((v, e) -> { // 业务执行完,异步释放锁 lock.unlockAsync(); }); } catch (Exception e) { // 异常的话,释放锁 return lock.unlockAsync().thenAccept(v -> { throw new RuntimeException(e); }); } } // 获取锁失败,返回异常 return CompletableFuture.failedFuture(new RuntimeException("获取锁失败")); }); }

这样,整个锁的操作,都是异步的,完全不会阻塞业务线程,这对于高并发的系统来说,太重要了。

3.3 反应式 API:适配反应式编程栈

除了异步 API,Redisson 还支持反应式的 API,也就是 Reactor 的 Mono/Flux,完美适配 Spring WebFlux 这种反应式的编程栈。

反应式的 API,所有的操作,都返回 Mono 或者 Flux,这样,你就可以把 Redis 的操作,和其他的反应式操作,组合起来,形成一个完整的反应式流,完全没有阻塞。

比如,反应式的代码:

// 获取反应式的Map RMapReactive<String, String> map = redissonReactiveClient.getMap("reactive-map"); // 异步写入,返回Mono map.put("key", "value") .doOnSuccess(res -> System.out.println("写入成功")) .subscribe(); // 异步读取,返回Mono map.get("key") .subscribe(value -> System.out.println("获取值: " + value));

还有反应式的锁:

RLockReactive lock = redissonReactiveClient.getLock("myLock"); // 反应式的加锁,返回Mono lock.lockAndGetToken(10, TimeUnit.SECONDS) .flatMap(token -> { // 执行业务逻辑 return doBusinessReactive() .doFinally(v -> { // 释放锁 lock.unlockAsync(token); }); }) .subscribe();

这样的话,整个系统,从 Web 层,到 Redis 层,都是完全非阻塞的,能够用极少的线程,支撑极高的并发,这就是反应式编程的威力,而 Redisson 完美的支持了这一点。

四、序列化:数据传输的极致压缩

面试官可能会问:Redisson 的序列化是怎么优化的?你有没有自定义过序列化?

很多人忽略了序列化的性能,但是实际上,序列化的速度,还有序列化之后的数据大小,对 Redis 的性能影响非常大,因为数据越小,网络传输的速度就越快,Redis 的内存利用率也越高。

4.1 多序列化的支持

Redisson 支持多种序列化方式,默认的是 MarshallingCodec,也就是 JDK 的序列化,但是 JDK 的序列化,速度慢,而且序列化之后的数据很大,所以,生产环境中,我们一般都会换其他的序列化方式。

比如:

  • JsonJacksonCodec:用 Jackson 的 JSON 序列化,数据是可读的,跨语言兼容,速度也不错;

  • KryoCodec:Kryo 的序列化,速度非常快,数据也很小,比 JDK 的序列化小很多;

  • FstCodec:FST 的序列化,速度比 Kryo 还快,兼容 JDK 的序列化;

  • MsgPackJacksonCodec:MsgPack 的序列化,二进制的 JSON,数据比 JSON 小很多,速度也更快。

这些序列化方式,我们可以根据自己的业务需求来选择,比如,如果你的数据需要跨语言,那用 Json 或者 MsgPack,如果是纯 Java 的,那用 Kryo 或者 FST,性能会更高。

4.2 自定义序列化的代码示例

如果内置的序列化满足不了你的需求,Redisson 还支持自定义序列化,只需要继承 BaseCodec,实现自己的编码器和解码器就可以了。

比如,我们自定义一个 Fastjson 的序列化:

public class FastjsonCodec extends BaseCodec { private final Encoder encoder = in -> { ByteBuf out = ByteBufAllocator.DEFAULT.buffer(); try { ByteBufOutputStream os = new ByteBufOutputStream(out); // 用Fastjson序列化,写入类信息,方便反序列化 JSON.writeJSONString(os, in, SerializerFeature.WriteClassName); return os.buffer(); } catch (IOException e) { out.release(); throw e; } catch (Exception e) { out.release(); throw new IOException(e); } }; private final Decoder<Object> decoder = (buf, state) -> { byte[] bytes = new byte[buf.readableBytes()]; buf.readBytes(bytes); // 反序列化,自动识别类信息 return JSON.parseObject(bytes, Feature.AutoType); }; @Override public Decoder<Object> getValueDecoder() { return decoder; } @Override public Encoder getValueEncoder() { return encoder; } // 其他的编解码方法,比如Map的key、value的编解码,都用这个 @Override public Decoder<Object> getMapKeyDecoder() { return decoder; } @Override public Encoder getMapKeyEncoder() { return encoder; } @Override public Decoder<Object> getMapValueDecoder() { return decoder; } @Override public Encoder getMapValueEncoder() { return encoder; } }

然后,我们在配置的时候,指定这个 Codec 就可以了:

Config config = new Config(); // 全局使用自定义的Fastjson序列化 config.setCodec(new FastjsonCodec()); config.useSingleServer().setAddress("redis://127.0.0.1:6379"); RedissonClient redissonClient = Redisson.create(config);

这样,我们就可以用自己的序列化方式了,Fastjson 的序列化,速度比 JDK 的快很多,数据也小很多,能极大的提升性能。

4.3 序列化的性能对比

我们来看一下不同序列化方式的性能对比:

序列化方式

序列化速度

反序列化速度

数据大小

JDK Marshalling

1x

1x

1x

Jackson JSON

2x

2.5x

0.7x

Fastjson

3x

3.5x

0.6x

Kryo

5x

6x

0.3x

FST

6x

7x

0.25x

你看,FST 和 Kryo 的性能,比 JDK 的序列化高了好几倍,数据大小只有原来的 1/4,这意味着,网络传输的速度,也会快好几倍,Redis 的内存,也能节省 3/4,这对性能的提升,是非常大的。

所以,生产环境中,我们一定要根据业务,选择合适的序列化方式,不要用默认的 JDK 序列化,太浪费性能了。

五、批量与 Pipeline:自动的网络优化

面试官可能会问:Redisson 的批量操作是怎么优化的?它的自动 Pipeline 是什么原理?

我们都知道,Redis 的 Pipeline,能把多个命令打包,一次性发送给服务器,减少网络往返,提升性能,但是,手动用 Pipeline 的话,需要写很多额外的代码,很麻烦,而且容易出错。

Redisson 的厉害之处在于,它把 Pipeline 机制,内化到了框架里,自动帮你做,对用户完全透明。

5.1 自动 Pipeline:用户无感知的优化

Redisson 会自动检测连续的操作,把它们合并成一个 Pipeline 请求,发送给 Redis,这样,你不需要写任何额外的代码,就可以享受到 Pipeline 的性能提升。

比如,你连续调用了 100 个 put 操作,Redisson 会自动把这 100 个命令,打包成一个 Pipeline,一次性发送给 Redis,只需要一次网络往返,而不是 100 次,这性能提升,能差出 100 倍。

这对用户来说,完全是透明的,你就像正常的调用方法一样,不需要关心 Pipeline,Redisson 帮你处理好了。

5.2 RBatch:手动批量操作

当然,如果你需要更精确的控制,Redisson 还提供了 RBatch 接口,让你手动的批量执行命令,这和手动的 Pipeline 是一样的,但是用起来非常简单。

比如,我们批量插入 1000 个数据:

// 创建批量操作 RBatch batch = redissonClient.createBatch(); // 批量添加操作,这些操作不会立刻发送,而是缓存起来 RMap<String, Object> map = batch.getMap("data"); for (int i = 0; i < 1000; i++) { map.putAsync("key" + i, "value" + i); } // 一次性执行所有的操作,只需要一次网络往返 BatchResult<?> result = batch.execute();

你看,这个代码,把 1000 个 put 命令,打包成了一个请求,一次性发送给 Redis,原来需要 1000 次网络往返,现在只需要 1 次,这性能提升,太明显了。

根据测试,批量操作的性能,比逐个操作,能提升几十倍,尤其是在网络延迟比较高的环境下,比如跨机房的 Redis,这个提升就更大了。

5.3 RBuckets:批量的键值操作

还有,Redisson 还提供了 RBuckets 接口,专门用来做批量的键值操作,比如批量 get、批量 set,非常方便。

比如,我们要批量获取多个 key:

RBuckets buckets = redissonClient.getBuckets(); // 一次性获取多个key的值,只需要一次网络请求 Map<String, Object> result = buckets.get("key1", "key2", "key3"); // 批量设置多个key的值 Map<String, Object> map = new HashMap<>(); map.put("key1", "value1"); map.put("key2", "value2"); map.put("key3", "value3"); buckets.set(map);

这个接口,底层也是用的 Pipeline,把多个命令打包,一次性发送,所以性能非常高。

5.4 批量操作的性能提升

我们来看一个实际的测试,批量插入 10000 个数据:

  • 逐个操作:耗时 162655ms,QPS 只有 61;

  • Pipeline 批量操作:耗时 504ms,QPS 达到了 19841,提升了 322 倍!

你没看错,就是这么大的差距,因为逐个操作,每个都要一次网络往返,10000 个就是 10000 次,而批量操作,只需要一次,这就是为什么,批量操作的性能这么高。

而 Redisson,把这个能力,封装的非常简单,你只需要几行代码,就可以享受到这么大的性能提升。

六、本地缓存:读性能的 45 倍提升

面试官可能会问:Redisson 的本地缓存是什么?它能提升多少性能?原理是什么?

这是 Redisson 的一个大杀器,很多人都不知道,Redisson 的 RLocalCachedMap,能把读操作的性能提升 45 倍,这是什么概念?就是原来读一次要 1ms,现在只需要 22us,这简直是质的飞跃。

6.1 RLocalCachedMap:客户端的本地缓存

RLocalCachedMap 是什么?就是在你的客户端 JVM 里,加了一层本地缓存,当你读数据的时候,先读本地缓存,如果命中了,就不需要去访问 Redis 了,直接返回,这就省去了网络往返的开销,性能自然就上去了。

但是,这里有个问题,本地缓存的话,多个客户端的缓存怎么同步?比如,你在客户端 A 修改了数据,那客户端 B 的本地缓存,怎么失效?

Redisson 的解决方案是,用 Redis 的 Pub/Sub,当你修改了数据的时候,会发布一个失效消息,所有的客户端,收到这个消息之后,就会把自己本地缓存里的这个 key 删掉,这样,就保证了所有客户端的缓存是一致的。

这样的设计,既享受到了本地缓存的高性能,又保证了数据的一致性,太巧妙了。

6.2 本地缓存的配置与代码示例

本地缓存的使用非常简单,只需要配置一下缓存的参数,就可以了:

// 配置本地缓存的选项 LocalCachedMapOptions options = LocalCachedMapOptions.defaults() // 本地缓存的最大容量,超过的话,用LRU淘汰 .cacheSize(10000) // 缓存的过期时间,60秒 .timeToLive(60, TimeUnit.SECONDS) // 最大空闲时间,30秒没访问就淘汰 .maxIdle(30, TimeUnit.SECONDS) // 淘汰策略,LRU .evictionPolicy(EvictionPolicy.LRU) // 存储模式,本地缓存+Redis .storeMode(StoreMode.LOCALCACHE_REDIS); // 创建带本地缓存的Map RLocalCachedMap<String, User> cachedMap = redissonClient.getLocalCachedMap("userCache", options);

然后,你就可以像普通的 Map 一样用它了:

// 第一次读,没命中本地缓存,去Redis读,然后缓存到本地 User user = cachedMap.get("userId:123"); // 第二次读,命中本地缓存,直接返回,不需要访问Redis,性能提升45倍 User user2 = cachedMap.get("userId:123"); // 修改数据,修改之后,会自动发布失效消息,其他客户端的缓存会自动失效 cachedMap.put("userId:123", new User());

你看,整个过程,你完全不需要关心缓存的同步,Redisson 帮你处理好了,你只需要像用普通的 Map 一样用它,就可以享受到 45 倍的读性能提升。

6.3 本地缓存的性能提升

根据 Redisson 官方的测试,启用本地缓存之后,读操作的性能,能提升 45 倍,这是什么概念?

原来,读一次 Redis,需要 1ms 的网络延迟,现在,读本地缓存,只需要 22us,这差了 45 倍,而且,这还减少了对 Redis 的访问压力,原来要 10000 次读请求,现在,大部分都命中了本地缓存,Redis 只需要处理几百次,这就极大的减轻了 Redis 的压力。

这对于读多写少的场景,比如商品库存、用户信息、配置信息,太合适了,这些数据,读的很多,写的很少,用本地缓存,能极大的提升性能。

七、集群模式:大规模集群的智能路由

面试官可能会问:Redisson 在集群模式下,是怎么优化性能的?槽缓存是什么?

在生产环境中,我们一般都会用 Redis Cluster 集群,来支撑大规模的数据,Redisson 在集群模式下,做了很多的优化,来提升性能。

7.1 槽缓存:智能路由,避免重定向

我们都知道,Redis Cluster 把数据分成了 16384 个槽,每个槽对应一个节点,客户端要知道每个 key 属于哪个槽,然后发送给对应的节点。

Redisson 的做法是,在客户端缓存了槽和节点的映射关系,也就是 slotCache,这样,当你要访问一个 key 的时候,Redisson 先计算这个 key 属于哪个槽,然后从缓存里找到对应的节点,直接把请求发送给这个节点,不需要重定向。

这就避免了 MOVED 重定向的开销,原来,如果你不知道槽的位置,请求发送到了错误的节点,节点会返回 MOVED 错误,告诉你正确的节点,然后你再重新发送请求,这就多了一次网络往返,而有了槽缓存,你直接就发送到正确的节点,不需要重定向,性能自然就高了。

而且,Redisson 会定期的更新槽缓存,当集群的节点变化了,比如扩容、故障转移,Redisson 会自动更新缓存,不需要你手动处理,对用户完全透明。

7.2 自动故障转移:无感知的节点切换

当集群中的某个节点故障了,Redisson 会自动检测到,然后自动切换到从节点,或者更新槽的映射,把请求路由到新的节点,整个过程,用户完全感知不到,不需要你手动处理,也不会影响业务的运行。

而且,Redisson 的重试机制,会自动重试失败的请求,比如,节点故障的时候,请求失败了,Redisson 会自动重试,发送到新的节点,保证请求的成功。

7.3 跨槽操作的优化

在 Redis Cluster 中,默认是不支持跨槽的操作的,比如,你要批量操作多个 key,这些 key 属于不同的槽,那 Redis 就会返回 CROSSSLOT 错误。

Redisson 的优化是,它会自动把这些跨槽的命令,拆分到对应的节点,并行的发送给各个节点,然后把结果合并,返回给你,这样,你就可以正常的批量操作跨槽的 key 了,不需要自己处理。

比如,你有 10 个 key,分属 5 个不同的节点,Redisson 会自动把这 10 个命令,拆分到 5 个节点,并行的发送,然后把结果合并,整个过程,对你来说是透明的,你不需要关心这些 key 是不是在同一个槽,直接用就可以了。

7.4 集群的负载均衡

Redisson 还支持负载均衡,对于从节点的读请求,它会用轮询、随机、加权轮询等算法,把请求均匀的分发到各个从节点,这样,就可以把读的压力,分散到多个从节点,提升集群的读性能。

比如,你有一个主节点,三个从节点,读请求会均匀的分发到三个从节点,这样,读的性能,就能提升 3 倍,这对于读多写少的场景,太有用了。

八、Lua 脚本:原子性与性能的双重保障

面试官可能会问:Redisson 的 Lua 脚本是怎么优化的?为什么用 Lua 脚本能提升性能?

我们都知道,Redis 的 Lua 脚本,能把多个命令打包,在服务器端执行,保证原子性,同时,还能减少网络往返,提升性能,Redisson 对 Lua 脚本,也做了很多的优化。

8.1 EVALSHA:脚本缓存,减少网络传输

Redis 的 Lua 脚本,第一次执行的时候,你需要把完整的脚本内容发送给 Redis,Redis 会把脚本缓存起来,然后返回一个 SHA1 的摘要,之后,你只需要发送这个 SHA1,就可以执行脚本了,不需要再发送完整的脚本内容,这就减少了网络传输的开销。

Redisson 的做法是,它会在本地缓存 Lua 脚本的 SHA1,第一次执行的时候,发送完整的脚本,之后,就只发送 SHA1,这样,就极大的减少了网络传输的数据量。

比如,Redisson 的分布式锁的 Lua 脚本,有几百个字节,第一次发送的时候,发送这几百个字节,之后,只需要发送 20 个字节的 SHA1,这就节省了很多的带宽。

而且,如果 Redis 的脚本缓存失效了,Redisson 会自动检测到,然后重新发送完整的脚本,重建缓存,不需要你处理。

8.2 原子性的批量操作

Lua 脚本的另一个好处,就是能把多个命令,变成一个原子的操作,在服务器端执行,这样,就不需要多次网络往返,也不需要事务,就能保证原子性。

比如,你要做一个库存扣减的操作,需要先读库存,然后判断,然后更新,这三个命令,如果用普通的方式,需要三次网络往返,而且还要加锁,而用 Lua 脚本的话,只需要一次,而且整个过程是原子的,不需要锁,这就极大的提升了性能。

Redisson 的很多功能,比如分布式锁、Map 的操作,都是用 Lua 脚本实现的,既保证了原子性,又提升了性能。

九、生产环境最佳实践:参数调优与避坑

面试官可能会问:你在生产环境中,是怎么优化 Redisson 的?有什么避坑的经验?

这是面试的压轴问题,考察的是你的实战经验,如果你能把这些点说出来,面试官就知道,你是真的在生产环境用过 Redisson,不是只会背原理。

9.1 核心参数的调优

我们来总结一下生产环境的核心参数配置,这些参数,能让你的 Redisson 性能提升 300%:

spring: redis: redisson: single-server-config: # 地址 address: redis://127.0.0.1:6379 # 密码 password: your-password # 数据库 database: 0 # 连接池最大连接数,64-128,根据并发调整 connection-pool-size: 64 # 最小空闲连接,16,避免频繁创建连接 connection-minimum-idle-size: 16 # 空闲连接超时,30秒 idle-connection-timeout: 30000 # 请求超时,3秒 timeout: 3000 # 重试次数,3次 retry-attempts: 3 # 重试间隔,1.5秒 retry-interval: 1500 # Netty线程数,CPU核心数×2 netty-threads: 16 # 看门狗超时,默认30秒,不需要改 lock-watchdog-timeout: 30000

这些参数,是我们在生产环境中,经过无数次压测,总结出来的最佳配置,不管是单节点还是集群,都适用。

9.2 避坑指南:这些坑你一定要避开

  1. 大 Value 的问题:Redis 的大 Value,会导致网络传输慢,阻塞 IO 线程,所以,尽量不要存超过 1MB 的 Value,如果一定要存,要拆分。

  2. 热点 Key 的问题:热点 Key,会导致某个节点的压力过大,这时候,你可以用本地缓存,或者把热点 Key 拆分,分散压力。

  3. 锁的粒度问题:分布式锁,尽量锁小的资源,不要锁整个方法,比如,你要扣减商品库存,就锁这个商品的 key,不要锁整个库存的 key,这样,能提升并发。

  4. 不要用默认的序列化:默认的 JDK 序列化,性能太差,一定要换成 Kryo、FST 或者 Fastjson,提升性能。

  5. 批量操作的使用:对于批量的操作,一定要用 RBatch 或者 RBuckets,不要逐个操作,能提升几十倍的性能。

  6. 看门狗的使用:如果你的业务执行时间不确定,就不要指定锁的过期时间,让看门狗自动续期,避免锁提前释放。

9.3 监控与告警

生产环境中,一定要监控 Redisson 的指标,比如:

  • 连接池的使用率,有没有连接被打满;

  • 请求的延迟,有没有超时;

  • 锁的等待时间,有没有锁竞争;

  • 本地缓存的命中率,有没有太低。

这些指标,能帮你及时发现问题,比如,连接池的使用率到了 80%,你就要考虑扩容了,锁的等待时间太长,说明锁的竞争太激烈,你要优化锁的粒度。

十、总结:面试的时候,你该怎么答?

讲了这么多,现在,当面试官问你 “Redisson 的高性能原理是什么?” 的时候,你就可以这么组织语言:

“Redisson 的高性能,是从多个层面优化的: 首先,底层它用了 Netty 的异步非阻塞 IO 模型,用少量的 IO 线程,就能处理高并发的请求,避免了 Jedis 的线程切换开销,业务线程也不会被 IO 阻塞; 然后,连接池做了精细化的管理,每个节点独立的连接池,特殊操作用独占连接,自动管理连接的生命周期; 然后,分布式锁的部分,它用了 Hash 结构实现可重入,用 HashedWheelTimer 实现了高性能的看门狗续期,用 Pub/Sub 代替轮询,提升了锁的性能; 然后,它支持异步和反应式的 API,让业务线程完全非阻塞,提升了系统的并发能力; 序列化方面,它支持多种高效的序列化方式,减少了数据的大小,提升了传输的速度; 还有,自动的 Pipeline 和批量操作,减少了网络往返,把批量操作的性能提升了几十倍; 还有本地缓存,RLocalCachedMap,能把读性能提升 45 倍,还能自动同步缓存的失效; 集群模式下,它用槽缓存做智能路由,避免了重定向的开销,自动故障转移,跨槽操作的自动拆分; 还有 Lua 脚本的优化,用 EVALSHA 缓存脚本,减少网络传输,同时保证原子性。”

你看,你把这些点,从底层到上层,一个个说出来,面试官一听,就知道,你不仅会用 Redisson,还懂它的原理,懂它的优化,这时候,offer 不就到手了吗?

Redisson 作为 Java 生态中最强大的 Redis 客户端,它的高性能,不是凭空来的,是每一个细节的优化,从网络层到应用层,从连接池到分布式锁,每一个点,都做了极致的优化,这就是为什么,它能成为现在最流行的 Redis 客户端,也是为什么,面试官这么爱问它的原理。

希望这篇文章,能帮你在面试的时候,搞定 Redisson 的问题,当场拿到 offer。

参考资料

[1] 每日 Java 面试场景题知识点之 - Redisson 核心价值与优化点详解. CSDN 博客,2026. [2] Redisson 学习专栏 (五): 源码阅读及 Redisson 的 Netty 通信层设计. CSDN 博客,2026. [3] Redisson 分布式锁自动续期:设计模式、源码原理与方案对比. CSDN 博客,2026. [4] 45 倍性能提升?Redisson 本地缓存 Map 与普通 Map 深度对比及实战指南. CSDN 博客,2026. [5] 超实用 Redisson 集群性能优化实战:从卡顿到飞一般的体验. CSDN 博客,2026. [6] 超实用 Redisson 优化指南:连接池与 Netty 线程调优让你的系统性能飙升 300%. CSDN 博客,2026. [7] Redisson 分布式锁的实现原理与加锁机制 (含可视化). CSDN 博客,2026. [8] 主流 Java Redis 客户端深度对比:Jedis、Lettuce 与 Redisson 性能特性全解析. CSDN 博客,2026. [9] Redisson 官方文档:Configuration. Redisson 官网,2026. [10] Redisson 官方文档:Pipelining. Redisson 官网,2026.

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

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

立即咨询