机制

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联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐