bigywater.tech
Redis · 112

Redis 网络缓冲区

Redis 为每个连接维护两块缓冲:读请求的查询缓冲和写回复的输出缓冲。它们平时很小,但可以在一个连接上涨到很大。这篇用 CLIENT LIST 里的字段把两块缓冲各自「怎么涨」量出来,并发现一个反直觉的细节:查询缓冲是按客户端声明的大小预留的。

105 MB
客户端不读取时,100 个 1 MB 的 GET 回复堆积成的输出缓冲(omem)
52 MB
声明 50 MB 的 bulk、只发 10 MB 时该连接占用的内存
8.5 KB
空闲连接的平均内存(200 个连接,mem_clients_normal ÷ 连接数)

CLIENT LIST 里的缓冲字段

实测 · 本机执行输出
  qbuf       0
  qbuf-free  0
  argv-mem   0
  multi-mem  0
  obl        0
  oll        0
  omem       0
  tot-mem    2304
  rbs        1024
  rbp        0
  tot-net-in 14
  tot-net-out 7
  (qbuf 查询缓冲、obl 固定输出缓冲字节、oll 输出链表节点数、omem 输出缓冲总内存、rbs/rbp 读缓冲大小与峰值、tot-mem 整个客户端占用)

qbuf / qbuf-free 是查询缓冲的已用与空闲;argv-mem 是已解析出来的参数占用;obl / oll / omem 描述输出缓冲;rbs / rbp 是读缓冲的当前大小与峰值;tot-mem 是整个连接占用的内存。空闲连接总共只有 2 KB 左右。(obl 与 oll 各自的确切含义——固定缓冲字节与链表节点数——我没有单独验证。)

输出缓冲:未读的回复线性堆积

实测 · 本机执行输出
  已发送   0 个 GET(每个回复 1 MB),客户端不读:omem =    0.0 MB   (obl=0 oll=0)
  已发送   1 个 GET(每个回复 1 MB),客户端不读:omem =    1.0 MB   (obl=47 oll=0)
  已发送   5 个 GET(每个回复 1 MB),客户端不读:omem =    5.2 MB   (obl=235 oll=0)
  已发送  20 个 GET(每个回复 1 MB),客户端不读:omem =   21.0 MB   (obl=940 oll=0)
  已发送 100 个 GET(每个回复 1 MB),客户端不读:omem =  104.9 MB   (obl=987 oll=1)

客户端只发 GET 而不读回复(并把自己的接收缓冲调到 4 KB),服务器为每个未读的 1 MB 回复多缓冲约 1 MB:1 个 → 1.0 MB,20 个 → 21 MB,100 个 → 105 MB。normal 类客户端默认没有输出缓冲上限,所以这会一直涨到你设置限制为止(第 113 篇)。

查询缓冲:按声明大小预留

实测 · 本机执行输出
  qbuf = 10.5 MB,qbuf-free = 42.0 MB,argv-mem = 0.0 MB,tot-mem = 52.4 MB

发 SET k,声明 value 长度 52428800 字节(50 MB),只发送 10 MB 后不再发送:qbuf = 10.5 MB,qbuf-free = 42.0 MB,合计 52.4 MB。也就是说,服务器在收到头部之后就为声明的 50 MB 预先分配了缓冲,而不是随实际收到的数据增长。所以「声明一个很大的 bulk」本身就能让服务器占内存,上限由 proto-max-bulk-len 决定(默认 512 MB;本实验里调成了 100 MB)。

连接的基础开销

实测 · 本机执行输出
  200 个空闲连接:connected_clients = 202,mem_clients_normal = 1.76 MB(平均每个连接 8.5 KB)

200 个空闲连接:mem_clients_normal 1.76 MB,平均每个连接约 8.5 KB。(第 114 篇用 used_memory 的变化量得到 1000 个连接每个约 17 KB,口径不同:那里包含了连接相关的其它分配。)

分步演示:两块缓冲

Redis 网络缓冲区
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 / Redis 8.10.2 独立实例(本机回环端口,save 关闭),redis-cli 风格输出由自写的 RESP 客户端打印;计时用 time.perf_counter,数字每次运行会有小幅抖动。缓冲数据来自 CLIENT LIST;不读取的客户端是原始 socket,接收缓冲设为 4 KB
    • 没有验证:obl/oll 的确切语义、多线程 I/O 开启后的缓冲行为、TLS 连接的额外缓冲、大量小 key 的 pipeline 下的输出缓冲增长
    京ICP备2026067502号-1京公网安备11010502063487号