机制

redis提供了一些操作客户端的命令,比如查询所有已连接到服务端的客户端数量,控制客户端的连接状态(关闭或者挂起)等。通过客户命令我们可以轻松实现对客户端的管理、控制。

redis服务器通过监听TCP端口的方式来接收客户端连接。当一个连接建立后,redis会自动执行以下过程:

  • 首先客户端socket被设置为非阻塞模式,这是因为redis在网络事件处理上采用了非阻塞IO(即IO多路复用模型)
  • 其次设置socket的TCP_NODELAY属性,从而禁用Nagle算法
  • 最后创建一个可读的文件事件,用它来监听客户端socket的数据发送

redis使用命令的格式向客户端输入数据,这个数据量是非常小的。

  • 当向客户端输入命令后,我们希望能够快速的得到服务器的应答,也就是低延迟性,但是如果开启了nagle算法就会出现频繁延时的现象,导致用户体验差。
    • nagle算法的基本定义是:任意时刻,最多只能有一个未被确认的小段。所谓“小段”,指的是小于MSS尺寸的数据块;所谓“未被确认”,是指一个数据块发送出去后,没有收到对方发送的ACK确认该数据已收到。
    • 延迟ACK:接收方在收到数据后,并不会立即回复ACK,而是延迟一段时间。一般为200ms,但这个200ms并非收到数据后需要延迟的时间。系统有一个固定的定时器每隔200ms就会立即来检查是否需要发送ACK。这样做有两个目的:
      • ACK是可以合并的,也就是如果连续收到两个TCP包,并不一定需要ACK两次,只需要回复最终的ACK就可以了
      • 如果接收方有数据要发送,那么会在发送数据的TCP数据包里,带上ACK信息。这样做,可以避免大量的ACK以一个单独的TCP包发送,减少了网络流量。
      • setsockopt(fd, IPPROTO_TCP, TCP_QUICKACK, (int[]){1}, sizeof(int))
  • nagle算法可以提高广域网传输效率,减少分组的报文个数,适合传输体量较大的数据(延迟ACK和nagle算法是冲突的)

redis IO多路复用

redis的底层是一个单线程模型,单线程指的是用一个线程来处理所有的网络事件请求,这样就避免了多进程或者多线程切换导致的CPU消耗,而且也不用考虑各种锁的问题。

redis为了充分利用单线程,加快服务器的处理速度,它采用IO多路复用模型来处理客户端与服务端的连接,这种模型有三种实现方式:select、poll、epoll。在Linux系统上,redis会采用epoll的方式来监控多个IO事件。当客户端空闲时,线程处于阻塞状态;当一个或者多个IO事件触发时(客户端发起网络连接请求),线程就会从阻塞状态唤醒,并同时使用epoll来轮询触发事件,并同时使用epoll来轮询触发事件,并依次提交给线程处理。

“多路”指的是多个网络连接,“复用”指的是复用同一个线程。多路 IO 复用技术可以让单个线程高效的处理多个连接请求

客户端API

命令 说明
CLIENT LIST 以列表的形式返回所有连接到 Redis 服务器的客户端。
CLIENT SETNAME 设置当前连接的名称。
CLIENT GETNAME 获取通过 CLIENT SETNAME 命令设置的服务名称。
CLIENT PAUSE 挂起客户端连接,将所有客户端挂起指定的时间(以毫秒为计算)
CLIENT KILL 关闭客户端连接。
CLIENT ID 返回当前客户端 ID。
CLIENT REPLY 控制发送到当前连接的回复,可选值包括 on

CLIENT ID

127.0.0.1:6379> CLIENT ID
(integer) 2557

Client List

作用

  • client list命令能列出与Redis服务端相连的所有客户端连接信息

语法

redis 127.0.0.1:6379> CLIENT LIST

实例

redis 127.0.0.1:6379> CLIENT LIST
addr=127.0.0.1:43143 fd=6 age=183 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
addr=127.0.0.1:43163 fd=5 age=35 idle=15 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=ping
addr=127.0.0.1:43167 fd=7 age=24 idle=6 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=get

返回值

命令返回多行字符串,这些字符串按以下形式被格式化:

  • 每个已连接客户端对应一行(以 LF 分割)
  • 每行字符串由一系列 属性=值 形式的域组成,每个域之间以空格分开
标识: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分别代表这个缓冲区的总容量和剩余容量
    • qbuf : 查询缓冲区的长度(字节为单位, 0 表示没有分配查询缓冲区)
    • qbuf-free : 查询缓冲区剩余空间的长度(字节为单位, 0 表示没有剩余空间)
  • redis没有提供相应的配置来规定每个缓冲区的大小,输入缓冲区会根据输入内容的大小的不同动态调整,只是要求每个客户端缓冲区的大小不能超过1G(源码中写死的),超过后客户端将关闭
/* 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,超过了maxmeory限制,可能会产生数据丢失,键值淘汰
    在这里插入图片描述

输入缓冲区使用不当造成的危害非常大,那么造成输入 缓冲区过大的原因有哪些两个原因:

  • 输入缓冲区过大主要是因为redis的处理速度跟不上输入缓冲区的输入速度,并且每次进入输入缓冲区的命令包含了大量bigkey,从而造成了输入缓冲区过大的情况
  • redis发生了阻塞,短期内不能处理命令,造成客户端输入的命令挤压在了输入缓冲区,造成了输入缓冲区过大

那么如何快速发现和监控呢?两种方法:

  • 通过定期执行client list命令,收集qbuf和qubf-free找到异常的连接记录并分析,最终找到可能出问题的客户端
  • 通过info命令的info clients模块,找到最大的输入缓冲区,比如下面命令中的其中client_biggest_input_buf代表最大的输入缓冲区,可以设置超过10M就进行报警
    在这里插入图片描述

在这里插入图片描述
运维提示:

  • 输入缓冲区问题出现概率比较低,但是也要做好防范,在开发中要减少bigkey、减少Redis阻塞、合理的监控报警
输出缓冲区:obl、oll、omem

Redis为每个客户端分配了输出缓冲区,它的作用是保存命令执行的结 果返回给客户端,为Redis和客户端交互返回结果提供缓冲。如下图:

在这里插入图片描述
与输入缓冲区不同的是

  • 输出缓冲区的容量可以通过参数client-outputbuffer-limit来进行设置;
  • 并且输出缓冲区做的更加细致,按照客户端的不同可以分为三种:普通客户端、发布订阅客户端、slave客户端,如下图:

在这里插入图片描述
对应的配置规则是:

client-output-buffer-limit <class> <hard limit> <soft limit> <soft seconds>
  • <class>:客户端类型,分为三种。
    • a)normal:普通客户端;
    • b) slave:slave客户端,用于复制;
    • c)pubsub:发布订阅客户端。
  • <hard limit>:如果客户端使用的输出缓冲区大于,客户端会被立即关闭。
  • ·<soft limit><soft seconds>:如果客户端使用的输出缓冲区超过了<soft limit>并且持续了<soft limit>秒,客户端会被立即关闭。

Redis的默认配置是:


[root@localhost ~]# grep -v '^#'  /usr/local/redis/6379.conf | grep buffer
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit replica 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新的返回结果存放在动态缓冲区的队列中,队列中的每个对象就是每个返回结果,如下图
在这里插入图片描述
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=
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代表输出缓冲区列表最大对象数。 比如:
127.0.0.1:6379> info clients
# Clients
connected_clients:502
client_longest_output_list:4869
client_biggest_input_buf:0
blocked_clients:0

这两种统计方法的优劣势和输入缓冲区是一样的

相比于输入缓冲区,输出缓冲区出现异常的概率相对比较大,那么如何预防呢方法如下

  • 进行上述监控,设置阈值,超过阈值即使处理
  • 限制普通客户端输出缓冲区的<hard limit>、<soft limit>、<soft seconds>,把错误扼杀在摇篮中,比如:
client-output-buffer-limit normal 20mb 10mb 120
  • 适当增大slave的输出缓冲区的<hard limit>、<soft limit>、<soft seconds>,如果master节点写入较大,slave客户端的输出缓冲区可能会比较大,一旦slave客户端连接因为输出缓冲区溢出被kill,会造成复制重连
  • 限制容易让输出缓冲区增大的命令,比如,高并发下的monitor命令就 是一个危险的命令
  • 及时监控内存,一旦发现内存抖动频繁,可能就是输出缓冲区过大
客户端的存活状态(age、idle)
  • client list中的age和idle分别代表:
    • 当前客户端已经连接的时间(单位是秒)
    • 最近一次的空闲时间(单位是秒):

实例

例如下面这条记录代表当期客户端连接Redis的时间为603382秒,其中空闲了331060秒:

id=2232080 addr=10.16.xx.55:32886 fd=946 name= age=603382 idle=331060 flags=N db=0

sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=get

下面这条记录代表当期客户端连接Redis的时间为8888581秒,其中空闲了8888581秒。实际上这种就属于不太正常的情况当age等于idle时, 说明连接一直处于空闲状态

id=254487 addr=10.2.xx.234:60240 fd=1311 name= age=8888581 idle=8888581 flags=N db=0

sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=get

演示

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参数的相应变化

  • 在执行代码之前,client list只有一个客户端,也就是当前的rediscli
127.0.0.1:6379> client list

id=45 addr=127.0.0.1:55171 fd=6 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
  • 使用Jedis生成了一个新的连接,并执行get操作,可以看到IP地址为 10.7.40.98的客户端,最后执行的命令是get,age和idle分别是1秒和0秒
127.0.0.1:6379> client list
id=46 addr=10.7.40.98:62908 fd=7 name= age=1 idle=0 flags=N db=0 sub=0 psub=0
multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=get
  • 休息10秒,此时Jedis客户端并没有关闭,所以age和idle一直在递 增
27.0.0.1:6379> client list
id=46 addr=10.7.40.98:62908 fd=7 name= age=9 idle=9 flags=N db=0 sub=0 psub=0
multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=get
  • 执行新的操作ping,发现执行后age依然在增加,而idle从0计算,也 就是不再闲置
127.0.0.1:6379> client list
id=46 addr=10.7.40.98:62908 fd=7 name= age=11 idle=0 flags=N db=0 sub=0 psub=0
multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=ping
  • 休息5秒,观察age和idle增加
127.0.0.1:6379> client list
id=46 addr=10.7.40.98:62908 fd=7 name= age=15 idle=5 flags=N db=0 sub=0 psub=0
multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=ping
  • 关闭Jedis,Jedis连接已经消失
redis-cli client list | grep "10.7.40.98”为空
客户端的限制maxclients和timeout
  • redis提供了maxclients参数来限制客户端的最大连接数,一旦连接数超过maxclients,新的连接将被拒绝。maxclients默认值是10000

可以通过info clients来查询当前Redis的连接数。

127.0.0.1:6379> info clients
#Clients
connected_clients:1414

可以通过config set maxclients对最大客户端连接数进行动态设置:


127.0.0.1:6379> config get maxclients
1)"maxclients"
2)"10000"
127.0.0.1:6379> config set maxclients 50
OK
127.0.0.1:6379> config get maxclients
1)"maxclients"
2) "50"
  • 一般来说maxclients=10000在大部分场景下已经绝对够用,但是某些情况由于业务方使用不当(例如没有主动关闭连接)可能存在大量idle连接,无论是从网络连接的成本还是超过maxclients的后果来说都不是什么好事,因此Redis提供了timeout(单位为秒)参数来限制连接的最大空闲时间,一旦客户端连接的idle时间超过了timeout,连接将会被关闭
# Redis默认的timeout是0,也就是不会检测客户端的空闲 
127.0.0.1:6379> config set timeout 30  #设置timeout为30秒
OK
客户端类型(flag)
  • client list中的flag是用于标识当前客户端的类型

例如flag=S代表当前客 户端是slave客户端、flag=N代表当前是普通客户端,flag=O代表当前客户端 正在执行monitor命令。下图列出了11种客户端类型:
在这里插入图片描述

client setName和client getName

client setName

client setName xx

client setName用于给客户端设置名字,这样比较容易标识出客户端的来源。例如将当前客户端命名为test_client,可以执行如下操作:

在这里插入图片描述
此时再执行client list命令,就可以看到当前客户端的name属性为test_client:

在这里插入图片描述

client getName

client getName

如果想直接查看当前客户端的name,可以使用client getName命令

第一次进入客户端时,客户端是没有名字的,因此名字为空
在这里插入图片描述
更改名字之后,就可以看到更改后的名字了。例如:
在这里插入图片描述

用得少

client kill

client kill ip:port
  • 此命令用于杀掉指定IP地址和端口的客户端
  • 由于一些原因(例如设置timeout=0时产生的长时间idle的客户端),需要手动杀掉客户端连接时,可以使用client kill命令

如果想杀掉127.0.0.1:52343的客户端,可以执行:

127.0.0.1:6379> client kill 127.0.0.1:52343

Client Pause

作用

  • 用于阻塞客户端timeout毫秒数,在此期间客户端连接将被阻塞

在这里插入图片描述

语法

redis 127.0.0.1:6379> CLIENT PAUSE timeout 

返回值

  • 返回 OK。如果 timeout 参数是非法的返回错误。

演示

例如在一个客户端执行下面的命令,在之后的10000毫秒内的其他客户端连接都会被阻塞
在这里插入图片描述
过一会后在另一个客户端执行ping命令,发现整个ping命令执行了2.40秒(手动执行redis-cli,只为了演示,不代表真实执行时间):
在这里插入图片描述

monitor

作用

  • monitor命令用于监控Redis正在执行的命令

演示

我们打开了两个redis-cli,右侧先执行monitor命令,左侧再执行其他命令

可以看到monitor命令能够监听其他客户端正在执行的命令,并记录了详细的时间戳

在这里插入图片描述

注意

  • monitor的作用很明显,如果开发和运维人员想监听Redis正在执行的命令,就可以用monitor命令
  • 但事实并非如此美好,每个客户端都有自己的输出缓冲区,既然monitor能监听到所有的命令,一旦Redis的并发量过大, monitor客户端的输出缓冲会暴涨,可能瞬间会占用大量内存。

客户端相关配置

  • timeout:
    • 检测客户端空闲连接的超时时间
    • 一旦idle时间达到了timeout,客户端将会被关闭
    • 如果设置为0就不检测
  • ·maxclients:客户端最大连接数
  • tcp-keepalive:
    • 检查TCP连接活性的周期,默认值为0,也就是不进行检测
    • 如果需要设置,建议60,那么redis会每隔60s对它创建的TCP连接进行活性检测,防止大量死连接占用系统资源
  • ·tcp-backlog
    • TCP三次握手后,会将接受的连接放入队列中,tcpbacklog就是队列的大小,它在Redis中的默认值是511
    • 通常来讲这个参数不 需要调整,但是这个参数会受到操作系统的影响,例如在Linux操作系统 中,如果/proc/sys/net/core/somaxconn小于tcp-backlog,那么在Redis启动时会 看到如下日志,并建议将/proc/sys/net/core/somaxconn设置更大
      在这里插入图片描述
      修改方法也非常简单,只需要执行如下命令:
echo 511 > /proc/sys/net/core/somaxconn

客户端最大连接数

在 Redis 配置文件中,有一个maxclients的配置项,它指定了连接到 Redis 服务器的最大客户端数量。其默认值是 10000。配置项如下所示:

127.0.0.1:6379> config get maxclients
1) "maxclients"
2) "10000"
#更改最大连接数量
127.0.0.1:6379> config set maxclients 20000
OK
127.0.0.1:6379> config get maxclients
1) "maxclients"
2) "20000"
Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐