客户端连接与 RESP 协议
本教程共 40 篇 · 第 32 篇 · 更新于 2026-08-02
32. 客户端连接与 RESP 协议
Redis 之所以快,除了数据放在内存里,另一个常被忽略的原因是它的网络层设计:用单线程、非阻塞 I/O 多路复用来处理成千上万个客户端连接。本章我们先看清一个客户端从「连上」到「通信」的全过程,再深入它说话用的语言——RESP 协议,最后落到生产中最常被问到的「最多能连多少」「怎么排查连接」这类问题上。
本节目标
- 理解 Redis 接收连接时做了哪几件事,以及「单线程为何能扛高并发」的底层逻辑。
- 看懂 RESP2 的五种基础类型在线路上长什么样,并能识别 RESP3 相比 RESP2 多了什么。
- 掌握
maxclients的含义、默认值与修改方式,知道连接数上限由操作系统文件描述符决定。 - 熟练使用
CLIENT LIST、CLIENT SETNAME、CLIENT KILL等命令排查与管控连接。 - 能用
redis-cli的-2/-3选项在 RESP2 与 RESP3 协议之间切换并观察差异。
连接是怎么建立的
当 Redis 在配置好的 TCP 端口(或启用的 Unix socket)上接受一个新的客户端连接时,会依次做三件事:
- 把客户端 socket 设为非阻塞模式。因为 Redis 的网络事件处理采用的是非阻塞多路复用模型,所有连接共享同一个事件循环,不会为某个连接单独开线程阻塞等待。
- 设置
TCP_NODELAY选项,禁用 Nagle 算法。这样可以避免小数据包被合并导致的发送延迟,保证命令尽快到达对端。 - 创建一个可读文件事件,一旦 socket 上有新数据可读,事件循环就会把客户端发来的查询收集起来交给命令执行器。
正因为「一个线程 + 事件循环 + 非阻塞 socket」这套组合,Redis 才能用单线程顺序处理海量连接上的命令,而不需要为每个连接分配一个操作系统线程。代价是:单条命令不能执行太久(否则会阻塞整个事件循环),这也是为什么官方反复强调「避免大 key、避免慢查询」。
我们可以通过 INFO clients 实时观察当前连接情况:
127.0.0.1:6379> INFO clients
# Clients
connected_clients:1
cluster_connections:0
maxclients:10000
blocking_clients:0
其中 connected_clients 是当前已建立的客户端数量,maxclients 是允许的最大值(见下文),blocking_clients 是正在执行阻塞式命令(如 BLPOP、XREAD 带 BLOCK)的客户端数。
最大连接数 maxclients
在 redis.conf 里有一个 maxclients 配置项,决定最多允许多少个客户端同时连上来。它的默认值是 10000,但这个值实际上受操作系统允许的最大文件描述符数约束——每个客户端连接都对应一个文件描述符。如果系统的 ulimit 比 maxclients 小,Redis 会按实际可用文件描述符来限制连接数(Redis 启动时会打印相关警告)。
查看当前生效值:
127.0.0.1:6379> CONFIG GET maxclients
1) "maxclients"
2) "10000"
可以在启动时通过命令行参数调整,也可以在配置文件里改:
redis-server --maxclients 100000
提示:把
maxclients调得很大之前,先确认操作系统的nofile上限足够(例如ulimit -n)。Redis 8.x(最新稳定版)对文件描述符的使用依旧遵循这一规则,光改配置不改系统限制是无效的。
客户端管控命令
Redis 提供了一组 CLIENT 子命令,用来观察、命名和切断连接,运维排查时非常有用。
CLIENT LIST —— 看谁连着
127.0.0.1:6379> CLIENT LIST
id=3 addr=127.0.0.1:51820 fd=8 name=worker-a age=42 idle=3 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 tot-net-in=1042 tot-net-out=8832 events=r cmd=ping user=default
每一行是一个客户端,字段含义关键的有:addr(客户端地址)、name(连接名)、age(已连接秒数)、idle(空闲秒数)、db(当前选中的库)、cmd(最近一条命令)、omem(输出缓冲区内存,排查「客户端输出缓冲区暴涨」时重点看)。
CLIENT SETNAME / CLIENT GETNAME —— 给连接起名
给当前连接起一个可读的名字,便于在 CLIENT LIST 里区分业务:
127.0.0.1:6379> CLIENT SETNAME order-service
OK
127.0.0.1:6379> CLIENT GETNAME
"order-service"
CLIENT KILL —— 掐掉某个连接
当某个客户端异常占用或需要强制下线时,可以按地址、名称或 ID 杀掉它:
127.0.0.1:6379> CLIENT KILL addr 127.0.0.1:51820
OK
CLIENT PAUSE —— 暂停所有客户端
以毫秒为单位挂起所有客户端,常用于需要在不影响数据一致性的前提下做某些运维操作(如切换、迁移)的场景:
127.0.0.1:6379> CLIENT PAUSE 5000
OK
5000 毫秒内所有客户端请求都会被服务端缓存排队,超时后恢复。
RESP 协议:客户端与服务端说的语言
RESP(REdis Serialization Protocol)是 Redis 客户端与服务端之间通信的协议。它设计目标很简单:对人类可读、解析简单、二进制安全。理解它,有助于你看懂 redis-cli 之外的「底层发生了什么」,也为后续理解客户端库、管道、Pub/Sub 的推送机制打基础。
RESP2 的五种基础类型
RESP2 用「第一个字节」标识类型,每条消息以 \r\n 结尾:
| 类型 | 首字节 | 示例(线路上的字节) | 含义 |
|---|---|---|---|
| 简单字符串 | + | +OK\r\n | 无二进制内容的成功响应 |
| 错误 | - | -ERR wrongtype\r\n | 错误信息 |
| 整数 | : | :1000\r\n | 整数结果(如 INCR 返回值) |
| 批量字符串 | $ | $5\r\nhello\r\n | 带长度前缀的二进制安全字符串 |
| 数组 | * | *2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n | 有序的元素集合 |
看一个真实的交互。客户端向服务端发送 GET foo 时,线路上实际发的是:
*2\r\n$3\r\nGET\r\n$3\r\nfoo\r\n
也就是「一个包含两个批量字符串的数组,第一个是 GET,第二个是 foo」。如果 foo 的值是 bar,服务端回复:
$3\r\nbar\r\n
如果是整数命令,比如 INCR counter,回复以 : 开头:
:42\r\n
错误则以 - 开头,后面跟错误类型与消息:
-WRONGTYPE Operation against a key holding the wrong kind of value\r\n
$ 后面的数字表示接下来字符串的字节长度;特殊值 $-1\r\n 表示「空」(nil),比如对一个不存在的 key 执行 GET 时返回的就是这个。
RESP3:更丰富的类型与推送能力
RESP2 只有上面五种类型,很多返回结构(比如哈希、集合)只能用「数组」来近似表示,客户端拿到后还得靠约定去「猜」哪段是字段、哪段是值。从 Redis 6.0 起引入 RESP3,在保持向后兼容的前提下新增了多种类型,到了 Redis 8.x(最新稳定版)已是默认推荐协议。新增的关键类型包括:
%(Map,映射):直接表示键值对集合,例如HGETALL在 RESP3 下返回的就是带类型的 Map,而不再是扁平数组。~(Set,集合):表示无序去重集合。>(Push,推送):用于服务端主动推数据,最典型的是 Pub/Sub 的消息——在 RESP3 下订阅消息以 Push 类型送达,不再占用普通回复通道。,(Double,双精度浮点)、#(Boolean 布尔,#t/#f)、_(Null 空)、!(Blob error 二进制错误)、=(Verbatim string 带格式字符串)、((Big number 大整数)。
你可以用 redis-cli 亲身体验两种协议的区别。默认 redis-cli 使用 RESP3(新版本),如果想强制退回 RESP2,用 -2;强制使用 RESP3 用 -3:
$ redis-cli -2
127.0.0.1:6379> HGETALL user:1
1) "name"
2) "tom"
3) "age"
4) "20"
$ redis-cli -3
127.0.0.1:6379> HGETALL user:1
1# "name" => "tom"
2# "age" => "20"
注意在 RESP3 模式下,HGETALL 的输出预览把「字段 ⇒ 值」的映射关系展示得更清晰,这背后就是 Map 类型在起作用。绝大多数现代客户端库(详见第 33、34 章)已默认协商 RESP3,对应用层透明,你通常无需关心,但理解它有助于排查协议级别的兼容问题。
提示:RESP2 仍是众多老版本客户端的默认协议,Redis 8.x(最新稳定版)服务端两种都支持。若你的某个旧客户端连接后行为异常(例如订阅收不到消息),优先确认它协商的是哪种协议版本。
小结
- Redis 用单线程事件循环 + 非阻塞多路复用处理所有连接,每个连接对应一个文件描述符。
maxclients默认 10000,但实际上限受操作系统文件描述符限制;调大前先调系统 ulimit。CLIENT LIST/SETNAME/GETNAME/KILL/PAUSE是排查连接问题、区分业务、紧急下线客户端的核心命令。- RESP 是 Redis 的通信协议:RESP2 有五种基础类型(+ - : $ *),RESP3 增加了 Map、Set、Push、Double、Boolean 等,使返回结构更精确、Pub/Sub 推送更高效。
redis-cli的-2/-3可在两种协议间切换,方便对比观察。
常见误区
- 误以为「单线程」=「只能处理一个连接」。实际上单线程指的是命令执行是串行的,但连接接入、网络读写由事件循环并发处理,所以能同时维持上万连接,瓶颈在单条命令的耗时而非连接数。
- 误以为
maxclients配多大就能连多少。若系统nofile上限更小,Redis 实际能接受的连接数会被系统限制截断。 - 看到
HGETALL在两种协议下输出格式不同就以为是 bug。这其实是 RESP2(扁平数组)与 RESP3(Map 预览)的正常差异。
提示:官方 redis.io 下载页另将 8.8 标注为 “Latest stable”,而 GitHub 上的最新发布 tag 为 8.10.0;二者同属 8.x,命令与协议层面差异对本教程影响极小,本教程统一以 Redis 8.x(最新稳定版)表述。