redis:客户端client
学习自:《Redis开发与运维》pdf 247页
简写
C:client,客户端
S:server,服务端
ibuf:输入缓冲区
obuf:输出缓冲区
几个网站:
[1] http://redis.io
[2] http://antirez.com
[3] https://github.com/antirez/redis 源码
1、客户端通信协议
C与S的通信(网络传输)是在TCP协议之上构建的。
C与S端的正常交互协议是RESP(REdis Serialization Protocal,Redis序列化协议),这种协议简单高效,既能被机器解析,又容易被人类识别。例如C端发一条set hello world给S,按照RESP的标准,C端会将其封装为以下格式(每行用\r\n分割):
1 2 3 4 5 6 7 | *3 $3 SET $5 hello $5 world |
S端收到后按照RESP将其解析为set hello world,执行,回复格式:
1 | +OK |
可以看到除了命令和结果外,还包含了一些特殊字符及数字。
下面对这些进行解释
1)发送命令的格式
RESP规定一条命令的格式如下,其中CRLF代表"\r\n":
1 2 3 4 5 6 7 8 | *<参数数量> CRLF $<参数1字节数> CRLF <参数1> CRLF $<参数2字节数> CRLF <参数2> CRLF ... $<参数N字节数> CRLF <参数N> CRLF |
以之前的set hello world为例:
1 2 3 4 5 6 7 | *3 #3个参数 $3 #第一个参数的字节数 SET #第一个参数 SET $5 #第二个参数字节数 hello #第二个参数 hello $5 #第三个参数的字节数 world #第三个参数 world |
需要注意的是,以上只是格式化显示的结果,实际的传输格式为:
1 2 | *3\r\n$3\r\nSET\r\n$5\r\nhello\r\n$5\r\nworld\r\n 2. |
2)返回结果的格式
回复 |
回复结果的首字节 |
适用命令 |
状态 | + | set |
错误 | - | 错误命令 |
整数 | : | incr |
字符串 | $ | get |
多条字符串 | * | mget |

redis-cli只能看到最终的执行结果,因为redis-cli本身就是按照RESP进行结果解析的,所以看不到中间结果。
如果想S端返回真正的结果,可以用nc命令、telnet命令、写一个socket程序进行模拟。例如在用nc时,首先连接到redis:
1 | nc 127.0.0.1 6379 |
状态回复
1 2 | set hello world +OK |
错误回复:某条指令不存在时
1 2 | sethx -ERR unknown command 'sethx' |
整数回复
1 2 | incr counter :1 |
字符串回复
1 2 3 | get hello $5 world |
多条字符串回复
1 2 3 4 5 6 | mget java python *2 $5 jedis $8 redis-py |
在回复字符串或多字符串时,如果有nil,那么会返回$-1。
1 2 3 4 5 6 7 8 9 10 | get not_exist_key $-1 mget hello not_exist_key java *3 $5 world $-1 $5 jedis |
有了RESP提供的发送命令、返回结果的协议格式,各种编程语言就能利用其来实现相应的Redis客户端:
Java有许多优秀的Redis客户端,详见:http://redis.io/clients#java,使用比较广泛的是Jedis;
Python的Redis客户端叫做redis-py。
2、客户端管理
1)客户端API
命令
命令 |
说明 |
client list | 列出所有C端信息 |
client setName xx clent getName |
给C端设置名字(会改变client list中的name属性) 查看当前C端的名字 |
client kill ip:port | kill指定ip和port对应的C端 |
client pause t | 阻塞C端t ms,在此期间C端连接将被阻塞 |
monitor |
监控Redis正在执行的命令 可以监听到其他C端正在执行的命令,并记录详细时间戳 |
详细介绍
①client list
client list命令可以列出与Redis S端相连的所有C端信息,例如下面代码就是在一个Redis实例上执行client list的结果:
1 2 3 | 127.0.0.1:6379> client list<br> id =3 addr=127.0.0.1:47538 laddr=127.0.0.1:6379 fd=9 name= age=21 idle=0 <br>flags=N db=0 sub=0 psub=0 multi=-1 qbuf=26 qbuf- free =40928 argv-mem=10 <br>obl=0 oll=0 omem=0 tot-mem=61466 events=r cmd=client user=default redir=-1<br> id =4 addr=192.168.10.20:58090 laddr=192.168.10.20:6379 fd=10 name= age=3 <br>idle=3 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf- free =0 argv-mem=0 <br>obl=0 oll=0 omem=0 tot-mem=20496 events=r cmd= command user=default redir=-1 |
每行输出代表一个C端信息,下面对这些属性进行说明:
属性 |
说明 |
C端标识 |
|
id |
客户端连接的唯一标识; 随着Redis的连接自增,当Redis重启后置0。 |
addr | C端IP和端口 |
fd |
Socket文件描述符; 与lsof命令中的fd相同,如果fd=-1代表当前C端不是外部C端,而是Redis内部的伪装C端 |
name | C端名字 |
flags | 客户端类型:N M S O x b i d u c A |
C端存活状态 |
|
age | 当前C端已经连接的时间(s) |
idle | 最近一次的空闲时间(s) |
输入缓冲区 input_buf |
|
qbuf | 缓冲区总容量 |
qbuf-free | 缓冲区剩余容量 |
输出缓冲区 obuf |
|
obl | 固定缓冲区的长度 |
oll | 动态缓冲区的当前长度(动态缓冲区的长度是实时变化的) |
omem | 两个缓冲区已使用的字节数 |
其他 |
|
db | 当前C端正在使用的数据库索引 |
sub/psub | 当前C端订阅的频道或者模式数 |
events | 文件描述符(r/w):C端的Socket可读、可写 |
cmd | C端最后一次执行的命令,不含参数 |
一些项的具体介绍
a、输入缓冲区input_buf
Redis为每个C端分配了输入缓冲区,其作用是将C端命令临时保存,同时Redis会从输入缓冲区拉取命令并执行,输入缓冲区为C端发送命令到Redis执行命令提供了缓冲功能。
可以通过qbuf、qbuf-free查询缓冲区的总容量和剩余容量,conf没有提供相应配置来规定每个缓冲区大小,它会根据输入内容大小不同动态调整,只是要求每个C端input_buf大小<1GB,超过之后C端将会关闭(这点只能从源码中看到):
1 2 | /* Protocol and I /O related defines */ #define REDIS_MAX_QUERYBUF_LEN (1024*1024*1024) /* 1GB max query buffer. */ |
输入缓冲设置不当会引发两个问题:
- 一旦某个C端的input_buf>1G,这个C端将被关闭;
- input_buf不受maxmemory控制,假设某个input_buf使用量+已经存储的数据>maxmemory,可能会产生数据丢失、键值淘汰、OOM等情况(如下图)。
此时再用info memory查看,会出现如下情况:
1 2 3 4 5 6 | 127.0.0.1:6390> info memory # Memory used_memory_human:5.00G ... maxmemory_human:4.00G .... |
这里展示了input_buf使用不当造成的危害,那么造成input_buf过大的原因有哪些?
- 主要原因在于Redis处理速度<input_buf的输入速度,并且每次进入input_buf的命令包含了大量bigkey,从而造成了input_buf过大。
- Redis发生了阻塞,短期内不能处理命令——C端输入的命令积压在了input_buf,造成input_buf过大
input_buf异常监控
- 定期执行client list命令,收集qbuf、qbuf-free找到异常连接并分析,最终找到可能出问题的C端;
- 通过info clients模块,找到最大input_buf,这个命令中的client_biggest_input_buf代表最大的input_buf为10MB,超过这个值就报警:
123456
127.0.0.1:6379> info clients
# Clients
connected_clients:1414
client_longest_output_list:0
client_biggest_input_buf:2097152
#11MB
blocked_clients:0
这两种方式有各自的优劣势:
命令 |
优点 |
缺点 |
client list | 精准分析每个C端来定位问题 | 执行速度慢(特别是连接较多时),频繁执行存在阻塞Redis的可能 |
info clients | 执行速度比client list快,过程简单 |
不能精确定位到哪个C端 不能显示所有input_buf的总量,只能显示最大量 |
input_buf出问题的情况比较少,但也要做好防范,在开发中减少bigkey、减少redis阻塞、合理的监控报警。
b、输出缓冲区 obuf
Reids为每个C端分配了obuf,其作用是保存命令的执行结果返回给C端,为Redis与C端的交互返回提供了buffer。
其容量通过配置项client-output-buffer-limit设置,并且obuf做得更加细致,按照C端不同可以分为3种:普通C端、发布订阅C端、slave C端
用法:client-output-buffer-limit <class> <hard limit> <soft limit> <soft seconds>
各项
项 |
说明 |
class |
客户端类型: normal:普通C端 slave:slave C端,用于复制 pubsub:发布订阅客户端 |
hard limit | 如果buffer大于hard limit,C端会立刻关闭 |
soft limit soft seconds |
如果C端使用的obuf超过了soft limit并且持续了soft second秒,C端会立刻关闭 |
和ibuf类似,obuf也不会受到maxmemory限制,如果使用不当同样会造成maxmemory用满,产生数据丢失、KV淘汰、OOM等问题。
实际上的obuf由两部分组成:固定缓冲区(16KB)、动态缓冲区。
固定缓冲区:存储较小的执行结果
动态缓冲区:存储较大的执行结果,如大字符串、hgetall、smembers命令的结果。
在redis.h(4.0版本后现在在server.h)中的结构体Client可以看到两个buffer的实现细节:
1 2 3 4 5 6 7 8 9 10 | typedef struct redisClient { // 动态缓冲区列表 list *reply; // 动态缓冲区列表的长度(对象个数) unsigned long reply_bytes; // 固定缓冲区已经使用的字节数 int bufpos; // 字节数组作为固定缓冲区 char buf[REDIS_REPLY_CHUNK_BYTES]; } redisClient; |
正如上边源码所写,固定缓冲区采用字节数组,动态缓冲区采用list。当固定缓冲区满了之后,会将Redis新返回结果存放在动态缓冲区队列中,队列中的每个对象就是每个返回结果:
obuf的信息在client list指令中的obl、oll、omem中可以查看。
obl=0 oll=4869 omem=133081288代表固定缓冲区长0、动态缓冲区有4869个对象,两个缓冲区共用了133081288B=126M内存。
obuf监控
- 定期执行client list,记录obl、oll、omem找到异常的连接记录并分析,最终找到可能出问题的C端;
- 通过info clients,找到obuf的最大对象数:
1234
connected_clients:502
client_longest_output_list:4869
client_biggest_input_buf:0
blocked_clients:0
其中,client_longest_output_list代表了输出缓冲区list最大对象数。
- 这两种方式优劣势和input buffer相同,这里不再多说。
相比ibuf,obuf出现异常的概率更大,如何预防:
- 进行上述监控,设置阈值,超过阈值及时处理
- 对普通C端的obuf作出限制:
1
client-output-buffer-limit normal 20mb 10mb 120
- 适当增大slave的obuf,如果m节点写入较大,那么slave C端的obuf可能会比较大,一旦slave C端连接因为obuf溢出被kill,会造成复制重连;
- 限制容易让obuf增大的命令,如高并发下的monitor;
- 及时监控内存,一旦发生内存抖动,可能就是obuf过大。
c、客户端存活状态
client list中的age和idle分别代表当前客户端已经连接的时间和最近一次的空闲时间:
1 | ... age=603382 idle=331060 ... |
该记录代表:当前C端连接Redis的时间为603382s,空闲了331060s。
Redis提供了maxclients参数来限制最大客户端连接数,一旦连接数超过maxclients,新的连接将被拒绝,最大连接和当前连接数都可以通过info clients查看。
在实际开发运维时,为了避免C端连接未被及时释放,造成大量idle连接占据着很多连接资源,通常要设置timeout。
d、客户端类型
标识:flag
类型:
②client setName、getName
用法
- client setName xx
- client getName
用途:给C端命名、获取C端名字(都是当前)
说明
- 会使client list中的name项发生变化。
③client kill
用法:client kill ip:port
作用:kill指定ip和port的C端
说明
- 常用于手动kill一些timeout为0产生的长期idle C端
④client pause
用法:client pause t
用途:阻塞C端t ms
说明
- 一个C端阻塞示意图:
- 在一个C端上开启两个连接,其中一个执行pause阻塞10s:
1 2 | 192.168.10.20:6379> client pause 10000 OK |
另一个连接执行ping命令,会发现一共执行了6.98s(从开始执行到获得响应)
1 2 3 | 127.0.0.1:6379> ping PONG (6.98s) |
使用场景
- pause只对普通、发布订阅C端有效,对于主从复制无效,在此期间主从复制仍会正常进行,因此该命令可以用来让主从复制保持一致;
- client pause以一种可控的方式将C端连接从一个Redis节点切换到另一个Redis节点;
- 需要注意的是,在生产环境下,暂停C端的成本很高。
⑤monitor
用法:直接用,无参数,连client前缀都没有
用途:监控Redis正在执行的命令,可以监听到其他C端
例子
开两个连接(一个连接就是一个C端),其中一个执行monitor,另一个执行set hello world,此时运行了monitor的连接会看到另一个连接运行set指令的情况
1 2 3 | 192.168.10.20:6379> monitor OK 1693364802.987665 [0 127.0.0.1:58692] "set" "hello" "world" |
1 2 3 | 127.0.0.1:6379> set hello world OK 127.0.0.1:6379> |
说明
虽然看起来开发和运维人员可以用monitor监听Redis正在执行的命令,但由于每个C端都有自己的obuf,既然monitor能监听到全部命令,一旦Redis并发量过大,monitor所在的C端的obuf会暴涨,瞬间占用大量内存,下图就展示了这一情况:
2)C端相关配置
上一节介绍了部分关于C端的配置,本节将介绍剩余配置
配置 |
说明 |
timeout |
C端空闲的超时时间,一旦idle达到了timeout,C端将被关闭; 如果设置为0就不进行检测 |
maxclients | C端最大连接数 |
tcp-keepalive |
检测TCP连接活性的周期,默认为0,即不进行检测。 如果设置为60,就意味着每60s对它建立的TCP连接进行活性检测,防止大量死连接占用系统资源 |
tcp-backlog | TCP三次握手后,会将接受的连接放入队列中,tcp-backlog就是队列的大小,它在Redis中的默认值为511。 |
3)客户端统计片段
在info clients中有几项:
项 |
说明 |
connected_clients | 当前连接的客户端数量 |
maxclients | 最大允许连接的客户端数量(在conf中设置) |
client_recent_max_output_buffer | 最近output buffer最大占用量 |
client_recent_max_input_buffer | 最近input buffer最大占用量 |
blocked_clients | 正在执行阻塞命令(如blpop、brpop)的客户端个数 |
在info stats中也有两项:
项 |
应用环境 |
说明 |
total_connections_received | C端指标 | Redis启动以来处理的C端连接总数 |
rejected_connections | Redis启动以来拒绝的客户端连接总数,需要重点监控 |
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性