客户端通信

每个 Redis 客户端(以下简称”Client”)都有多个状态属性,而理解和分析这些属性,对于我们设计 Redis 键空间和运营管理都有帮助。使用 redis client 命令可查看当前 Redis 实例的所有客户端;每行数据对应一个客户端。

127.0.0.1:6390> client list
id=2 addr=127.0.0.1:53184 fd=8 name= age=33 idle=24 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=client
id=3 addr=127.0.0.1:53190 fd=7 name= age=2 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=32768 obl=0 oll=0 omem=0 events=r cmd=client

以上为两个客户端,每个包含 18 个字段属性;其中属性的基本含义此处简单说明,后文会对重启指标深入分析。

id:客户端唯一标识, 每新创建一个连接就自增1;redis重启后重置1。
addr: 客户端源ip:port;用于分析异常的客户端,定位是由哪个服务器哪个进程引起的; 如id=2的客户端 netstat -anp | grep 53184
fd: socket的文件描述符;数值同lsof的FD字段相同
name: 客户端的名字,默认不会设置,一般用处不大。可手动执行[clientsetname](http://redis.io/commands/client-setname)
age: 客户端存活的秒数
idle: 空闲的秒数;用于回收客户端和分析大量连接时有用
flages:客户端类型的标志, 共13种,常用的几种:N(普通客户端),M(master),S(slave),O(执行monitor)
db:客户端当前使用的database序号
sub/psub: 快订阅的频道/模式数
multi:当前事务中已执行命令个数
qbuf: query buffer的字节数 重要
qbuf-free: query buffer的剩余字节数
obl:定长Output buffer的使用字节数
oll:可变大小output buffer的对象个数
omem:可变大小output buffer的内存使用字节数 重要
events: 文件描述符事作件(r/w)
cmd:客户端最近一次执行的命令,不包含参数

Buffer

Query Buffer

每个 Client 都有一个 query buffer(查询缓存区或输入缓存区), 它用于保存客户端的发送命令,redis server 从 query buffer 获取命令并执行。每个客户端 query buffer 自动动态调整使用内存大小的,范围在 0~1GB 之间;当某个客户端的 query buffer 使用超过 1GB, server 会立即关闭它,为避免过度使用内存,触发 oom killer。query buffer 的大小限制是硬编码的 1GB,没法控制配置参数修改。

server.h#163
/* Protocol and IO related defines */
#define PROTO_MAX_QUERYBUF_LEN (1024*1024*1024) /* 1GB max query buffer. */

用 client list 命令,观察 qbuf 和 qbuf-free 两个字段,就是 client query buffer 使用内存大小。 如下示例(省去部分字段):

127.0.0.1:6390> client list
id=169 qbuf=128679888 qbuf-free=425984 obl=0 oll=0 omem=0 events=r cmd=NULL
id=171 qbuf=128679888 qbuf-free=425984 obl=0 oll=0 omem=0 events=r cmd=NULL
id=218 qbuf=128679888 qbuf-free=425984 obl=0 oll=0 omem=0 events=r cmd=NULL
id=151 qbuf=128696272 qbuf-free=425984 obl=0 oll=0 omem=0 events=r cmd=NULL

Query Buffer 过载

模拟 100 个客户端,连续写入大小为 500MB(生产建议小于 1KB)的 Key; redis server 设置 maxmemory 为 4gb,但 redis 实际已用内存 43gb(见 used_memory)。结论是 query buffer 使用内存不受 maxmemory 的限制,这 BUG 已经提给官方, 如不能限制 redis 使用的内存量,很易导致 redis 过度使用内存,无法控制出现 oom。

127.0.0.1:6390> info memory
# Memory
used_memory:46979129016
used_memory_human:43.75G
used_memory_rss:49898303488
used_memory_rss_human:46.47G
used_memory_peak:54796105584
used_memory_peak_human:51.03G
total_system_memory:134911881216
total_system_memory_human:125.65G
maxmemory:4294967296
maxmemory_human:4.00G
maxmemory_policy:allkeys-random
mem_fragmentation_ratio:1.06
mem_allocator:jemalloc-4.0.3
## 当client断开后,rss会马上释放内存给OS

query buffer 占用内存,会计入 maxmemory, 如果达到 maxmemory 限制,会触发 KEY 的 LRU 淘汰或无法写入新数据。

127.0.0.1:6390> set a b
(error) OOM command not allowed when used memory > 'maxmemory'.

我们在程序中要避免 Query Buffer 过度使用,可以参考如下建议:

  • 禁用大 KEY,尽量保证 key 小于 1KB; 虽 redis 支持 512MB 大小 string。

  • 监控 redis 内存使用,如果忽高忽低,极有可能 query buffer 引起

  • 核心 Redis 集群定期收集 client list 并分析 qbuf 的使用量

  • 建议官方提供 query buffer size 的设置参数,以保证过载保护

Output Buffer

客户端输出缓存区:执行命令所返回的结果会保存到 output buffer,返回给客户端。每个客户端都有 2 个 query buffer:

  • 静态定长 16KB 的缓存区;主要快速存储返回比较小的结果;如简单的 get 等

  • 动态大小缓冲区;存储返回较大的结果,如大的集合类型:set/list/hash

    因为静态的 buffer,一般无性能和风险影响,这里简单介绍。

#define PROTO_REPLY_CHUNK_BYTES (16*1024) /* 16k output buffer */
/* With multiplexing we need to take per-client state.
* Clients are taken in a linked list. */
typedef struct client {
uint64_t id; /* Client incremental unique ID. */
redisDb *db; /* Pointer to currently SELECTed DB. */
robj *name; /* As set by CLIENT SETNAME. */
sds querybuf; /* Buffer we use to accumulate client queries. */
list *reply; /* List of reply objects to send to the client. */
/* Response buffer */
int bufpos;
char buf[PROTO_REPLY_CHUNK_BYTES];
} client;

我们常说的 output buffer 都是指“动态大小的输出缓冲区”。和 qeury buffer 不同,output buffer 提供配置参数”client-output-buffer-limit”设置 buffer 的使用大小。下面是 limit 的设置格式:

client-output-buffer-limit normal 10mb 5mb 60
client-output-buffer-limit slave 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60

redis 对 3 种不同客户端类型,可设置对应的 buffer limit 规则

  • normal: 普通的客户端

  • slave: 从库复制,连接到主库的客户端

  • pubsub: 发布/订阅客户端

设置的 limit 规则 3 个值: hard limit size, soft limit size, soft limit second;只要客户端使用 output buffer 内存大小超过 hard limit 限制,redis 会立即关闭此客户端;使用 buffer 内存大小超过 soft limit,并且持续 soft limit 秒数,redis 也会立即关闭此客户端。被关闭客户端信息会打印到 redis 日志文件中,格式如下:

569:M 18 Jun 21:12:57.775 # Client id=972 addr=127.0.0.1:57934 fd=107 name= age=2 idle=0
flags=O db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=366 omem=10492208 events=rw cmd=monitor
scheduled to be closed ASAP for overcoming of output buffer limits.

要查看 output buffer 使用,主要查开 client list 的 obl(静态定长 buffer)。omem: 当前客户端使用 output buffer 的内存字节数,如下客户端执行 monitor 命令(cmd=monitor), 已使用 buffer 内存是 10492208,超过 normal 的 hard limit 10mb,所以被 redis 关闭。

id=972 addr=127.0.0.1:57934 idle=0 flags=O db=0 qbuf=0 qbuf-free=0 obl=0 oll=366 omem=10492208 events=rw cmd=monitor

另外 output buffer 受 maxmemory 的限制,基本不会超过 maxmemory 设置值。因为 output buffer 是每个客户端都有,如使用不当,每个占用 1mb * 10000 clients 就约使用 10G 内存; 所以要有效限制程序滥用。

  • 对于 normal 限制尽量小,可避免程序过度使用 output buffer.

  • 监控 redis used_memory 如果抖动严重,极有可能

  • 增加 slave 的 limit 限制,避免 slave 同步线程被杀,导致无限循环同步数据;且 slave 线程和挂载的 slave 个数相同,理论只有几个

  • 禁止生产环境使用 monitor 命令,在高 QPS 环境下,monitor 很快会产生 output query 使用