文章目录

  • 一、Redis中的辅助功能
  • 1.1 慢查询
  • 1.1.1 慢查询相关的两个参数
  • 1.1.2 慢查询使用建议
  • 1.2 Redis Shell
  • 1.2.1 redis-cli
  • 1.2.2 redis-benchmark
  • 1.3 Pipeline
  • 1.4 事务
  • 1.4.1 事务命令
  • 1.4.2 Redis事务保证原子性吗,支持回滚吗
  • 1.5 Lua
  • 1.5.1 使用Lua脚本
  • 1.5.2 管理Lua脚本
  • 二、Redis客户端
  • 2.1 Jedis的使用
  • 2.1.1 Jedis的简单使用
  • 2.1.2 Jedis常用API
  • 2.1.3 Jedis连接池
  • 2.2 客户端管理
  • 2.2.1 client list
  • 2.2.2 client setName和client getName
  • 2.2.3 client kill
  • 2.2.4 client pause
  • 2.2.5 monitor
  • 2.2.6 config set
  • 2.2.7 info stats
  • 2.3 常见异常
  • 2.3.1 无法从连接池获取到连接
  • 2.3.2 客户端读写超时
  • 2.3.3 客户端连接超时
  • 2.3.4 客户端缓冲区异常
  • 2.3.5 JedisDataException
  • 2.3.6 Redis使用的内存超过maxmemory配置
  • 2.3.7 客户端连接数过大
  • 2.4 Redis客户端的一些问题
  • 2.4.1 Redis和Redisson有什么关系?
  • 2.4.2 Jedis和Redisson对比有什么优缺点?



一、Redis中的辅助功能

1.1 慢查询

  Redis客户端执行一条命令分为如下4个部分:

redis客户端使用 redis客户端操作_java


  慢查询只统计命令执行的时间。

1.1.1 慢查询相关的两个参数

  • slowlog-log-slower-than
      slowlog-log-slower-than是预设阀值,单位是微秒,默认值是10000,假如执行了一条“很慢”的命令(例如keys*),如果它的执行时间超过了10000微秒,那么它将被记录在慢查询日志中。

如果slowlog-log-slower-than=0会记录所有的命令,slowlog-log-slower-than<0对于任何命令都不会进行记录。

  • slowlog-max-len
      Redis使用了一个列表来存储慢查询日志,slowlog-max-len就是列表的最大长度。一个新的命令满足慢查询条件时被插入到这个列表中,当慢查询日志列表已处于其最大长度时,最早插入的一个命令将从列表中移出,例如slowlog-max-len设置为5,当有第6条慢查询插入的话,那么队头的第一条数据就出列,第6条慢查询就会入列。

  Redis中有两种修改配置的方法,一种是修改配置文件,另一种是用config set命令动态修改,示例:

config set slowlog-log-slower-than 20000
	config set slowlog-max-len 1000
	config rewrite

  从上面可以看出慢查询日志是存放在Redis内存列表中的,但是Redis并没有暴露这个列表的键,而是通过一组命令来实现对慢查询日志的访问和管理。慢查询相关命令:

  • 1、获取慢查询日志
slowlog get [n]

  每个慢查询日志有4个属性组成,分别是慢查询日志的标识id、发生时间戳、命令耗时、执行命令和参数。

  • 2、获取慢查询日志列表当前的长度
slowlog len
  • 3、清空慢查询日志列表
slowlog reset

1.1.2 慢查询使用建议

  slowlog-max-len配置建议:线上建议调大慢查询列表,记录慢查询时Redis会对长命令做截断操作,并不会占用大量内存。增大慢查询列表可以减缓慢查询被剔除的可能,例如线上可设置为1000以上。
  slowlog-log-slower-than配置建议:默认值超过10毫秒判定为慢查询,需要根据Redis并发量调整该值。由于Redis采用单线程响应命令,对于高流量的场景,如果命令执行时间在1毫秒以上,那么Redis最多可支撑OPS不到1000。因此对于高OPS场景的Redis建议设置为1毫秒。
  由于慢查询日志是一个先进先出的队列,也就是说如果慢查询比较多的情况下,可能会丢失部分慢查询命令,为了防止这种情况发生,可以定期执行slow get命令将慢查询日志持久化到其他存储中。

1.2 Redis Shell

1.2.1 redis-cli

  可以执行redis-cli-help命令来查看该命令的全部参数。

  • 1、-r
      -r(repeat)选项代表将命令执行多次,示例:
redis-cli -r 3 ping
  • 2、-i
      -i(interval)选项代表每隔几秒执行一次命令,但是-i选项必须和-r选项一起使用。每隔1秒执行一次ping命令,一共执行5次示例:
redis-cli -r 5 -i 1 ping
  • 3、-x
      -x选项代表从标准输入(stdin)读取数据作为redis-cli的最后一个参数,将字符串world作为set hello的值示例:
echo "world" | redis-cli -x set hello
  • 4、-a
      如果Redis配置了密码,可以用-a(auth)选项,有了这个选项就不需要手动输入auth命令。
  • 5、–slave
      --slave选项是把当前客户端模拟成当前Redis节点的从节点,可以用来获取当前Redis节点的更新操作。
  • 6、–rdb
      --rdb选项会请求Redis实例生成并发送RDB持久化文件,保存在本地。可用来做持久化文件的定期备份。
  • 7、–eval
      用于执行指定Lua脚本。
  • 8、–latency
      latency有三个选项,分别是–latency、–latency-history、–latency-dist,它们都可以检测网络延迟。
      --latency可以测试不同客户端到目标Redis示例的网络延迟。比如客户端B和Redis在机房B,客户端A在机房A,则机房A和机房B是跨地区的:
      --latency的执行结果只有一条,如果想以分时段的形式了解延迟信息,可以使用--latency-history选项,示例:
redis-cli -h 10.10.xx.xx --latency-history
	min: 0, max: 1, avg: 0.28 (1330 samples) -- 15.01 seconds range …
	min: 0, max: 1, avg: 0.05 (1364 samples) -- 15.01 seconds range

  延时信息每15秒输出一次,可以通过-i参数控制间隔时间。
  --latency-dist会使用统计图表的形式从控制台输出延迟统计信息。

  • 9、–stat
      --stat选项可以实时获取Redis的重要统计信息,示例:

1.2.2 redis-benchmark

  redis-benchmark可以为Redis做基准性能测试。

  • 1、-c
      -c(clients)表示客户端的并发数量(默认是50)。
  • 2、-n
      -n(num)表示客户端请求总量(默认是100000)。
  • 3、-q
      -q仅仅显示redis-benchmark的吞吐量信息,示例:
redis-benchmark -c 100 -n 20000 -q
	PING_INLINE: 74349.45 requests per second
	PING_BULK: 68728.52 requests per second
	SET: 71174.38 requests per second …
	LRANGE_500 (first 450 elements): 11299.44 requests per second
	LRANGE_600 (first 600 elements): 9319.67 requests per second
	MSET (10 keys): 70671.38 requests per second
  • 4、-r
      -r(random)可以向Redis插入更多随机的键。示例:
redis-benchmark -c 100 -n 20000 -r 10000

  -r选项会在key、counter键上加一个12位的后缀,-r10000代表只对后四位做随机处理。

  • 5、-P
      -P表示每个请求pipeline的数据量(默认为1)。
  • 6、-k
      -k表示客户端是否使用keepalive,1为使用,0为不使用,默认值为1。
  • 7、-t
      -t可以对指定命令进行基准测试。示例:
redis-benchmark -t get,set -q
	SET: 98619.32 requests per second
	GET: 97560.98 requests per second

1.3 Pipeline

  Redis客户端执行一条命令分为如下四个过程:发送命令、命令排队、命令执行、返回结果。这四步合起来称为Round Trip Time(RTT,往返时间)。
  Redis提供了批量操作命令(例如mget、mset等),有效地节约RTT。但大部分命令是不支持批量操作的,例如要执行n次hgetall命令,并没有mhgetall命令存在,需要消耗n次RTT。
  Pipeline(流水线)机制能改善上面这类问题,它能将一组Redis命令进行组装,通过一次RTT传输给Redis,再将这组Redis命令的执行结果按顺序返回给客户端。
  Redis命令真正执行的时间通常在微秒级别,所以才会有Redis性能瓶颈是网络这样的说法
  redis-cli的–pipe选项实际上就是使用Pipeline机制。
  非Pipeline和Pipeline执行命令的区别:

  1. Pipeline执行速度一般比逐条执行要快。
  2. 客户端和服务端的网络延时越大,Pipeline的效果越明显。

  原生批量命令与Pipeline的区别:

  1. 原生批量命令是原子的,Pipeline是非原子的。
  2. 原生批量命令是一个命令对应多个key,Pipeline支持多个命令。
  3. 原生批量命令是Redis服务端支持实现的,而Pipeline需要服务端和客户端的共同实现。

  Pipeline虽然好用,但是每次Pipeline组装的命令个数不能没有节制,否则一次组装Pipeline数据量过大,一方面会增加客户端的等待时间,另一方面会造成一定的网络阻塞,可以将一次包含大量命令的Pipeline拆分成多次较小的Pipeline来完成。

1.4 事务

  Redis 事务的本质是通过MULTI、EXEC、WATCH等一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。
  总结说:redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令
  在 Redis 中,事务具有一致性和隔离性,并且当 Redis 运行在某种特定的持久化模式下时,事务也具有持久性。但是,Redis中的事务不具有一致性,可能部分命令会执行成功,而另一部分命令执行失败。

  Redis 事务可以一次执行多个命令, 并且带有以下三个重要的保证:

  1. 批量操作在发送 EXEC 命令前被放入队列缓存。
  2. 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。
  3. 在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。

  一个事务从开始到执行会经历以下三个阶段:开始事务、命令入队、执行事务。

  事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做。

1.4.1 事务命令

  Redis事务功能是通过MULTI、EXEC、DISCARD和WATCH实现的。事务功能有以下特点:

  1. Redis会将一个事务中的所有命令序列化,然后按顺序执行。
  2. redis 不支持回滚,“Redis 在事务失败时不进行回滚,而是继续执行余下的命令”, 所以 Redis 的内部可以保持简单且快速。
  3. 如果在一个事务中的命令出现错误,那么所有的命令都不会执行
  4. 如果在一个事务中出现运行错误,那么正确的命令会被执行
  • 1、Multi
      Multi用于开启一个事务。 Multi执行之后,客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被按照先后顺序放到一个队列中,当EXEC命令被调用时,所有队列中的命令才会被执行。
  • 2、Exec
    Exec命令用于执行所有事务块内的命令。这些命令按先后顺序排列。 当操作被打断时,返回空值 nil 。
      前两个命令的使用示例:
  • 3、Discard
    discard 命令用于取消事务(清空事务队列),放弃执行事务块内的所有命令。示例:
  • 4、Watch
      Watch命令是一个乐观锁,可以为 Redis 事务提供(CAS)行为。其功能是:可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,监控一直持续到EXEC命令。示例:
  • 5、Unwatch
    Unwatch命令用于取消WATCH命令对所有key的监视。示例:

1.4.2 Redis事务保证原子性吗,支持回滚吗

  Redis中,单条命令是原子性执行的,但事务不保证原子性,且没有回滚。事务中任意命令执行失败,其余的命令仍会被执行
  如果想实现回滚,就需要用WATCH命令,具体的做法是:

  需要在MULTI之前使用WATCH来监控某些键值对,然后使用MULTI命令来开启事务,执行对数据结构操作的各种命令,此时这些命令入队列。
  当使用EXEC执行事务时,首先会比对WATCH所监控的键值对,如果没发生改变,它会执行事务队列中的命令,提交事务;如果发生变化,将不会执行事务中的任何命令,同时事务回滚。当然无论是否回滚,Redis都会取消执行事务前的WATCH命令。

redis客户端使用 redis客户端操作_redis客户端使用_02

1.5 Lua

  Lua语言提供了如下几种数据类型:booleans(布尔)、numbers(数值)、strings(字符串)、tables(表格)。

  • 1、字符串的简单使用
      定义一个字符串示例:
local strings val = "world"

  local代表val是一个局部变量,如果没有local代表是全局变量。打印该变量示例:

-- 结果是 "world"
	print(hello)
  • 2、数组的简单使用
      如果要使用类似数组的功能,可以用tables类型。Lua的数组下标从1开始计算。定义tables类型的变量示例:
local tables myArray = {"redis", "jedis", true, 88.0}
	--true
	print(myArray[3])

  使用for遍历数组示例:

local int sum = 0
for i = 1, 100
do
	sum = sum + i
end
-- 输出结果为 5050
print(sum)

  table类型变量前加一个#,就代表table类型变量的长度。示例:

for i = 1, #myArray
do
	print(myArray[i])
end

  遍历索引和值示例:

for index,value in ipairs(myArray)
do
	print(index)
	print(value)
end

  除了可以用for,也可以用while来遍历,示例:

local int sum = 0
local int i = 0
while i <= 100
do
	sum = sum +i
	i = i + 1
end
-- 输出结果为 5050
print(sum)
  • 3、哈希的简单使用
      如果要使用类似哈希的功能,同样可以使用tables类型。示例定义一个tables,每个元素包含了key和value,其中strings1…string2是将两个字符串进行连接:
local tables user_1 = {age = 28, name = "tome"}
--user_1 age is 28
print("user_1 age is " .. user_1["age"])

  如果要遍历user_1,可以使用Lua的内置函数pairs,示例:

for key,value in pairs(user_1)
do print(key .. value)
end
  • 4、函数定义
      在Lua中,函数以function开头,以end结尾,funcName是函数名,中间部分是函数体。示例:
function funcName()
	...
end
contact 函数将两个字符串拼接:
function contact(str1, str2)
	return str1 .. str2
end
--"hello world"
print(contact("hello ", "world"))

1.5.1 使用Lua脚本

  • 1、eval和evalsha
      在Redis中执行Lua脚本有两种方法:eval和evalsha。
      eval使用语法:
eval 脚本内容 key 个数 key 列表 参数列表

  如果Lua脚本较长,还可以使用redis-cli–eval直接执行文件。

  eval命令和–eval参数本质是一样的,客户端如果想执行Lua脚本,首先在客户端编写好Lua脚本代码,然后把脚本作为字符串发送给服务端,服务端会将执行结果返回给客户端,过程图示:

redis客户端使用 redis客户端操作_redis_03


  除了使用eval,Redis还提供了evalsha命令来执行Lua脚本。使用evalsha执行Lua脚本过程图示:

redis客户端使用 redis客户端操作_redis客户端使用_04


  如图所示,首先要将Lua脚本加载到Redis服务端,得到该脚本的SHA1校验和,evalsha命令使用SHA1作为参数可以直接执行对应Lua脚本,避免每次发送Lua脚本的开销。这样客户端就不需要每次执行脚本内容,而脚本也会常驻在服务端,脚本功能得到了复用。

  加载脚本:script load命令可以将脚本内容加载到Redis内存中,例如将lua_get.lua加载到Redis中,得到SHA1:

# redis-cli script load "$(cat lua_get.lua)"
	"7413dc2440db1fea7c0a0bde841fa68eefaf149c"

  执行脚本:evalsha的使用方法如下,参数使用SHA1值,执行逻辑和eval一致:

evalsha 脚本 SHA1 值 key 个数 key 列表 参数列表
  • 2、Lua的Redis API
      Lua可以使用redis.call函数实现对Redis的访问,Lua使用redis.call调用了Redis的set和get操作示例:
redis.call("set", "hello", "world")
	redis.call("get", "hello")

  除此之外,Lua还可以使用redis.pcall函数实现对Redis的调用,redis.call和redis.pcall的不同在于,如果redis.call执行失败,那么脚本执行结束会直接返回错误,而redis.pcall会忽略错误继续执行脚本。

1.5.2 管理Lua脚本

  Redis提供了4个命令实现对Lua脚本的管理。

  • 1、script load
script load script

  此命令用于将Lua脚本加载到Redis内存中。

  • 2、script exists
scripts exists sha1 [sha1 … ]

  此命令用于判断sha1是否已经加载到Redis内存中。示例:

127.0.0.1:6379> script exists a5260dd66ce02462c5b5231c727b3f7772c0bcc5
1) (integer) 1

  返回结果代表sha1[sha1…]被加载到Redis内存的个数。

  • 3、script flush
      此命令用于清除Redis内存已经加载的所有Lua脚本。
  • 4、script kill
      此命令用于杀掉正在执行的Lua脚本。
      Redis提供了一个lua-time-limit参数,默认是5秒,它是Lua脚本的“超时时间”,但这个超时时间仅仅是当Lua脚本时间超过lua-time-limit后,向其他命令调用发送BUSY的信号,但是并不会停止掉服务端和客户端的脚本执行,所以当达到lua-time-limit值之后,其他客户端在执行正常的命令时,将会收到“Busy Redis is busy running a script”错误,并且提示使用script kill或者shutdown nosave命令来杀掉这个busy的脚本。
      如果当前Lua脚本正在执行写操作,那么script kill将不会生效。

二、Redis客户端

  几乎所有的主流编程语言都有Redis的客户端,Redis的客户端的特点:

  1. 客户端与服务端之间的通信协议是在TCP协议之上构建的。
  2. Redis制定了RESP(REdis Serialization Protocol,Redis序列化协议)实现客户端与服务端的正常交互,这种协议简单高效,既能够被机器解析,又容易被人类识别。

2.1 Jedis的使用

  Jedis是Redis的Java实现的客户端,其API提供了比较全面的Redis命令的支持。

2.1.1 Jedis的简单使用

  • 1、引入依赖
      创建一个SpringBoot项目,引入Jedis的依赖:
<dependency>
	<groupId>redis.clients</groupId>
	<artifactId>jedis</artifactId>
	<version>2.8.2</version>
</dependency>

  对于第三方开发包,版本的选择也是至关重要的,通常来讲选取第三方开发包有如下两个策略:

  1. 选择比较稳定的版本,也就是尽可能选择稳定的里程碑版本。
  2. 选择更新活跃的第三方开发包,例如Redis3.0有了Redis Cluster新特性。
  • 2、Jedis的使用
      就可以查看Jedis的使用,示例:
//生成一个Jedis对象,初始化了Redis实例的IP和端口,这个对象和指定Redis实例进行通信
		Jedis jedis = new Jedis("127.0.0.1", 6379);
		jedis.set("hello", "world");
		System.out.println(jedis.get("hello")); //world

  除了上面的Jedis构造方法,还有一个包含了四个参数的构造函数是比较常用的:

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

  4个参数的意义:

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

  在实际使用Jedis时,肯定要注意关闭流之类的操作:

Jedis jedis = null;
		try {
			jedis = new Jedis("127.0.0.1", 6379);
			System.out.println(jedis.get("hello"));
		} catch (Exception e) {
			System.out.println(e.getMessage());
		} finally {
			//关闭流
			if (jedis != null) {
				jedis.close();
			}
		}

  Jedis对于Redis五种数据结构的简单操作示例:

Jedis jedis = new Jedis("127.0.0.1", 6379);
			// 1.string
			jedis.set("hello", "world"); 
			System.out.println(jedis.get("hello")); //world
			// 2.hash
			jedis.hset("myhash", "f1", "v1");
			jedis.hset("myhash", "f2", "v2");
			System.out.println(jedis.hgetAll("myhash")); //{f2=v2, f1=v1}
			// 3.list
			jedis.rpush("mylist", "1");
			jedis.rpush("mylist", "2");
			jedis.rpush("mylist", "3");
			System.out.println(jedis.lrange("mylist", 0, -1)); //[1, 2, 3]
			// 4.set
			jedis.sadd("myset", "a");
			jedis.sadd("myset", "b");
			jedis.sadd("myset", "a");
			System.out.println(jedis.smembers("myset")); //[a, b]
			// 5.zset
			jedis.zadd("myzset", 99, "tom");
			jedis.zadd("myzset", 66, "peter");
			jedis.zadd("myzset", 33, "james");
			System.out.println(jedis.zrange("myzset", 0, -1)); //[james, peter, tom]

2.1.2 Jedis常用API

  • 1、Jedis中对键通用的操作

方法

描述

返回值 /补充说明

boolean exists(String key)

判断某个键是否存在

set(String key,String value)

新增键值对(key,value)

返回String类型的OK代表成功

Set< String > jedis.keys(*)

获取所有key

返回set 无序集合

del(String key)

删除指定key

expire(String key,int i)

设置键为key的过期时间为i秒

int jedis.ttl(String key)

获取key数据项的剩余时间(秒)

persist(String key)

移除键为key属性项的生存时间限制

type(String key)

查看键为key所对应value的数据类型

  • 2、Jedis中的字符串操作
      字符串类型是Redis中最为基础的数据存储类型,在Redis中字符串类型的Value最多可以容纳的数据长度是512M。

语法

描述

set(String key,String value)

增加(或覆盖)数据项

setnx(String key,String value)

不覆盖增加数据项(重复的不插入)

setex(String ,int t,String value)

增加数据项并设置有效时间

del(String key)

删除键为key的数据项

get(String key)

获取键为key对应的value

append(String key, String s)

在key对应value 后边追加字符串 s

mset(String k1,String V1,String K2,String V2,…)

增加多个键值对

String[] mget(String K1,String K2,…)

获取多个key对应的value

del(new String[](String K1,String K2,.... ))

删除多个key对应的数据项

String getSet(String key,String value)

获取key对应value并更新value

String getrange(String key , int i, int j)

获取key对应value第i到j字符 ,从0开始,包头包尾

  • 3、Jedis中的增减操作

语法

描述

incr(String key)

将key对应的value加1

incrBy(String key,int n)

将key对应的value加n

decr(String key)

将key对应的value减1

decrBy(String key , int n)

将key对应的value减n

  • 4、Jedis中的列表操作

语法

描述

lpush(String key, String v1, String v2,....)

添加一个List , 如果已经有该List对应的key, 则按顺序在左边追加 一个或多个

rpush(String key , String vn)

key对应list右边插入元素

lrange(String key,int i,int j)

获取key对应list区间[i,j]的元素,注:从左边0开始,包头包尾

ltrim(String key,int i,int j)

删除list区间[i,j] 之外的元素

lpop(String key)

左弹出一个key对应的元素

rpop(String key)

右弹出一个key对应的元素

llen(String key)

获取key对应list的长度

lset(String key,int index,String val)

修改key对应的list指定下标index的元素

lindex(String key,int index)

获取key对应list下标为index的元素

  • 5、Jedis中的集合操作

语法

描述

sadd(String key,String v1,String v2,…)

添加一个set

smenbers(String key)

获取key对应set的所有元素

srem(String key,String val)

删除集合key中值为val的元素

srem(String key, Sting v1, String v2,…)

删除值为v1, v2 , …的元素

sinter(String key1, String key2)

获取集合key1和集合key2的交集

sunion(String key1, String key2)

获取集合key1和集合key2的并集

sdiff(String key1, String key2)

获取集合key1和集合key2的差集

  • 6、Jedis中的有序集合操作

语法

描述

zadd(String key,Map map)

添加一个ZSet

hset(String key,int score , int val)

往 ZSet插入一个元素(Score-Val)

zrange(String key, int i , int j)

获取ZSet 里下表[i,j] 区间元素Val

zscore(String key,String value)

获取ZSet里value元素的Score

zrem(String key,String value)

删除ZSet里的value元素

zcard(String key)

获取ZSet的元素个数

zcount(String key , int i ,int j)

获取ZSet总score在[i,j]区间的元素个数

zincrby(String key,int n , String value)

把ZSet中value元素的score+=n

  • 7、Jedis中的哈希操作

语法

描述

hmset(String key,Map map)

添加一个Hash

hset(String key , String key, String value)

向Hash中插入一个元素(K-V)

hgetAll(String key)

获取Hash的所有(K-V) 元素

hkeys(String key)

获取Hash所有元素的key

hvals(String key)

获取Hash所有元素的value

hdel(String key , String k1, String k2,…)

从Hash中删除一个或多个元素

hlen(String key)

获取Hash中元素的个数

hexists(String key,String K1)

判断Hash中是否存在指定key对应的元素

hmget(String key,String K1,String K2)

获取Hash中一个或多个元素value

2.1.3 Jedis连接池

  客户端连接Redis使用的是TCP协议,直连的方式每次需要建立TCP连接,而连接池的方式是可以预先初始化好Jedis连接,每次只需要从Jedis连接池借用即可,只有少量的并发同步开销,远远小于新建TCP连接的开销。两者的比较:

优点

缺点

直连

简单方便,适用于少量长期连接的场景

1、存在每次新建/关闭TCP连接开销;

2、资源无法控制,可能会出现连接泄露;

3、Jedis对象线程不安全

连接池

1、无需每次连接都生成Jedis对象,降低开销;

2、使用连接池控制开销

使用较麻烦,要熟悉各个参数的意义

  连接池的基本使用:

//使用默认配置
		GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
		//初始化Jedis连接池
		JedisPool jedisPool = new JedisPool(poolConfig, "127.0.0.1", 6379);
		Jedis jedis = null;
		try {
			//从连接池获取jedis对象
			jedis = jedisPool.getResource();
			System.out.println(jedis.get("hello")); //world
		} catch (Exception e) {
		} finally {
			if (jedis != null) {
				//close操作不是关闭连接,代表归还连接池
				jedis.close();
			}
		}

  在GenericObjectPoolConfig中,可以设置很多关于Redis连接池的属性,一些较常用的设置:

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);

  GenericObjectPoolConfig的重要属性:

参数名

含义

默认值

maxActive

连接池中最大连接数

8

maxIdle

连接池中最大空闲的连接数

8

minIdle

连接池中最少空闲的连接数

0

maxWaitMillis

当连接池资源耗尽后,调用者的最大等待时间(单位为毫秒),一般不建议使用默认值

-1永远不超时,一直等:

jmxEnabled

是否开启jmx监控,如果应用开启了jmx端口,并且jmxEnabled设置为true,就可以通过jconsole或jvisualvm看到关于连接池的相关统计,有助于了解连接池的使用情况,并且可以针对做监控统计

true

minEvictableIdleTimeMillis

连接的最小空闲时间,达到此值后空闲连接将被移除

1000L x 60L x 30毫秒 = 30分钟

numTestsPerEvictionRun

做空闲连接检测时,每次的采样数

3

testOnBorrow

向连接池借用连接时是否做连接有效性检测(ping),无效连接将被移除,每次借用多执行一次ping命令

false

testOnReturn

是否做周期性空闲检测

false

testWhileIdle

向连接池借用连接时是否做连接空闲检测,空闲超时的连接会被移除

false

timeBetweenEvictionRunsMillis

空闲连接的检测周期(单位为毫秒)

-1:表示不做检测

blockWhenExhausted

当连接池用尽后,调用者是否要等待,这个参数和maxWaitMillis对应,当此参数为true时,maxWaitMillis 才生效

false

2.2 客户端管理

2.2.1 client list

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

redis客户端使用 redis客户端操作_Redis_05


  输出结果的每一行代表一个客户端的信息。

  • 1、id
      客户端连接的唯一标识,这个id是随着Redis的连接自增的,重启Redis后会重置为0。
  • 2、addr
      客户端连接的ip和端口。
  • 3、fd
      socket的文件描述符。
  • 4、name
      客户端的名字。
  • 5、qbuf、qbuf-free
      这两个属性都表述输入缓冲区相关的信息。
    Redis服务端为每个Redis客户端分配了输入缓冲区,它的作用是将客户端发送的命令临时保存,同时服务端从会输入缓冲区拉取命令并执行。图示:

    qbuf代表这个缓冲区的总容量,qbuf-free代表这个缓冲区的剩余容量。Redis没有提供相应的配置来规定每个缓冲区的大小,输入缓冲区会根据输入内容大小的不同动态调整,只是要求每个客户端缓冲区的大小不能超过1G,超过后客户端将被关闭
      对于Redis服务端而言,假设一个Redis实例设置了maxmemory(Redis服务端的最大内存)为4G,已经存储了2G数据,但是如果此时输入缓冲区使用了3G,已经超过maxmemory限制,可能会产生数据丢失、键值淘汰、OOM等情况。
      查看Redis服务端内存配置的命令是info memory,示例:

      造成输入缓冲区过大的原因有哪些?
  1. 主要是因为Redis的处理速度跟不上输入缓冲区的输入速度,并且每次进入输入缓冲区的命令包含了大量bigkey。
  2. Redis发生了阻塞,短期内不能处理命令,造成客户端输入的命令积压在了输入缓冲区。

  监控输入缓冲区异常的方法有两种:

  1. 通过定期执行client list命令,收集qbuf和qbuf-free找到异常的连接记录并分析,最终找到可能出问题的客户端。
  2. 通过info clients命令,找到最大的输入缓冲区,下面命令中的其中client_biggest_input_buf代表最大的输入缓冲区,例如可以设置超过10M就进行报警:

      client list和info clients的对比:

命令

优点

缺点

client list

能精准分析每个客户端来定位问题

执行速度较慢,频繁执行存在阻塞Redis的可能

info clients

执行速度比client list快,分析过程较为简单

不能精确定位到客户端;

不能显示所有输入缓冲区的总量,只能显示最大量

  • 6、obl、oll、omem
      这三个属性都表述输出缓冲区相关的信息
      Redis服务端为每个Redis客户端分配了输出缓冲区,它的作用是保存命令执行的结果返回给客户端,为服务端和客户端交互返回结果提供缓冲,图示:

      输出缓冲区的容量可以通过参数client-output-buffer-limit来进行设置,按照客户端的不同分为三种:普通客户端、发布订阅客户端、slave客户端,图示:

      client-output-buffer-limit命令的使用:client-output-buffer-limit <class> <hard limit> <soft limit> <soft seconds>。参数:

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

  client-output-buffer-limit的默认配置:

redis客户端使用 redis客户端操作_redis客户端使用_06


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

  固定缓冲区使用的是字节数组,动态缓冲区使用的是列表。当固定缓冲区存满后会将Redis新的返回结果存放在动态缓冲区的队列中,队列中的每个对象就是每个返回结果。

  client list中的obl代表固定缓冲区的长度,oll代表动态缓冲区列表的长度,omem代表使用的字节数。例如下面代表当前客户端的固定缓冲区的长度为0,动态缓冲区有4869个对象,两个部分共使用了133081288字节=126M内存:

redis客户端使用 redis客户端操作_客户端_07


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

  1. 通过定期执行client list命令,收集obl、oll、omem找到异常的连接记录并分析,最终找到可能出问题的客户端。
  2. 通过info命令的info clients模块,找到输出缓冲区列表最大对象数。示例:

    client_longest_output_list代表输出缓冲区列表最大对象数
      如何预防输出缓冲区出现异常呢?主要方法有以下几种:

  1、监控并设置阀值,超过阀值及时处理。
  2、根据client-output-buffer-limit命令对普通缓冲区设置,示例:client-output-buffer-limit normal 20mb 10mb 120
  3、及时监控内存,一旦发现内存抖动频繁,可能就是输出缓冲区过大。

  • 7、age和idle
    age代表当前客户端已经连接的时间,idle代表当前客户端最近一次的空闲时间。示例:
      上面这条记录代表当期客户端连接Redis的时间为8888581秒,其中空闲了8888581秒,实际上这种就属于不太正常的情况,当age等于idle时,说明连接一直处于空闲状态
  • 8、和maxclients/timeout配合使用
      Redis提供了maxclients参数来限制最大客户端连接数,一旦连接数超过maxclients,新的连接将被拒绝。maxclients默认值是10000,可以通过info clients来查询当前Redis的连接数:

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

      一般来说maxclients=10000在大部分场景下已经绝对够用。同时,Redis提供了timeout(单位为秒)参数来限制连接的最大空闲时间,一旦客户端连接的idle时间超过了timeout,连接将会被关闭。Redis 默认的 timeout 是 0 ,也就是不会检测客户端的空闲
      将timeout设置为30秒示例:

      在实际开发和运维中,需要将timeout设置成大于0,例如可以设置为300秒,这样可以避免Redis的客户端使用不当或者客户端本身的一些问题,造成没有及时释放客户端连接的问题。
  • 9、flags
      flags是用于标识当前客户端的类型,例如flags=S代表当前客户端是slave客户端、flags=N代表当前是普通客户端。客户端类型:

客户端类型

说明

N

普通客户端

M

master节点

S

slave节点

o

正在执行monitor命令

x

正在执行事务

b

正在等待阻塞时间

u

客户端未被阻塞

A

尽可能快地关闭连接

  • 10、client list所有参数

参数

含义

id

客户端连接id

addr

客户端连接IP和端口

fd

socket的文件描述符

name

客户端连接名

age

客户端连接存活时间

idle

客户端连接空闲时间

flags

客户端连接标识

db

当前客户端正在使用的数据库索引下标

sub/psub

当前客户端订阅的频道数或模式数

multi

当前事务中已执行命令个数

qbuf

输入缓冲区总容量

qbuf-ree

输入缓冲区剩余容量

obl

固定缓冲区的长度

oll

动态缓冲区列表的长度

omem

固定缓冲区和动态缓存区使用的容量

cmd

当前客户端最后一次执行的命令

2.2.2 client setName和client getName

  用于给当前客户端设置和获取名称,示例:

redis客户端使用 redis客户端操作_客户端_08

2.2.3 client kill

  用法为client kill ip:port,此命令用于杀掉指定IP地址和端口的客户端。

2.2.4 client pause

  用法为’client pause timeout’,表示阻塞客户端timeout毫秒数,在此期间客户端连接将被阻塞。

  1. client pause只对普通和发布订阅客户端有效,对于主从复制(从节点内部伪装了一个客户端)是无效的,所以此命令可以用来让主从复制保持一致。
  2. client pause可以用一种可控的方式将客户端连接从一个Redis节点切换到另一个Redis节点。

2.2.5 monitor

  monitor命令用于监控Redis正在执行的命令。示例:

redis客户端使用 redis客户端操作_客户端_09

2.2.6 config set

  该命令用于设置客户端属性。

  • 1、timeout
      检测客户端空闲连接的超时时间,一旦idle时间达到了timeout,客户端将会被关闭,如果设置为0就不进行检测。示例:
  • 2、maxclients
      客户端最大连接数。示例:
  • 3、tcp-keepalive
      检测TCP连接活性的周期,默认值为0,也就是不进行检测,如果需要设置,建议为60,那么Redis会每隔60秒对它创建的TCP连接进行活性检测,防止大量死连接占用系统资源。示例:
  • 4、tcp-backlog
      TCP三次握手后,会将接受的连接放入队列中,tcp-backlog就是队列的大小,它在Redis中的默认值是511。通常来讲这个参数不需要调整,示例:

2.2.7 info stats

   info stats可以统计一些Redis总的状态,示例:

redis客户端使用 redis客户端操作_redis_10


   total_connections_received表示Redis自启动以来处理的客户端连接数总

数。

  rejected_connections表示Redis自启动以来拒绝的客户端连接数。

2.3 常见异常

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

  1、假设JedisPool中的Jedis对象个数是8个,8个Jedis对象被占用,并且没有归还,此时还要从JedisPool中借用Jedis,就需要进行等待(例如设置maxWaitMillis>0),如果在maxWaitMillis时间内仍然无法获取到Jedis对象就会抛出JedisConnectionException
  2、设置了blockWhenExhausted=false,那么调用者发现池子中没有Jedis资源时,会立即抛出异常不进行等待。

该属性的作用是:连接耗尽时是否阻塞, false报异常,ture阻塞直到超时, 默认true。

  一些可能会造成连接池中资源被耗尽的原因:

1、高并发下连接池设置过小。
2、没有正确使用连接池,比如没有进行释放。
3、存在慢查询操作,这些慢查询持有的Jedis对象归还速度会比较慢。
4、客户端是正常的,但是Redis服务端由于一些原因造成了客户端命令执行过程的阻塞。

2.3.2 客户端读写超时

  SocketTimeoutException,原因有以下几种:

1、读写超时间设置得过短。
2、命令本身执行就比较慢。
3、客户端与服务端网络不正常。
4、Redis服务端自身发生阻塞。

2.3.3 客户端连接超时

  JedisConnectionException,原因有以下几种:

1、连接超时设置得过短,修改示例:jedis.getClient().setConnectionTimeout(time);,单位毫秒。
2、Redis服务端发生阻塞,造成tcp-backlog已满,造成新的连接失败。
3、客户端与服务端网络不正常。

2.3.4 客户端缓冲区异常

  Jedis在调用Redis时,如果出现客户端数据流异常,会出现JedisConnectionException。原因有以下几种:

1、输出缓冲区满。
2、长时间闲置连接被服务端主动断开。
3、不正常并发读写:Jedis对象同时被多个线程并发操作,可能会出现该异常。

2.3.5 JedisDataException

  Jedis调用Redis时,如果Redis正在加载持久化文件,会出现JedisDataException

redis客户端使用 redis客户端操作_客户端_11

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

  Jedis执行写操作时,如果Redis的使用内存大于maxmemory的设置,会报如下异常:

redis客户端使用 redis客户端操作_redis_12

2.3.7 客户端连接数过大

  如果客户端连接数超过了maxclients,新申请的连接就会出现如下异常:

redis客户端使用 redis客户端操作_客户端_13

2.4 Redis客户端的一些问题

2.4.1 Redis和Redisson有什么关系?

  Redisson是一个高级的分布式协调Redis客户端,能帮助用户在分布式环境中轻松实现一些Java的对象,如:Bloom filter、BitSet、Set、SortedSet、Map、ConcurrentMap、List、Queue、BlockingQueue、Semaphore、ReadWriteLock、AtomicLong、CountDownLatch等。

2.4.2 Jedis和Redisson对比有什么优缺点?

  Jedis是Redis的Java实现的客户端,其AP提供了比较全面的Redis命令的支持。
  Redisson实现了分布式和可扩展的Java数据结构,和Jedis相比,功能较为简单,不支持字符串操作,不支持排序、事务、管道、分区等Redis特性。Redisson的宗旨是促进使用者对Redis的关注分离,让使用者将精力更集中地放在处理业务逻辑上。