1--redis客户端

目录结构:

  • 一、 客户端通讯协议
    • 1.发送命令格式
    • 2.返回结果格式
  • 二、java客户端jedis
    • 2.1 jedis基本操作
      • 1.测试连通性
      • 2. 一个key
      • 3. 五大数据类型
    • 2.2 JedisPool(redis线程池)
      • 1.直接连接和通过线程池连接对比
    • 2.3 Redis中Pipeline的使用方法
  • 三、客户端管理
    • 3.1 客户端API
    • 3.2 客户端相关配置
    • 3.3 客户端统计片段
  • 四、客户端常见异常
    • 4.1 无法从连接池获取到连接
    • 4.2 客户端读写超时
    • 4.3 客户端连接超时
    • 4.4 客户端缓冲区异常
    • 4.5 edis正在加载持久化文件
    • 4.6 Redis正在加载持久化文件
    • 4.7 Redis使用的内存超过maxmemory配置
    • 4.8 客户端连接数过大
  • 参考文献

 

一、 客户端通讯协议

        几乎所有的主流编程语言都有Redis的客户端(http://redis.io/clients),不考虑Redis非常流行的原因,如果站在技术的角度看原因还有两个:

第一,客户端与服务端之间的通信协议是在TCP协议之上构建的。

第二,Redis制定了RESP(REdis Serialization Protocol,Redis序列化协议)实现客户端与服务端的正常交互,这种协议简单高效,既能够被机器解析,又容易被人类识别。

RESP设计的十分精巧,下面是一张完备的协议描述图:

                                fig.1-1 redis protocol

例如客户端发送一条set hello world命令给服务端,按照RESP的标准,客户端需要将其封装为如下格式(每行用\r\n分隔):


*3

$3
SET
$5
hello
$5
world



这样Redis服务端能够按照RESP将其解析为set hello world命令,执行后回复的格式如下:


+OK


可以看到除了命令(set hello world)和返回结果(OK)本身还包含了一些特殊字符以及数字,下面将对这些格式进行说明。

如上图fig.1所示,Redis序列化协议分为两部分:

1.发送命令格式

RESP的规定一条命令的格式如下,CRLF代表"\r\n"。对应于上面set hello world的例子:


*<参数数量> CRLF
$<参数1的字节数量> CRLF
<参数1> CRLF
...
$<参数N的字节数量> CRLF
<参数N> CRLF


 

有一点要注意的是,上面只是格式化显示的结果,实际传输格式为如下代码:


*3\r\n$3\r\nSET\r\n$5\r\nhello\r\n$5\r\nworld\r\n


2.返回结果格式

  • 状态回复:在RESP中第一个字节为"+"。
  • 错误回复:在RESP中第一个字节为"-"。
  • 整数回复:在RESP中第一个字节为":"。
  • 字符串回复:在RESP中第一个字节为"$"。
  • 多条字符串回复:在RESP中第一个字节为"*"。

 

            fig.1-2 Redis五种回复类型在RESP下的编码

关于客户端通讯协议更加详细的描述请参考http://redisdoc.com/topic/protocol.html

二、java客户端jedis

2.1 jedis基本操作

1.测试连通性

2. 一个key

3. 五大数据类型

1)String

2)List

3) hash

4) Set集合

5) Zset (有序集合)

可以看到初始化Jedis需要两个参数:Redis实例的IP和端口,除了这两个参数外,还有一个包含了四个参数的构造函数是比较常用的:


Jedis(final String host, final int port, final int connectionTimeout, final intsoTimeout)


参数说明:
·host:Redis实例的所在机器的IP。
·port:Redis实例的端口。
·connectionTimeout:客户端连接超时。
·soTimeout:客户端读写超时。

2.2 JedisPool(redis线程池)

1.直接连接和通过线程池连接对比

 

              fig. 2-1 Redis直连方式             

           fig. 2-2 Redis连接池方式                

       客户端连接Redis使用的是TCP协议,直连的方式每次需要建立TCP连接,而连接池的方式是可以预先初始化好Jedis连接,所以每次只需要从Jedis连接池借用即可,而借用和归还操作是在本地进行的,只有少量的并发同步开销,远远小于新建TCP连接的开销。另外直连的方式无法限制Jedis对象的个数,在极端情况下可能会造成连接泄露,而连接池的形式可以有效的保护和控制资源的使用。但是直连的方式也并不是一无是处,表4-1给出两种方式各自的优劣势。

                            表2-1 Jedis直连方式和连接池方式对比

Jedis提供了JedisPool这个类作为对Jedis的连接池,同时使用了Apache的通用对象池工具common-pool作为资源的管理工具,下面是使用JedisPool操作Redis的代码示例

// common-pool连接池配置,这里使用默认配置,后面会介绍具体配置说明
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
// 初始化Jedis连接池
JedisPool jedisPool = new JedisPool(poolConfig, "127.0.0.1", 6379);

 


2)获取Jedis对象不再是直接生成一个Jedis对象进行直连,而是从连接池直接获取,代码如下:

这里可以看到在finally中依然是jedis.close()操作,为什么会把连接关闭呢,这不和连接池的原则违背了吗?但实际上Jedis的close()实现方式如下:

参数说明:
·dataSource!=null代表使用的是连接池,所以jedis.close()代表归还连接给连接池,而且Jedis会判断当前连接是否已经断开。
·dataSource=null代表直连,jedis.close()代表关闭连接。

      前面GenericObjectPoolConfig使用的是默认配置,实际它提供有很多参数,例如池子中最大连接数、最大空闲连接数、最小空闲连接数、连接活性检测,等等,例如下面代码:

1
2
3
4
5
6
7
8
9
10
11
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
// 设置最大连接数为默认值的5倍
poolConfig.setMaxTotal(GenericObjectPoolConfig.DEFAULT_MAX_TOTAL * 5);
// 设置最大空闲连接数为默认值的3倍
poolConfig.setMaxIdle(GenericObjectPoolConfig.DEFAULT_MAX_IDLE * 3);
// 设置最小空闲连接数为默认值的2倍
poolConfig.setMinIdle(GenericObjectPoolConfig.DEFAULT_MIN_IDLE * 2);
// 设置开启jmx功能
poolConfig.setJmxEnabled(true);
// 设置连接池没有连接后客户端的最大等待时间(单位为毫秒)
poolConfig.setMaxWaitMillis(3000); 

2.3 Redis中Pipeline的使用方法

  我们知道Redis提供了mget、mset方法,但是并没有提供mdel方法,如果想实现这个功能,可以借助Pipeline来模拟批量删除,虽然不会像mget和mset那样是一个原子命令,但是在绝大数场景下可以使用。下面代码是mdel删除的实现过程。这里为了节省篇幅,没有写try catch finally,没有关闭jedis,如下所示:

三、客户端管理

3.1 客户端API

总览:

client listclient setNameclient getNameclient killclient pausemonitor

1.client list

client list命令能列出与Redis服务端相连的所有客户端连接信息,例如下面代码是在一个Redis实例上执行client list的结果:


127.0.0.1:6379> client list
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
id=300210 addr=10.2.xx.215:61972 fd=3342 name= age=8054103 idle=8054103 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
id=5448879 addr=10.16.xx.105:51157 fd=233 name= age=411281 idle=331077 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=ttl
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
id=7125108 addr=10.10.xx.103:33403 fd=139 name= age=241 idle=1 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=del
id=7125109 addr=10.10.xx.101:58658 fd=140 name= age=241 idle=1 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=de


输出结果的每一行代表一个客户端的信息,可以看到每行包含了十几个属性,它们是每个客户端的一些执行状态,理解这些属性对于Redis的开发和运维人员非常有帮助。下面将选择几个重要的属性进行说明,其余通过表格的形式进行展示。

(1)标识:id、addr、fd、name

这四个属性属于客户端的标识:
·id:客户端连接的唯一标识,这个id是随着Redis的连接自增的,重启Redis后会重置为0。
·addr:客户端连接的ip和端口。
·fd:socket的文件描述符,与lsof命令结果中的fd是同一个,如果fd=-1代表当前客户端不是外部客户端,而是Redis内部的伪装客户端。
·name:客户端的名字,后面的client setName和client getName两个命令会对其进行说明。

(2)输入缓冲区:qbuf、qbuf-free

Redis为每个客户端分配了输入缓冲区,它的作用是将客户端发送的命令临时保存,同时Redis从会输入缓冲区拉取命令并执行,输入缓冲区为客户端发送命令到Redis执行命令提供了缓冲功能,如图fig.3.1 所示。

                fig.3-1  输入缓冲区基本模型

client list中qbufqbuf-free分别代表这个缓冲区的总容量和剩余容量,Redis没有提供相应的配置来规定每个缓冲区的大小,输入缓冲区会根据输入内容大小的不同动态调整,只是要求每个客户端缓冲区的大小不能超过1G,超过后客户端将被关闭。下面是Redis源码中对于输入缓冲区的硬编

码:


 

/* Protocol and I/O related defines */
#define REDIS_MAX_QUERYBUF_LEN (1024*1024*1024) /* 1GB max query buffer. */


 

输入缓冲使用不当会产生两个问题:

1)一旦某个客户端的输入缓冲区超过1G,客户端将会被关闭。

2)输入缓冲区不受maxmemory控制,假设一个Redis实例设置了maxmemory为4G,已经存储了2G数据,但是如果此时输入缓冲区使用了3G,已经超过maxmemory限制,可能会产生数据丢失、键值淘汰、OOM等情况(如图3-2所示)。

          fig.3-2 输入缓冲区超过了maxmemory

执行效果如下:


127.0.0.1:6390> info memory
# Memory
used_memory_human:5.00G
...
maxmemory_human:4.00G
....


 

缓冲区过大的原因有哪些?

1)Redis的处理速度跟不上输入缓冲区的输入速度,并且每次进入输入缓冲区的命令包含了大量bigkey,从而造成了输入缓冲区过大的情况。

2)Redis发生了阻塞,短期内不能处理命令,造成客户端输入的命令积压在了输入缓冲区,造成了输入缓冲区过大。

那么如何快速发现和监控呢?监控输入缓冲区异常的方法有两种:

1)通过定期执行client list命令,收集qbuf和qbuf-free找到异常的连接记录并分析,最终找到可能出问题的客户端。

2)通过info命令的info clients模块,找到最大的输入缓冲区,例如下面命令中的其中client_biggest_input_buf代表最大的输入缓冲区,例如可以设置超过10M就进行报警。

这两种方法各有自己的优劣势,表3-1对两种方法进行了对比。

表3-1 对比client list和info clients监控输入缓冲区的优劣势

(3)输出缓冲区:obl、oll、omem

首先,介绍一下各个参数的含义:

client list中的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>:如果客户端使用的输出缓冲区大于<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


 

PS:以上normal的值0 0 0 表示不受内存大小和时间限制。(存在风险)参考美团由于输出缓冲区过大导致内存占用飙升:https://blog.csdn.net/liqfyiyi/article/details/50894004

实际上输出缓冲区由两部分组成:固定缓冲区(16KB)和动态缓冲区,其中固定缓冲区返回比较小的执行结果,而动态缓冲区返回比较大的结果,例如大的字符串、hgetall、smembers命令的结果等。

监控输出缓冲区的方法依然有两种:

(1)通过定期执行client list命令,收集obl、oll、omem找到异常的连接记录并分析,最终找到可能出问题的客户端。

(2)通过info命令的info clients模块,找到输出缓冲区列表最大对象数。

这两种统计方法的优劣势和输入缓冲区是一样的,参考表3-1.

(4)客户端的存活状态

client list中的ageidle分别代表当前客户端已经连接的时间和最近一次的空闲时间。

(5)客户端的限制maxclients和timeout

Redis提供了maxclients参数来限制最大客户端连接数,一旦连接数超过maxclients,新的连接将被拒绝。maxclients默认值是10000,可以通过infoclients来查询当前Redis的连接数:


127.0.0.1:6379> info clients
# Clients
connected_clients:1414
...


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

       一般来说maxclients=10000在大部分场景下已经绝对够用,但是某些情况由于业务方使用不当(例如没有主动关闭连接)可能存在大量idle连接,无论是从网络连接的成本还是超过maxclients的后果来说都不是什么好事,因此Redis提供了timeout(单位为秒)参数来限制连接的最大空闲时间,一旦客户端连接的idle时间超过了timeout,连接将会被关闭,并可以通过config set timeout xx秒对最大客户端连接超时进行设置。

(6)客户端类型

client list中的flag是用于标识当前客户端的类型,例如flag=S代表当前客户端是slave客户端、flag=N代表当前是普通客户端,flag=O代表当前客户端正在执行monitor命令,表3-2列出了11种客户端类型:

                                fig. 3-2 客户端类型

2.client setName和client getName

client setName用于给客户端设置名字,这样比较容易标识出客户端的来源,如果想直接查看当前客户端的name,可以使用client getName命令。

3.client kill

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

4. client pause

client pause命令用于阻塞客户端timeout毫秒数,在此期间客户端连接将被阻塞。

          fig. 3-3 client pause命令示意图

5. monitor

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

             fig.3-4 高并发下monitor命令使用大量输出缓冲区

3.2 客户端相关配置

  1. timeout:检测客户端空闲连接的超时时间,一旦idle时间达到了
  2. timeout,客户端将会被关闭,如果设置为0就不进行检测。
  3. maxclients:客户端最大连接数,4.4.1节中的客户端存活状态部分已经进行分析,这里不再赘述,但是这个参数会受到操作系统设置的限制,第12章Linux相关配置小节还会对这个参数进行介绍。
  4. tcp-keepalive:检测TCP连接活性的周期,默认值为0,也就是不进行检测,如果需要设置,建议为60,那么Redis会每隔60秒对它创建的TCP连接进行活性检测,防止大量死连接占用系统资源。
  5. tcp-backlog:TCP三次握手后,会将接受的连接放入队列中,tcpbacklog就是队列的大小,它在Redis中的默认值是511。

3.3 客户端统计片段

1.info clients

下面就是一次info clients的执行结果:


127.0.0.1:6379> info clients

# Clients
connected_clients:1414
client_longest_output_list:0
client_biggest_input_buf:2097152
blocked_clients:0


 说明如下:

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

2.info stats

下面就是一次info stats的执行结果:


127.0.0.1:6379> info stats

# Stats
total_connections_received:80
...
rejected_connections:0


 

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

四、客户端常见异常

4.1 无法从连接池获取到连接

4.2 客户端读写超时

4.3 客户端连接超时

4.4 客户端缓冲区异常

4.5 edis正在加载持久化文件

4.6 Redis正在加载持久化文件

4.7 Redis使用的内存超过maxmemory配置

4.8 客户端连接数过大

参考文献

1. http://redisdoc.com/topic/index.html

2. https://blog.csdn.net/liqfyiyi/article/details/50894004

3.《Redis开发与运维》,付磊,张益军编著。

posted @   明明不平凡  阅读(287)  评论(0编辑  收藏  举报
编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
点击右上角即可分享
微信分享提示