最有效的两个动作是启用连接池和使用pipeline批量操作;实测max-active设为100后QPS从217跃升至2580,executePipelined可将100条写入耗时从100ms+压至3–5ms。

直接提升吞吐量最有效的两个动作是:启用连接池 + 使用 pipeline 批量操作。其他优化(如序列化、键设计)属于必要但边际收益递减的环节。
RedisTemplate必须配Lettuce连接池
默认配置下,LettuceConnectionFactory不启用连接池,每次操作都新建连接,QPS卡在 200 左右。实测将 max-active 设为 100 后,相同压测场景吞吐量从 217 QPS 跃升至 2580 QPS。
关键配置项必须显式声明:
-
spring.redis.lettuce.pool.max-active=100(别用默认值 8) -
spring.redis.lettuce.pool.max-idle=20(避免空闲连接过多占用内存) -
spring.redis.lettuce.pool.min-idle=5(冷启动时保底可用连接) -
spring.redis.timeout=2000(防止个别慢请求拖垮整个连接池)
注意:若项目里手动声明了 LettuceConnectionFactory Bean,必须同步调用 setPoolConfig(),否则 application.properties 中的 pool 配置不生效。
高频写入必须用 executePipelined
单条 opsForValue().set() 走一次网络往返,延迟约 0.8–1.2ms;100 条串行调用就是 100ms+。改用 executePipelined 后,100 条合并为 1 次请求,实测耗时压到 3–5ms。
典型误用:
- 用
opsForValue().multiSet()替代 pipeline —— 它只支持 Map 入参,且不支持带 TTL 的 set - 在 pipeline 里混用不同数据结构操作(比如同时 set 和 hSet)—— Lettuce 会拒绝执行,抛
InvalidDataAccessResourceUsageException - pipeline 内部未做异常捕获,导致某条失败后整批静默丢弃
正确写法示例(带 TTL 的批量写):
redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
for (Map.Entry<String, String> entry : data.entrySet()) {
connection.stringCommands().set(
entry.getKey().getBytes(),
entry.getValue().getBytes(),
Expiration.seconds(3600),
RedisStringCommands.SetOption.UPSERT
);
}
return null;
});
序列化器选 Jackson2JsonRedisSerializer 而非 JDK 默认
默认的 JdkSerializationRedisSerializer 生成二进制内容不可读、跨语言不兼容、反序列化慢,还会让 Redis RDB/AOF 文件体积膨胀 3–4 倍。
必须替换为 JSON 序列化,并注意三点:
-
template.setKeySerializer(new StringRedisSerializer())—— key 必须是字符串,不能序列化 -
template.setValueSerializer(new GenericJackson2JsonRedisSerializer())—— 比Jackson2JsonRedisSerializer更健壮,能处理泛型擦除 - 务必配置
ObjectMapper可见性:mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY),否则 private 字段无法序列化
不设 HashKeySerializer 和 HashValueSerializer 会导致 opsForHash() 写入乱码,这个坑很多人踩过。
避免在高并发路径上用 opsForValue().get() + 业务逻辑 + set() 组合
这种“读-改-写”模式本质是竞态条件,吞吐量上不去还容易覆盖数据。例如计数器场景,直接用 opsForValue().increment();分布式锁场景,用 opsForValue().setIfAbsent() + Lua 脚本释放。
更隐蔽的问题是:某些业务代码在事务内反复调用 get(),却没意识到每次都是独立 Redis 请求。应优先考虑用 opsForValue().multiGet() 一次性拉回多个 key,或改用 Hash 结构把相关字段聚合成一个 key。
真正难调的点不在代码写法,而在于 pipeline 的边界判断——它适合「确定性批量」,不适合「边查边判再写」的动态逻辑。这类场景得退回到 Lua 或用 Redisson 封装。


















