executepipelined要求回调返回null,故不可用opsforvalue().set();应改用redisconnection原生命令或sessioncallback,且需手动处理序列化、分批(每批1000~5000条)、过期时间用psetex()。

直接用 executePipelined 会报错:返回值必须为 null
很多同学一上来就照着文档写:redisTemplate.executePipelined(...),然后在回调里调用 opsForValue().set(),结果发现抛出 InvalidDataAccessApiUsageException,提示“Callback cannot return a non-null value”。这是因为 executePipelined 要求回调函数的返回值必须是 null,而 opsForValue().set() 内部会尝试返回命令执行结果(比如 OK),触发校验失败。
真正能用的路径只有两条:
- 用
RedisConnection原生命令(推荐):绕过RedisOperations封装,直接调用connection.stringCommands().set()等底层方法 - 用
SessionCallback包裹多个opsForXxx()调用:它不校验返回值,但注意——它内部仍是通过RedisConnection实现的,只是做了封装
stringCommands().set() 和 opsForValue().set() 的序列化行为不同
用原生 connection.stringCommands().set(key.getBytes(), value.getBytes()) 时,你传的是原始字节数组,RedisTemplate 的序列化器(比如 StringRedisSerializer 或 GenericJackson2JsonRedisSerializer)完全不生效。这意味着:
- key 和 value 必须自己确保是 UTF-8 编码的字节,否则 Redis 里存的是乱码
- 如果你的 key 是中文或带特殊字符,
new String(keyStr.getBytes(), "UTF-8")要和写入时一致,否则get不出来 - 如果 value 是对象,不能直接传
object.toString().getBytes(),得手动 JSON 序列化(比如用ObjectMapper.writeValueAsBytes())
而 opsForValue().set() 会自动走 valueSerializer,但如前所述,它不能在 executePipelined 的 RedisCallback 中直接使用。
百万数据分批写入,别一次性塞进一个 pipeline
Redis 单次 pipeline 承载能力有限,实测在 10MB 响应体或 10 万条命令左右就容易触发超时或 OOM。尤其当你的 value 较大(比如含 JSON 字段),实际能塞的命令数会更少。强行一次发百万条,大概率出现:
使用位于 ci-tools.xrow.de 的 CI Tools 组件目录构建和维护 GitLab CI/CD 流水线,适用于创建或修复 .gitlab-ci.yml 文件,选择合适的组件。
java.net.SocketTimeoutException: Read timed outio.lettuce.core.RedisCommandTimeoutException- Lettuce 连接池耗尽、连接被复位
稳妥做法是按 1000~5000 条/批切分,每批单独调用一次 executePipelined:
int batchSize = 2000;
for (int i = 0; i batch = keys.subList(i, end);
redisTemplate.executePipelined((RedisCallback<object>) connection -> {
for (String key : batch) {
connection.stringCommands().set(
key.getBytes(StandardCharsets.UTF_8),
("value:" + key).getBytes(StandardCharsets.UTF_8)
);
}
return null;
});
}</object>
注意:不要在循环里反复 new ObjectMapper 或拼接字符串,批量构造前预热好编码器、复用 byte[] 缓冲区可再省 10%~15% 时间。
别把 pipeline 当事务,过期时间得手动加
pipeline 不是 MULTI/EXEC,它没有原子性,也不支持命令级条件控制。最常踩的坑是——想给每个 key 设置不同过期时间,却发现 set(key, value, timeout, unit) 没法在 pipeline 里用。
因为 connection.stringCommands().set() 只有无过期版;而 connection.setEx() 才支持过期,但它要求 key/value 都是 byte[],且 timeout 是 int 秒(不是 TimeUnit):
- 若需毫秒级精度或非整数秒,得用
connection.pSetEx()(单位是毫秒) - 若过期时间各不相同,必须在循环里逐个调用
pSetEx(),无法合并成一条命令 - 若所有 key 过期时间相同,优先用
pSetEx(),比先set()再expire()少一次命令开销
这个细节在百万级写入时会被放大:多一次命令,就多一次 Redis 内部处理开销,整体耗时可能多出 8%~12%。










