根本原因是rbatch默认启用redis pipeline,将多个命令压缩为单次网络请求,避免千级key下rtt逐次放大;同时复用netty事件循环处理响应,减少线程阻塞,并返回rfuture。

为什么 RBatch.executeAsync() 比多次 sync 调用快得多
根本原因不是“异步”本身,而是 RBatch 默认启用 Redis Pipeline(管道),把多个命令压缩成单次网络请求发送。传统逐条调用 set()、hset() 等同步方法,每条命令都经历一次 RTT(网络往返),在千级 Key 场景下,延迟直接被放大上千倍。
而 executeAsync() 不仅打包命令,还复用 Netty 的事件循环处理响应,避免线程阻塞;它返回的是 RFuture<batchresult>></batchresult>,真正做到了「发完就走」。
- 不启用 Pipeline 时:1000 次操作 ≈ 1000 × RTT(假设平均 2ms → 2s)
- 启用
RBatch+executeAsync():1 次 RTT + 批量解析(通常 - 注意:
execute()是同步阻塞的,虽仍走 Pipeline,但会卡住当前线程,不适合高并发写入场景
RBatch 异步执行必须显式获取每个操作的返回值
RBatch 不会自动帮你聚合或解包结果。你添加的每个 *Async() 方法(如 putAsync()、addAsync())都会返回一个 RFuture,这些 future 必须从 BatchResult 中手动提取,否则你根本不知道哪条失败了。
常见错误是只调用 batch.executeAsync() 却忽略后续结果解析,导致异常静默、数据不一致。
- 正确做法:调用
result.getResponses()获取按添加顺序排列的List<object></object> - 每个元素对应一个操作的返回值,类型取决于原始命令(例如
getAtomicLong(...).incrementAndGetAsync()返回Long,getSet(...).addAsync()返回Boolean) - 若某操作抛异常(如序列化失败、Key 过长),对应位置为
null,需结合result.getResponses().get(i)和result.getFailedCommands()定位
大批量 Key 写入时,batch size 和 connectionPoolSize 必须协同调整
单个 RBatch 不宜塞入上万条命令——Redis 单次请求有协议和内存限制(默认 proto-max-bulk-len 通常为 512MB,但实际建议控制在几 MB 内)。同时,连接池若太小,大量 executeAsync() 会排队等待连接,反而抵消 Pipeline 带来的收益。
- 推荐 batch size:200–500 条命令/批(具体看单条命令数据大小,如大批量
hset字段建议更小) -
connectionPoolSize至少设为预期并发批次数 × 1.5,例如预估峰值每秒发起 20 批,则设为32或更高 - 务必检查
idleConnectionTimeout和connectTimeout,避免因连接空闲回收导致重连开销(参考配置项:connectionMinimumIdleSize、timeout)
混合数据结构操作时,RBatch 仍能保持原子性吗
不能。Redis 的 Pipeline 本身不提供跨命令原子性保证,RBatch 只是客户端层面的批量发送封装,底层仍是多条独立 Redis 命令。如果其中某条失败(如 set 成功但后续 lpush 因 key 类型冲突失败),前面的操作已生效,无法回滚。
需要强一致性时,必须改用 Lua 脚本封装逻辑,或拆分为事务性子流程(例如先校验再批量写,失败时用补偿操作清理)。
- 适用场景:日志记录、缓存预热、统计计数等允许最终一致的批量写入
- 禁用场景:资金转账、库存扣减等要求 ACID 的操作,应使用
RLock+ 单条事务或 Lua - 小技巧:可在同一批中插入
exists或type校验命令,提前发现类型冲突,减少无效写入
真正容易被忽略的是:executeAsync() 返回的 RFuture 若未被监听或 join,其异常会被 Netty 事件循环吞掉,既不打印也不传播——这会让批量失败变成“静默丢数据”。务必对每个 future 做 handle() 或 onComplete() 处理。











