Redis(开发与运维):27---客户端之(客户端API:client、monitor)
程序员文章站
2022-05-19 10:49:41
...
一、client list
- client list命令能列出与Redis服务端相连的所有客户端连接信息。例如下面代码是在一个Redis实例上执行client list的结果,其中每一行代表一个客户端信息:
- 关于客户端连接信息的设计与实现还可以参阅:https://blog.csdn.net/qq_41453285/article/details/103318465
- 下面将选择几个重要的属性进行说明,其余通过表格的形式进行展示
①标识: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
- Redis为每个客户端分配了输入缓冲区,它的作用是:将客户端发送的命令临时保存,同时Redis从会输入缓冲区拉取命令并执行,输入缓冲区为客 户端发送命令到Redis执行命令提供了缓冲功能,如下图所示
- client list中qbuf和qbuf-free:
- 这两个属性分别代表这个缓冲区的总容量和剩余容量
- Redis没有提供相应的配置来规定每个缓冲区的大小,输入缓冲区会根据输入内容大小的不同动态调整,只是要求每个客户端缓冲区的大小不能超过1G,超过后客户端将被关闭
- 下面是Redis源码中对于输入缓冲区的硬编码:
/* Protocol and I/O related defines */ #define REDIS_MAX_QUERYBUF_LEN (1024*1024*1024) /* 1GB max query buffer. */
- 输入缓冲使用不当会产生两个问题:
- 一旦某个客户端的输入缓冲区超过1G,客户端将会被关闭
- 输入缓冲区不受maxmemory控制,假设一个Redis实例设置了maxmemory为4G,已经存储了2G数据,但是如果此时输入缓冲区使用了3G,已经超过maxmemory限制,可能会产生数据丢失、键值淘汰、OOM等情况(如下图所示)
- 上图的执行效果如下:
- 上面已经看到,输入缓冲区使用不当造成的危害非常大,那么造成输入缓冲区过大的原因有哪些?
- 输入缓冲区过大主要是因为Redis的处理速度跟不上输入缓冲区的输入速度,并且每次进入输入缓冲区的命令包含了大量 bigkey,从而造成了输入缓冲区过大的情况
- 还有一种情况就是Redis发生了阻塞,短期内不能处理命令,造成客户端输入的命令积压在了输入缓冲区, 造成了输入缓冲区过大
- 那么如何快速发现和监控呢?监控输入缓冲区异常的方法有两种:
- 通过定期执行client list命令,收集qbuf和qbuf-free找到异常的连接记录 并分析,最终找到可能出问题的客户端。
- 通过info命令的info clients模块,找到最大的输入缓冲区,例如下面命令中的其中client_recent_max_input_buffer代表最大的输入缓冲区,例如可以设置超过10M就进行报警
- 上面两种方法各有自己的优劣势,下图对两种方法进行了对比:
- 运维提示:输入缓冲区问题出现概率比较低,但是也要做好防范,在开发中要减少bigkey、减少Redis阻塞、合理的监控报警
③输出缓冲区:obl、oll、omem
- Redis为每个客户端分配了输出缓冲区,它的作用是:保存命令执行的结 果返回给客户端,为Redis和客户端交互返回结果提供缓冲
- 与输入缓冲区不同的是:
- 输出缓冲区的容量可以通过参数client-outputbuffer-limit来进行设置
- 并且输出缓冲区做得更加细致,按照客户端的不同分为三种:普通客户端、发布订阅客户端、slave客户端。如下图所示
- client-output-buffer-limit格式如下。参数意义为:
- <class>:客户端类型,分为三种。a)normal:普通客户端;b) slave:slave客户端,用于复制;c)pubsub:发布订阅客户端
- <hard limit>:如果客户端使用的输出缓冲区大于该值,客户端会被立即关闭
- <soft limit>和<soft seconds>:如果客户端使用的输出缓冲区超过了并且持续了秒,客户端会被立即关闭
- 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
- 和输入缓冲区相同的是,输出缓冲区也不会受到maxmemory的限制,如果使用不当同样会造成maxmemory用满产生的数据丢失、键值淘汰、OOM等情况
- 实际上输出缓冲区由两部分组成:
- 固定缓冲区(16KB):返回比较小的执行结果
- 动态缓冲区:返回比较大的结果。例如大的字符串、hgetall、smembers命令的结果等,通过Redis源码中redis.h的redisClient结构体(Redis3.2版本变为Client)可以看到两个缓冲区的实现细节:
typedef struct redisClient { // 动态缓冲区列表 list *reply; // 动态缓冲区列表的长度(对象个数) unsigned long reply_bytes; // 固定缓冲区已经使用的字节数 int bufpos; // 字节数组作为固定缓冲区 char buf[REDIS_REPLY_CHUNK_BYTES]; } redisClient;
- 固定缓冲区使用的是字节数组,动态缓冲区使用的是列表。当固定缓冲区存满后会将Redis新的返回结果存放在动态缓冲区的队列中,队列中的每个对象就是每个返回结果,如下图所示:
- obl、oll、omem:
- client list中的obl代表固定缓冲区的长度,oll代表动态缓冲区列表的长度,omem代表使用的字节数
- 例如下面代表当前客户端的固定缓冲区的长度为0,动态缓冲区有4869个对象,两个部分共使用了133081288字节=126M 内存:
id=7 addr=127.0.0.1:56358 fd=6 name= age=91 idle=0 flags=O db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=4869 omem=133081288 events=rw cmd=monitor
- 监控输出缓冲区的方法依然有两种:
- ①通过定期执行client list命令,收集obl、oll、omem找到异常的连接记录 并分析,最终找到可能出问题的客户端
- ②通过info命令的info clients模块,找到输出缓冲区列表最大对象数,例如(其中,client_longest_output_list代表输出缓冲区列表最大对象数):
- 这两种统计方法的优劣势和输入缓冲区是一样的,这里就不再赘述了
- 相比于输入缓冲区,输出缓冲区出现异常的概率相对会比较大,那么如何预防呢?方法如下:
- 进行上述监控,设置阀值,超过阀值及时处理
- 适当增大slave的输出缓冲区的,如果master节点写入较大,slave客户 端的输出缓冲区可能会比较大,一旦slave客户端连接因为输出缓冲区溢出 被kill,会造成复制重连
- 限制容易让输出缓冲区增大的命令,例如,高并发下的monitor命令就 是一个危险的命令
- 及时监控内存,一旦发现内存抖动频繁,可能就是输出缓冲区过大
- 限制普通客户端输出缓冲区的,把错误扼杀在摇篮中,例如可以进行如下设置:
client-output-buffer-limit normal 20mb 10mb 120
④客户端的存活状态(age、idle)
- client list中的age和idle分别代表:当前客户端已经连接的时间、最近一次的空闲时间:
- 例如下面这条记录代表当期客户端连接Redis的时间为304秒,其中空闲了0秒:
- 例如下面这条记录代表当期客户端连接Redis的时间为8888581秒,其中空闲了8888581秒。实际上这种就属于不太正常的情况,当age等于idle时, 说明连接一直处于空闲状态
演示案例
- 为了更加直观地描述age和idle,下面用一个例子进行说明:
String key = "hello"; // 1) 生成jedis,并执行get操作 Jedis jedis = new Jedis("127.0.0.1", 6379); System.out.println(jedis.get(key)); // 2) 休息10秒 TimeUnit.SECONDS.sleep(10); // 3) 执行新的操作ping System.out.println(jedis.ping()); // 4) 休息5秒 TimeUnit.SECONDS.sleep(5); // 5) 关闭jedis连接 jedis.close();
- 下面对代码中的每一步进行分析,用client list命令来观察age和idle参数的相应变化(备注:为了与redis-cli的客户端区分,本次测试客户端IP地址:10.7.40.98)
- 1)在执行代码之前,client list只有一个客户端,也就是当前的rediscli,下面为了节省篇幅忽略掉这个客户端。
- 2)使用Jedis生成了一个新的连接,并执行get操作,可以看到IP地址为 10.7.40.98的客户端,最后执行的命令是get,age和idle分别是1秒和0秒
- 3)休息10秒,此时Jedis客户端并没有关闭,所以age和idle一直在递 增:
- 4)执行新的操作ping,发现执行后age依然在增加,而idle从0计算,也 就是不再闲置
- 5)休息5秒,观察age和idle增加:
- 6)关闭Jedis,Jedis连接已经消失:
⑤客户端类型(flag)
- client list中的flag是用于标识当前客户端的类型
- 例如flag=S代表当前客 户端是slave客户端、flag=N代表当前是普通客户端,flag=O代表当前客户端 正在执行monitor命令。下图列出了11种客户端类型:
⑥其他
- 上面已经将client list中重要的属性进行了说明,下图列出之前介绍过 以及一些比较简单或者不太重要的属性
二、client setName和client getName
client setName xx
client setName
- client setName用于给客户端设置名字,这样比较容易标识出客户端的来源。例如将当前客户端命名为test_client,可以执行如下操作:
- 此时再执行client list命令,就可以看到当前客户端的name属性为test_client:
client getName
client getName
- 如果想直接查看当前客户端的name,可以使用client getName命令
- 第一次进入客户端时,客户端是没有名字的,因此名字为空
- 更改名字之后,就可以看到更改后的名字了。例如:
- client getName和setName命令可以做为标识客户端来源的一种方式,但是通常来讲,在Redis只有一个应用方使用的情况下,IP和端口作为标识会更加清晰。当多个应用方共同使用一个Redis,那么此时client setName可以作为标识客户端的一个依据
三、client kill
client kill ip:port
- 此命令用于杀掉指定IP地址和端口的客户端
- 由于一些原因(例如设置timeout=0时产生的长时间idle的客户端),需要手动杀掉客户端连接时,可以使用client kill命令
演示案例
- 例如左侧为一个客户端(127.0.0.1:34658),右侧为一个客户端(127.0.0.1:34660)
- 如果想杀掉127.0.0.1:34656的客户端,可以执行:
- 执行命令后,client list结果只剩下了127.0.0.1:34658自己这个客户端:
四、client pause
client pause timeout(毫秒)
- client pause命令用于阻塞客户端timeout毫秒数,在此期间客户端连接将被阻塞。如下图所示:
演示案例
- 例如在一个客户端执行下面的命令,在之后的10000毫秒内的其他客户端连接都会被阻塞
- 过一会后在另一个客户端执行ping命令,发现整个ping命令执行了2.40秒(手动执行redis-cli,只为了演示,不代表真实执行时间):
-
该命令可以在如下场景起到作用:
- client pause只对普通和发布订阅客户端有效,对于主从复制(从节点内部伪装了一个客户端)是无效的,也就是此期间主从复制是正常进行的, 所以此命令可以用来让主从复制保持一致
- client pause可以用一种可控的方式将客户端连接从一个Redis节点切换到另一个Redis节点
- 需要注意的是在生产环境中,暂停客户端成本非常高
五、monitor
- monitor命令用于监控Redis正在执行的命令
- 关于monitor的设计与实现还可以参阅:https://blog.csdn.net/qq_41453285/article/details/103501304
演示案例
- 如下图所示:
- 我们打开了两个redis-cli,右侧先执行monitor命令,左侧再执行其他命令
- 可以看到monitor命令能够监听其他客户端正在执行的命令,并记录了详细的时间戳
-
注意事项:monitor的作用很明显,如果开发和运维人员想监听Redis正在执行的命令,就可以用monitor命令,但事实并非如此美好,每个客户端都有自己的输出缓冲区,既然monitor能监听到所有的命令,一旦Redis的并发量过大, monitor客户端的输出缓冲会暴涨,可能瞬间会占用大量内存。下图展示了monitor命令造成大量内存使用
上一篇: 微信开发通过.Net发送图文消息实例解析