客户端通信协议
Redis制定了RESP(REdis Serialization Protocol, Redis序列化协议) 实现客户端与服务端的正常交互, 这种协议简单高效, 既能够被机器解析, 又容易被人类识别。
示例:客户端发送一条set hello world命令给服务端。按照RESP的标准, 客户端需要将其封装为如下格式(每行用\r\n分隔):
*3 $3 SET $5 hello $5 world
返回:
+OK
RESP格式说明:
发送命令格式
RESP的规定一条命令的格式如下, CRLF代表"\r\n" *<参数数量> CRLF $<参数1的字节数量> CRLF <参数1> CRLF ... $<参数N的字节数量> CRLF <参数N> CRLF
示例解说:以set hello world为例
*3 参数数量为3个,第一行为*3 $3 set有3个字节,第二行为$3 SET 第一个参数set $5 hello有5个字节,第三行为$5 hello 第二个参数hellot $5 world有5个字节,第五行为$5 world 第三个参数world
上面只是格式化显示的结果, 实际传输格式为如下代码:
*3\r\n$3\r\nSET\r\n$5\r\nhello\r\n$5\r\nworld\r\n
返回结果格式
Redis的返回结果类型分为以下五种
·状态回复: 在RESP中第一个字节为"+"。 ·错误回复: 在RESP中第一个字节为"-"。 ·整数回复: 在RESP中第一个字节为": "。 ·字符串回复: 在RESP中第一个字节为"$"。 ·多条字符串回复: 在RESP中第一个字节为"*"。
redis-cli只能看到最终的执行结果, 那是因为redis-cli本身就是按照RESP进行结果解析的, 所以看不到中间结果。
例如执行set hello world, 返回结果是OK, 并不能看到加号:
127.0.0.1:6379> set hello world OK
为了看到Redis服务端返回的“真正”结果, 可以使用nc命令、 telnet命令、 甚至写一个socket程序进行模拟。
示例:
使用nc命令连接redis:nc 127.0.0.1 6379 set hello world +OK sethx -ERR unknown command 'sethx' incr counter :1 get hello $5 world mget java python *2 $5 jedis $8 redis-py
无论是字符串回复还是多条字符串回复, 如果有nil值, 那么会返回$-1
对一个不存在的键执行get操作, 返回结果为: get not_exist_key $-1 如果批量操作中包含一条为nil值的结果, 那么返回结果如下: mget hello not_exist_key java *3 $5 world $-1 $5 jedis
客户端API
client list
id=5020789 addr=172.19.8.172:36962 fd=516 name= age=24621 idle=24621 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=cluster 标识:id、 addr、 fd、 name id:客户端连接的唯一标识,这个id是随着Redis的连接自增的,重启Redis后会重置为0。 addr:客户端连接的ip和端口。 fd:socket的文件描述符,与lsof命令结果中的fd是同一个,如果fd=-1代表当前客户端不是外部客户端,而是Redis内部的伪装客户端。 name:客户端的名字 后面的client setName和client getName两个命令会对其进行说明。 输入缓冲区: qbuf、 qbuf-free qbuf、 qbuf-free 分别代表这个缓冲区的总容量和剩余容量。
输出缓冲区: obl、 oll、 omem
obl代表固定缓冲区的长度
oll代表动态缓冲区列表的长度
omem代表使用的字节数
注:输出缓冲区由两部分组成: 固定缓冲区(16KB)和动态缓冲区,其中固定缓冲区返回比较小的执行结果,而动态缓冲区返回比较大的结果。固定缓冲区使用的是字节数组, 动态缓冲区使用的是列表。 当固定缓冲区存满后会将Redis新的返回结果存放在动态缓冲区的队列中。
客户端的存活状态: age、idle
age表示当前客户端已经连接的时间
idle表示最近一次的空闲时间
客户端类型: flag
N 普通客户端
M 当前客户端是master节点
S 当前客户端是slave节点
O 当前客户端正在执行monitor命令
X 当前客户端正在执行事务
b 当前客户端正在等待阻塞事件
i 当前客户端正在等待VM I/O,此状态目前已弃用
d 一个受监视的键被修改,EXEC命令将失败
u 客户端未被阻塞
c 回复完整输出后,关闭连接
A 尽可能快的关闭连接
输入缓冲区说明:
Redis为每个客户端分配了输入缓冲区,它的作用是将客户端发送的命令临时保存,同时Redis从会输入缓冲区拉取命令并执行, 输入缓冲区为客户端发送命令到Redis执行命令提供了缓冲功能。
注:输入缓冲区会根据输入内容大小的不同动态调整,只是要求每个客户端缓冲区的大小不能超过1G,超过后客户端将被关闭。
输入缓冲使用不当会产生两个问题:
·一旦某个客户端的输入缓冲区超过1G, 客户端将会被关闭。
·输入缓冲区不受maxmemory控制, 假设一个Redis实例设置了maxmemory为4G, 已经存储了2G数据, 但是如果此时输入缓冲区使用了3G, 已经超过maxmemory限制, 可能会产生数据丢失、 键值淘汰、 OOM等情况
造成输入缓冲区过大的原因有哪些?
1、Redis的处理速度跟不上输入缓冲区的输入速度,并且每次进入输入缓冲区的命令包含了大量bigkey, 从而造成了输入缓冲区过大的情况。 2、还有一种情况就是Redis发生了阻塞,短期内不能处理命令,造成客户端输入的命令积压在了输入缓冲区,造成了输入缓冲区过大。
监控输入缓冲区异常的方法有两种:
1、通过定期执行client list命令,收集qbuf和qbuf-free找到异常的连接记录并分析,最终找到可能出问题的客户端。 2、通过info命令的info clients模块,找到最大的输入缓冲区。
对比client list和info clients监控输入缓冲区的优劣势
输出缓冲区说明:
Redis为每个客户端分配了输出缓冲区,它的作用是保存命令执行的结果返回给客户端,为Redis和客户端交互返回结果提供缓冲。
注:输出缓冲区的容量可以通过参数client-outputbuffer-limit来进行设置,并且输出缓冲区做得更加细致,按照客户端的不同分为三种: 普通客户端、 发布订阅客户端、 slave客户端。
client-outputbuffer-limit配置:
client-output-buffer-limit <class> <hard limit> <soft limit> <soft seconds> ·<class>: 客户端类型, 分为三种。 a) normal: 普通客户端; b) slave: slave客户端,用于复制; c) pubsub: 发布订阅客户端。 ·<hard limit>: 如果客户端使用的输出缓冲区大于<hard limit>, 客户端会被立即关闭。 ·<soft limit>和<soft seconds>: 如果客户端使用的输出缓冲区超过了<softlimit>并且持续了<soft limit>秒, 客户端会被立即关闭。
redis默认配置:
client-output-buffer-limit normal 0 0 0 client-output-buffer-limit slave 256mb 64mb 60 client-output-buffer-limit pubsub 32mb 8mb 60
其他:
client setName xx 给客户端设置名字,这样比较容易标识出客户端的来源。 client getName 查看当前客户端的name client kill ip:port 杀掉指定IP地址和端口的客户端 client pause timeout(毫秒) 阻塞客户端timeout毫秒数,在此期间客户端连接将被阻塞(对主从复制的客户端无效) monitor 监控Redis正在执行的命令
关注我的公众号,不定期推送资讯
本文来自博客园,作者:链条君,转载请注明原文链接:https://www.cnblogs.com/MacoLee/p/14066842.html