redis开发与运维笔记(3)

1、客户端

通讯协议是建立在TCP协议之上的。Redis制定了RESP(REdis Serialization Protocol, Redis序列化协议) 实现客户端与服务端的正常交互

(1)客户端API。client .. 命令

  • client list。列出与Redis服务端相连的所有客户端连接信息。输出结果的每一行代表一个客户端的信息, 可以看到每行包含了十几个属性
    id: 客户端连接的唯一标识, 这个id是随着Redis的连接自增的, 重启Redis后会重置为0
    addr: 客户端连接的ip和端口。
    fd: socket的文件描述符, 与lsof命令结果中的fd是同一个, 如果fd=-1代表当前客户端不是外部客户端, 而是Redis内部的伪装客户端。
    name: 客户端的名字
    age和idle分别代表当前客户端已经连接的时间和最近一次的空闲时间。当age等于idle时,说明连接一直处于空闲状态
    flag是用于标识当前客户端的类型, 例如flag=S代表当前客户端是slave客户端、 flag=N代表当前是普通客户端, flag=O代表当前客户端正在执行monitor命令

  • 输入缓冲区: qbuf(总容量)、 qbuf-free(剩余容量)。
    redis为每个客户端分配了输入缓冲区, 它的作用是将客户端发送的命令临时保存, 同时Redis从会输入缓冲区拉取命令并执行, 输入缓冲区为客户端发送命令到Redis执行命令提供了缓冲功能
    输入缓冲区会根据输入内容大小的不同动态调整, 只是要求每个客户端缓冲区的大小不能超过1G, 超过后客户端将被关闭。输入缓冲区过大主要是因为Redis的处理速度跟不上输入缓冲区的输入速度

  • 输入缓冲使用不当会产生两个问题:
    ·一旦某个客户端的输入缓冲区超过1G, 客户端将会被关闭。
    ·输入缓冲区不受maxmemory控制,一旦超过maxmemory限制, 可能会产生数据丢失、 键值淘汰、 OOM等情况

  • 监控输入缓冲区异常的方法有两种:
    通过定期执行client list命令,收集qbuf和qbuf-free找到异常的连接记录并分析,最终找到可能出问题的客户端。能精准定位客户端,但是执行速度较慢,频繁指定可能阻塞redis
    通过info命令的info clients模块,找到最大的输入缓冲区,client_biggest_input_buf代表最大的输入缓冲区,可以设置超过10M(阈值)就进行报警。定位不精准,执行速度快。

  • 输出缓冲区: obl、 oll、 omem。
    Redis为每个客户端分配了输出缓冲区, 它的作用是保存命令执行的结果返回给客户端, 为Redis和客户端交互返回结果提供缓冲。
    输出缓冲区由两部分组成: 固定缓冲区(16KB) 和动态缓冲区, 其中固定缓冲区返回比较小的执行结果, 而动态缓冲区返回比较大的结果
    client list中的obl代表固定缓冲区的长度, oll代表动态缓冲区列表的长度, omem代表使用的字节数
    监控输出缓冲区的方法同输入缓冲区监控方法

  • 输出缓冲区的容量可以通过参数client-outputbuffer-limit来进行设置,输出缓冲区也不会受到maxmemory的限制
    配置规则:client-output-buffer-limit
    : 客户端类型, 分为三种。 a) normal: 普通客户端; b)slave: slave客户端, 用于复制; c) pubsub: 发布订阅客户端。
    : 如果客户端使用的输出缓冲区大于, 客户端会被立即关闭。
    : 如果客户端使用的输出缓冲区超过了并且持续了秒, 客户端会被立即关闭

  • 输出缓冲区出现异常的概率相对会比较大,如何预防;
    进行上述监控, 设置阀值, 超过阀值及时处理
    限制普通客户端输出缓冲区的, 把错误扼杀在摇篮中
    适当增大slave的输出缓冲区的,master节点写入较大, slave客户端的输出缓冲区可能会比较大, 一旦slave客户端连接因为输出缓冲区溢出被kill, 会造成复制重连
    限制容易让输出缓冲区增大的命令, 例如, 高并发下的monitor命令就是一个危险的命令。
    及时监控内存, 一旦发现内存抖动频繁, 可能就是输出缓冲区过大。

  • client setName和client getName。client setName用于给客户端设置名字, 这样比较容易标识出客户端的来源。
  • client kill ip:port。此命令用于杀掉指定IP地址和端口的客户端。
  • client pause timeout(毫秒)。client pause命令用于阻塞客户端timeout毫秒数,在此期间客户端连接将被阻塞。生产环境中,暂停客户端成本非常高
    该命令可以在如下场景起到作用:
    client pause只对普通和发布订阅客户端有效,对于主从复制(从节点内部伪装了一个客户端)是无效的,也就是此期间主从复制是正常进行的,此命令可以用来让主从复制保持一致
    client pause可以用一种可控的方式将客户端连接从一个Redis节点切换到另一个Redis节点

  • monitor命令用于监控Redis正在执行的命令
    每个客户端都有自己的输出缓冲区, 既然monitor能监听到所有的命令, 一旦Redis的并发量过大,monitor客户端的输出缓冲会暴涨, 可能瞬间会占用大量内存

(2)客户端相关配置

  • Redis提供了maxclients参数来限制最大客户端连接数, 一旦连接数超过maxclients, 新的连接将被拒绝
    可以通过info clients来查询当前Redis的连接数
    axclients默认值是10000,可以通过config set maxclients对最大客户端连接数进行动态设置

  • Redis提供了timeout(单位为秒)参数来限制连接的最大空闲时间,一旦客户端连接的idle时间超过了timeout,连接将会被关闭。默认的timeout是0,动态设置config set timeout 30
  • tcp-keepalive:检测TCP连接活性的周期,默认值为0,也就是不进行检测,如果需要设置,建议为60,那么Redis会每隔60秒对它创建的TCP连接进行活性检测,防止大量死连接占用系统资源
  • tcp-backlog:TCP三次握手后,会将接受的连接放入队列中,tcpbacklog就是队列的大小,它在Redis中的默认值是511。通常来讲这个参数不需要调整,但是这个参数会受到操作系统的影响

(3)客户端统计片段

info clients命令

  • connected_clients:代表当前Redis节点的客户端连接数,需要重点监控,一旦超过maxclients,新的客户端连接将被拒绝。
  • client_longest_output_list: 当前所有输出缓冲区中队列对象个数的最大值。
  • client_biggest_input_buf: 当前所有输入缓冲区中占用的最大容量。
  • blocked_clients:正在执行阻塞命令(例如blpop、 brpop、brpoplpush)的客户端个数。

info stats  命令

  • total_connections_received: Redis自启动以来处理的客户端连接数总数
  • rejected_connections: Redis自启动以来拒绝的客户端连接数, 需要重点监控

2、客户端常见异常

(1)无法从连接池获取到连接

  • JedisPool中的Jedis对象个数是有限的, 默认是8个。如果连接池中没有空闲Jedis对象,新的请求就需要进行等待(例如设置了maxWaitMillis>0)
    在maxWaitMillis时间内仍然无法获取到Jedis对象就会抛出异常:JedisConnectionException: Could not get a resource from the pool
    如果设置了blockWhenExhausted=false, 那么调用者发现池子中没有资源时, 会立即抛出异常不进行等待

  • 造成没有资源的原因非常多:
    客户端: 高并发下连接池设置过小, 出现供不应求
    客户端: 没有正确使用连接池, 比如没有进行释放
    客户端: 存在慢查询操作, 这些慢查询持有的Jedis对象归还速度会比较慢,造成池子满了
    服务端: 客户端是正常的, 但是Redis服务端由于一些原因造成了客户端命令执行过程的阻塞

(2)客户端读写超时,SocketTimeoutException: Read timed out

造成该异常的原因也有以下几种:
·读写超时间设置得过短。
·命令本身就比较慢。
·客户端与服务端网络不正常。
·Redis自身发生阻塞。

(3)客户端连接超时,SocketTimeoutException: connect timed out

造成该异常的原因也有以下几种:
连接超时设置得过短, 可以通过下面代码进行设置:jedis.getClient().setConnectionTimeout(time);
Redis发生阻塞, 造成tcp-backlog已满, 造成新的连接失败。
客户端与服务端网络不正常。

(4)客户端缓冲区异常

造成这个异常的原因可能有如下几种:
输出缓冲区满。 
长时间闲置连接被服务端主动断开
不正常并发读写: Jedis对象同时被多个线程并发操作, 可能会出现上述异常

(5)Lua脚本正在执行

如果Redis当前正在执行Lua脚本, 并且超过了lua-time-limit, 此时Jedis调用Redis时, 会收到下面的异常。

JedisDataException: BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE. 

(6)Redis正在加载持久化文件

Jedis调用Redis时, 如果Redis正在加载持久化文件, 那么会收到下面的异常:
JedisDataException: LOADING Redis is loading the dataset in memory

(7)Redis使用的内存超过maxmemory配置

Jedis执行写操作时, 如果Redis的使用内存大于maxmemory的设置, 会收到下面的异常, 此时应该调整maxmemory并找到造成内存增长的原因
JedisDataException: OOM command not allowed when used memory > 'maxmemory'.

(8)客户端连接数过大

  • 如果客户端连接数超过了maxclients, 新申请的连接就会出现如下异常:
    JedisDataException: ERR max number of clients reached
  • 一般来说可以从两个方面进行着手解决:
    如果应用方是分布式结构的话,可以通过下线部分应用节点(例如占用连接较多的节点),使得Redis的连接数先降下来。从而让绝大部分节点可以正常运行,此时再通过查找程序bug或者调整maxclients进行问题的修复。
    果此时客户端无法处理, 而当前Redis为高可用模式(例如Redis Sentinel和Redis Cluster) , 可以考虑将当前Redis做故障转移。
    但是无论从哪个方面进行处理, 故障的快速恢复极为重要, 当然更为重要的是找到问题的所在, 否则一段时间后客户端连接数依然会超过maxclients。

 3、客户端案例分析

(1)Redis主节点内存陡增

  • 服务端现象: Redis主节点内存陡增, 几乎用满maxmemory, 而从节点内存并没有变化
  • 客户端现象: 客户端产生了OOM异常, 也就是Redis主节点使用的内存已经超过了maxmemory的设置, 无法写入新的数据
  • 分析原因:
    确实有大量写入, 但是主从复制出现问题: 查询了Redis复制的相关信息, 复制是正常的, 主从数据基本一致。主从的键个数基本一致,使用dbsize命令
    其他原因造成主节点内存使用过大: 排查是否由客户端缓冲区造成主节点内存陡增, 使用info clients命令发现客户端输出缓冲区不正常,client_longest_output_list:225698
    通过client list命令找到omem不正常的连接, 一般来说大部分客户端的omem为0,redis-cli client list | grep -v "omem=0"
    最后发现是因为有客户端在执行monitor命令造成的

(2)客户端周期性超时

  • 客户端现象: 客户端出现大量超时, 经过分析发现超时是周期性出现的
  • 服务端现象: 服务端并没有明显的异常, 只是有一些慢查询操作
  • 原因分析:
    网络原因: 服务端和客户端之间的网络出现周期性问题, 经过观察网络是正常的
    客户端: 由于是周期性出现问题, 就和慢查询日志的历史记录对应了一下时间, 发现只要慢查询出现, 客户端就会产生大量连接超时, 两个时间点基本一致
    最终找到问题是慢查询操作造成的, 通过执行hlen发现有200万个元素, 这种操作必然会造成Redis阻塞, 有定时任务代码每5分钟执行一次hgetall操作

你可能感兴趣的:(Redis)