Redis 开发与运维客户端
Posted 木兮同学
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis 开发与运维客户端相关的知识,希望对你有一定的参考价值。
文章目录
一、客户端通信协议
- 几乎所有的主流编程语言都有 Redis 的客户端,不考虑 Redis 非常流行的原因,如果站在技术的角度看原因还有两个
- 第一,客户端与服务端之间的通信协议是在
TCP 协议
之上构建的。 - 第二,Redis 制定了
RESP(REdis Serialization Protocol,Redis 序列化协议)实现客户端与服务端的正常交互
,这种协议简单高效,既能够被机器解析,又容易被人类识别。
- 第一,客户端与服务端之间的通信协议是在
二、Java 客户端 Jedis
获取Jedis
- 添加maven依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.8.2</version>
</dependency>
Jedis 的基本使用
- 演示获取 Jedis 对象,进行简单地set、get操作,要注意开发中关闭不用的连接资源。
Jedis jedis = null;
try
// 1. 生成一个Jedis对象,这个对象负责和指定Redis实例进行通信
jedis = new Jedis("127.0.0.1", 6379);
// 2. jedis执行set操作
jedis.set("hello", "world");
// 3. jedis执行get操作, value="world"
String value = jedis.get("hello");
System.out.println(value);
catch (Exception e)
log.error(e.getMessage(), e);
finally
if (jedis != null)
jedis.close();
- Jedis 操作其他五种数据结构演示
// 1.string
// 输出结果:OK
jedis.set("hello", "world");
// 输出结果:world
jedis.get("hello");
// 输出结果:1
jedis.incr("counter");
// 2.hash
jedis.hset("myhash", "f1", "v1");
jedis.hset("myhash", "f2", "v2");
// 输出结果:f1=v1, f2=v2
jedis.hgetAll("myhash");
// 3.list
jedis.rpush("mylist", "1");
jedis.rpush("mylist", "2");
jedis.rpush("mylist", "3");
// 输出结果:[1, 2, 3]
jedis.lrange("mylist", 0, -1);
// 4.set
jedis.sadd("myset", "a");
jedis.sadd("myset", "b");
jedis.sadd("myset", "a");
// 输出结果:[b, a]
jedis.smembers("myset");
// 5.zset
jedis.zadd("myzset", 99, "tom");
jedis.zadd("myzset", 66, "peter");
jedis.zadd("myzset", 33, "james");
// 输出结果:[[["james"],33.0], [["peter"],66.0], [["tom"],99.0]]
jedis.zrangeWithScores("myzset", 0, -1);
- 参数除了可以是字符串,Jedis还提供了字节数组的参数,例如:
public String set(final String key, String value)
public String set(final byte[] key, final byte[] value)
public byte[] get(final byte[] key)
public String get(final String key)
- 有了这些API的支持,就可以将Java对象序列化为二进制,当应用需要获取Java对象时,使用 get(final byte[]key) 函数将字节数组取出,然后反序列化为Java对象即可。
Jedis 连接池使用
- Jedis 直连方式和连接池方式对比
上面是 Jedis 的直连方式,所谓直连是指Jedis每次都会新建TCP连接,使用后再断开连接,对于频繁访问Redis的场景显然不是高效的使用方式,因此
生产环境中一般使用连接池的方式对Jedis连接进行管理
。
方式 | 优点 | 缺点 |
---|---|---|
直连 | 简单方便,适用于少量长期连接的场景 | 1.存在每次新建/关闭TCP连接开销 2.资源无法控制,极端情况会出现连接泄漏 3. Jedis 对象线程不安全 |
连接池 | 1.无需每次连接都生成 Jedis 对象,降低开销 2.使用连接池的形式保护和控制资源的使用 | 相对于直连使用相对麻烦,尤其在资源的管理上需要很多参数来保证,一旦规划不合理也会出现问题 |
- 连接池方式使用
// common-pool连接池配置,这里使用默认配置,后面小节会介绍具体配置说明
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
// 初始化Jedis连接池
JedisPool jedisPool = new JedisPool(poolConfig, "127.0.0.1", 6379);
Jedis jedis = null;
try
// 1. 从连接池获取jedis对象
jedis = jedisPool.getResource();
// 2. 执行操作
jedis.get("hello");
catch (Exception e)
log.error(e.getMessage(), e);
finally
if (jedis != null)
// 如果使用JedisPool,close操作不是关闭连接,代表归还连接池
jedis.close();
Jedis 中 Pipeline 的使用
前面介绍了 Pipeline 能够将一组 Redis 命令进行组装,通过一次RTT传输给Redis,再将这组Redis命令的执行结果按顺序返回给客户端。
- 比如来实现一个批量删除 mdel 的操作,实际上Redis提供了mget、mset方法,但是并没有提供mdel方法
Jedis jedis = new Jedis("127.0.0.1");
// 1)生成pipeline对象
Pipeline pipeline = jedis.pipelined();
// 2)pipeline执行命令,注意此时命令并未真正执行
for (String key : keys)
pipeline.del(key);
// 3)执行命令
pipeline.sync();
- 除了
pipeline.sync()
,还可以使用pipeline.syncAndReturnAll()
将pipeline的命令进行返回,例如下面代码将 set 和 incr 做了一次 pipeline 操作,并顺序打印了两个命令的结果
Jedis jedis = new Jedis("127.0.0.1");
Pipeline pipeline = jedis.pipelined();
pipeline.set("hello", "world");
pipeline.incr("counter");
List<Object> resultList = pipeline.syncAndReturnAll();
for (Object object : resultList)
System.out.println(object);
Jedis 的 Lua 脚本使用
- Jedis 中执行 Lua 脚本和 redis-cli 十分类似,Jedis 提供了三个重要的函数实现 Lua 脚本的执行
Object eval(String script, int keyCount, String... params)
Object evalsha(String sha1, int keyCount, String... params)
String scriptLoad(String script)
eval函数
- script:Lua脚本内容
- keyCount:键的个数
- params:相关参数KEYS和ARGV
scriptLoad和evalsha函数
要一起使用- scriptSha:脚本的SHA1
- keyCount:键的个数
- params:相关参数KEYS和ARGV
// 首先使用scriptLoad将脚本加载到Redis中
String scriptSha = jedis.scriptLoad(script);
String key = "hello";
Object result = jedis.evalsha(scriptSha, 1, key);
// 打印结果为world
System.out.println(result);
三、客户端管理
Redis 提供了客户端相关 API 对其状态进行监控和管理。
客户端 API
client list
:列出与 Redis 服务端相连的所有客户端连接信息client setName 和 client getName
:分别用于给客户端设置和获取名字,这样比较容易表示出客户端的来源client kill
:用于杀掉指定 IP 地址和端口的客户端client pause
:用于阻塞客户端 timeout 毫秒数,在此期间客户端连接将被阻塞。monitor
:用于监控 Redis 正在执行的命令
客户端常见异常
- 无法从连接池获取到连接
- JedisPool 中的 Jedis 对象个数是有限的,默认是 8 个。如果都被占用的,并且没有归还,此时调用者还要从 JedisPool 中借用 Jedis,就需要进行等待,如果
在 maxWaitMillis 时间内仍然无法获取到 Jedis 对象
就会抛如下异常:
redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
...
Caused by: java.util.NoSuchElementException: Timeout waiting for idle object at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:449)
- 还有一种情况,就是设置了 blockWhenExhausted=false,那么调用者发现池子中没有资源时,会
立即抛出异常不进行等待
,如下:
redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
...
Caused by: java.util.NoSuchElementException: Pool exhausted as org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:446)
- 对于这个问题,造成没有资源的原因非常多,可能如下:
- 客户端:高并发下连接池设置过小
- 客户端:没有正确使用连接池,比如没有释放
- 客户端:存在慢查询操作
- 服务端:Redis 服务端由于一些原因造成了客户端命令执行过程的阻塞
- 客户端读写超时
- 出现时会抛出下面的异常:
redis.clients.jedis.exceptions.JedisConnectionException:
java.net.SocketTimeoutException: Read timed out
- 造成原因可能有:
- 读写超时时间设置得过短
- 命令本身就比较慢
- 客户端与服务端网络不正常
- Redis 自身发生阻塞
- 客户端连接超时
- 出现时会抛出下面的异常:
redis.clients.jedis.exceptions.JedisConnectionException:
java.net.SocketTimeoutException: connect timed out
- 造成原因可能有:
- 连接超时设置过短
- Redis 发生阻塞
- 客户端与服务端网络不正常
- 客户端缓冲区异常
- 出现时会抛出下面的异常:
redis.clients.jedis.exceptions.JedisConnectionException: Unexpected end of stream.
- 造成原因可能有:
- 输出缓冲区满了
- 长时间闲置连接被服务端主动断开
- 不正常并发读写:Jedis 对象同时被多个线程并发操作,可能会出现上述问题
- Lua 脚本正在执行
- Redis 正在加载持久化文件
- 出现时会抛出下面的异常:
redis.clients.jedis.exceptions.JedisDataException: LOADING Redis is loading the dataset in memory
- Redis 使用的内存超过 maxmemory 配置
- 出现时会抛出下面的异常:
redis.clients.jedis.exceptions.JedisDataException: OOM command not allowed when used memory > 'maxmemory'.
- 客户端连接数过大
redis.clients.jedis.exceptions.JedisDataException: ERR max number of clients reached
四、客户端案例分析
Redis 内存陡增
- 可能原因
确实有大量写入
- 排查是否有
客户端缓冲区
造成主节点内存陡增,使用info clients
命令查询。
- 处理方法
- 使用
client kill
命令杀掉这个连接,让其他客户段恢复正常些数据即可。
- 使用
- 后期处理
- 从运维层面禁止 monitor 命令
- 从开发层面进行培训,禁止在生产环境中使用 monitor 命令
- 限制输出缓冲区的大小
- 使用专业的 Redis
运维工具
,比如后面介绍的 CacheCloud,上述问题都会得到报警,快速发现和定位问题
客户端周期性超时
- 现象
- 客户端现象:客户端出现大量超时
- 服务端现象:服务端并没有明显的异常,只是有一些慢查询操作
- 分析
- 网络原因:服务端和客户端之间的网络出现周期性问题
- Redis 本身:查看 Redis 日志统计
- 客户端:对比慢查询日志的历史记录,看看是否慢查询导致
- 后期处理
- 从运维层面,
监控慢查询
,一旦超过阈值,就发出报警 - 从开发层面,
加强对于 Redis 的理解
,避免不正确的使用方式 - 使用专业的 Redis
运维工具
,同上
- 从运维层面,
来源:《Redis 开发与运维》第 4 章 客户端
以上是关于Redis 开发与运维客户端的主要内容,如果未能解决你的问题,请参考以下文章