Redis 流水线把多条命令装进一次往返
Redis 流水线允许客户端连续发送多条命令,不必等前一条回应以后再发下一条。它减少了请求与响应之间的等待,也能分摊套接字输入输出开销。但服务端仍需要执行每条命令并生成回复;批处理不代表这些命令并行运行,也不自动形成事务边界。 少等几次,而不是少执行几条 同步循环不断重复发送一条、等待一条。流水线则先发送一个序列,再按对应顺序收取回复。把一批近似理解为一次往返,可以帮助估算延迟,但不能据此承诺只有一个 TCP 数据包、一次系统调用,或所有回应在同一时刻到达。实际传输仍受缓冲区、数据量和协议处理影响。 收益也不只出现在远程链路。把套接字读写集中起来,可以减少系统调用和调度开销,所以即使本地连接,也可能在不分片的情况下提高实际吞吐。反过来,流水线消除不了昂贵命令本身的工作和带宽上限。应先定位真实瓶颈,而不是看到单节点就断言下一步必须加分片。 普通流水线没有“不被插队”的保证。其它客户端可能在批次的命令之间执行操作,但具体某一次运行也可能碰巧全部连续完成。观察到一次连续执行,并不能把它上升为事务隔离承诺。正确性不能依赖一次测试里没有出现的调度交错。 区分顺序依赖与回复依赖 同一批命令不必完全无关。先 SET 计数器,再 INCR,最后 GET,可以放在一个流水线中,因为每条请求的参数在发送前都已确定;命令顺序本身就表达了服务端的数据依赖。真正无法放进同一个预先构造批次的,是必须根据前面回复计算键名或参数的下一条请求。 这种回复依赖应拆成不同阶段,或使用合适的原生命令、有界脚本。如果应用内的条件判断还需要并发保护,可以考虑 WATCH。MULTI/EXEC 提供执行隔离,却不会在运行期错误之后自动撤销其它已成功命令,这与减少往返是不同的问题。 还要检查客户端默认行为。redis-py 8.1.0 的 pipeline() 默认使用事务,显式传 transaction=False 才是普通流水线。因此拿默认包装器做测量,会同时测到批处理与 MULTI/EXEC。下面不仅写出选项,还读取服务端命令计数,核对实际有没有调用事务指令。 坏掉的样子 普通流水线中一条命令报错,不会取消已经提交的后续命令。错误回复仍占据结果序列中的对应位置,必须逐项检查;仅仅收到一个数组,不表示整个业务动作成功。忽略中间失败的写入,可能留下形状看似完整却不一致的应用数据。 redis-py 默认可能在读完回复后抛出 ResponseError,execute(raise_on_error=False) 则把命令错误放在结果元素里,两者都不会撤销成功写入。连接中断是另一类问题:缺少回应可能让已执行的前缀无法确定。即使从最后收到确认的命令之后继续,也可能重复一条已经执行、只是回应丢了的写入。 因此不能盲目重试递增或其它非幂等效果。即便反复写同一个固定值,如果中途另一个写入方已经更新数据,也需要并发约定来避免覆盖。可靠恢复是业务操作的职责,不是流水线传输优化顺带提供的能力。 批量应同时限制命令条数和字节数。客户端缓冲请求与结果,Redis 也可能暂存客户端还没读取的回应。大负载或慢读取方会增加内存与延迟成本;只按命令数量给出一个通用批量上限,忽略了不同命令和回复大小的差异。 快速验证 需要具备 C 编译器、make、curl、tar,以及 Python 3.10 或更新版本的 POSIX 环境。本次在 macOS、Python 3.14.4、Redis 8.10.1 与 redis-py 8.1.0 上实测。安装步骤只在临时目录编译使用 libc 的 Redis 核心,并将客户端放入虚拟环境,不安装系统服务。 ...