redis开发与运维-redis04-redis客户端Jedis与连接池及客户端异常模拟
文章目录
- 【README】
- 【1】redis客户端通信协议
- 【2】java客户端Jedis连接redis集群
- 【2.1】Jedis基本用法
- 【2.2】Jedis操作5种数据类型代码实践
- 【2.3】Jedis使用序列化api操作
- 【2.3.1】操作Jedis字节数组api代码实践
- 【3】Jedis连接池
- 【3.1】Jedis连接池JedisPool代码实践
- 【3.1.1】池化Jedis对象#close方法解析
- 【3.2】Jedis连接池JedisPool配置属性概览
- 【4】redis客户端常见异常总结(共计8个)
- 【4.1】问题1-无法从连接池获取jedis连接
- 【4.1.1】模拟无法从连接池获取连接
- 【4.2】问题2-客户端读写超时
- 【4.2.1】模拟客户端读写超时场景
- 【4.3】问题3-客户端连接超时
- 【4.3.1】客户端连接超时场景
- 【4.4】问题4-客户端缓冲区异常
- 【4.4.1】模拟客户端缓冲区异常场景
- 【4.5】问题5-lua脚本正在执行(仅了解)
- 【4.6】redis正在加载持久化文件(仅了解)
- 【4.7】redis使用的内存超过maxmemory设置
- 【4.7.1】模拟redis使用的内存超过maxmemory设置
- 【4.8】客户端连接数过大
- 【4.8.1】模拟客户端连接数过大异常
【README】
本文总结自《redis开发与运维》,作者付磊,张益军,墙裂推荐;
- 本文使用的redis版本是 7.0.15 ;
- 本文主要介绍java的redis客户端Jedis及redis连接池;
- 代码参见: https://github.com/TomJourney/redisDiscover/tree/master
【1】redis客户端通信协议
1)redis客户端介绍:
- redis客户端与服务器之间的通信协议是在tcp协议上建立的;
- redis制定了 RESP(REdis Serialization Protocol, redis序列化协议)实现客户端与服务器的交互,该协议简单高效,能够被机器解析,也对开发者友好;
2)RESP协议的报文演示:
[root@centos211 ~]# telnet 192.168.163.211 6379
Trying 192.168.163.211...
Connected to 192.168.163.211.
Escape character is '^]'.
# 新增或更新key
set user2 tom2
+OK
# 命令错误
sethx^H^H
-ERR unknown command 'set', with args beginning with:
# 自增key
incr counter
:1
# 获取key值
get user2
$4
tom2
# 设置或更新多个key-value
mset user3 tom3 user4 tom4
+OK
# 获取多个key的value
mget user3 user4
*2 # 显然,*2表示key的个数
$4 # $4 表示值的长度
tom3
$4
tom4
【2】java客户端Jedis连接redis集群
【2.1】Jedis基本用法
1)引入jedis的maven依赖
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>5.2.0</version>
</dependency>
2)新建jedis简单客户端操作redis
public class TomeJedisClient01Main {
public static void main(String[] args) {
long start = System.currentTimeMillis();
Jedis jedis = null;
try {
jedis = new Jedis("192.168.163.211", 6379, 10000, 3000);
jedis.set("tom:jedis:user1", "tom1");
System.out.println(jedis.get("user1"));
} catch (Exception e) {
e.printStackTrace(); // 真实业务不要这么写
} finally {
if (Objects.nonNull(jedis)) {
// 记得关闭redis连接
jedis.close();
}
System.out.println("耗时(秒)=" + (System.currentTimeMillis() - start) / 1000);
}
}
}
【运行结果】
tom1
耗时(秒)=0
【2.2】Jedis操作5种数据类型代码实践
1)Jedis操作5种数据类型代码实践:
/**
* @author Tom
* @version 1.0.0
* @ClassName TomeJedisClient02Main.java
* @Description jedis操作5种数据类型
* @createTime 2024年12月25日 09:30:00
*/
public class TomeJedisClient02Main {
public static void main(String[] args) {
// 10000为连接超时时间, 3000为写入redis超时时间,仅演示,生产环境不要这么写
Jedis jedis = new Jedis("192.168.163.211", 6379, 10000, 3000);
try {
oprFiveTypeKey(jedis);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (Objects.nonNull(jedis)) {
jedis.close();
}
}
}
private static void oprFiveTypeKey(Jedis jedis) {
System.out.println("\n========== 字符串类型 ==========");
// 1 字符串类型
// 实际可以是字符串(简单字符串,复杂字符串如json),数字(整数,浮点数),二进制(图片,音视频),值最大不超过512M
jedis.set("name01", "tom01");
System.out.println(jedis.get("name01")); // tom01
// 1.1 数字
System.out.println(jedis.incr("counter01")); // 4
System.out.println(jedis.get("counter01")); // 4
System.out.println("\n========== hash类型 ==========");
// 2 hash类型
// 哈希类型定义:指键的值本身又是一个键值对结构。 如value={{field1, value1}, {field2, value2}}
jedis.hset("person", "name", "tom01");
jedis.hset("person", "addr", "chengdu");
System.out.println(jedis.hget("person", "name")); // tom01
System.out.println(jedis.hgetAll("person"));// {name=tom01, addr=chengdu}
System.out.println("\n========== list有序列表 ==========");
// 3 list有序列表
// 列表类型定义: 用来存储多个有序的字符串, 如a,b,c这3个元素从左到右组成了一个有序列表
jedis.del("userList"); // 先删除key
jedis.rpush("userList", "tom01", "tom02", "tom03");
System.out.println(jedis.lrange("userList", 0, -1)); // [tom01, tom02, tom03]
System.out.println("\n========== set无序集合 ==========");
// 4 set无序集合
// 集合类型定义:集合类型用于保存多个字符串元素,但不允许重复元素,且元素无序,不能通过通过下标获取元素;
jedis.sadd("userSet", "tom01", "tom02", "tom03");
System.out.println(jedis.spop("userSet")); // tom03
System.out.println(jedis.smembers("userSet"));// [tom02, tom01]
System.out.println("\n========== zset有序集合 ==========");
// 5 zset有序集合
jedis.zadd("userSortedSet", 3, "tom03");
jedis.zadd("userSortedSet", 2, "tom02");
jedis.zadd("userSortedSet", 1, "tom01");
System.out.println(jedis.zrangeByScore("userSortedSet", 2, 3)); // [tom02, tom03]
}
}
【2.3】Jedis使用序列化api操作
1)引入序列化与反序列化:TomeJedisClient02Main类全部使用java字符串格式key-value操作redis,能够覆盖一部分业务场景;此外,有一些业务场景需要使用字节格式存储(方便加解密),如用户token
2)Jedis本身没有提供序列化工具,开发者需要自己引入序列化工具,如xml,Json,谷歌的Protobuf,facebook的Thrift等;本文选择Protobuf;
【2.3.1】操作Jedis字节数组api代码实践
1)protostuff是 Protobuf的java客户端,maven依赖如下:
<!-- https://mvnrepository.com/artifact/io.protostuff/protostuff-core -->
<dependency>
<groupId>io.protostuff</groupId>
<artifactId>protostuff-core</artifactId>
<version>1.8.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.protostuff/protostuff-runtime -->
<dependency>
<groupId>io.protostuff</groupId>
<artifactId>protostuff-runtime</artifactId>
<version>1.8.0</version>
</dependency>
2)使用Jedis字节数组api代码实践
【TomeJedisClientMainUsingSerialization】
public class TomeJedisClientMainUsingSerialization {
public static void main(String[] args) {
Jedis jedis = new Jedis("192.168.163.211", 6379, 10000, 3000);
byte[] keyByteArr = ProtostuffSerializationUtils.serialize("car01");
try {
// 序列化后设置redis键值对
jedis.set(keyByteArr, ProtostuffSerializationUtils.serialize(Car.build(2, "tesla", "shanghai")));
// 根据key获取redis值并反序列化
Car deserializeCar = ProtostuffSerializationUtils.deserialize(jedis.get(keyByteArr), Car.class);
System.out.println(deserializeCar);
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
} finally {
if (Objects.nonNull(jedis)) {
jedis.close();
}
}
}
}
【日志】
Car{id=2, name='tesla', birthAddr='shanghai'}
【ProtostuffSerializationUtils】Protobuf序列化工具
public class ProtostuffSerializationUtils {
// 缓冲区
private static LinkedBuffer BUFFER = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
//Schema缓存
private static final Map<String, Schema<?>> SCHEMA_CACHE = new ConcurrentHashMap<>();
public static <T> byte[] serialize(T object) {
Class<T> clazz = (Class<T>) object.getClass();
try {
return ProtostuffIOUtil.toByteArray(object, getSchemaInstance(clazz), BUFFER);
} catch (Exception e) {
throw new IllegalStateException(e);
} finally {
BUFFER.clear();
}
}
public static <T> T deserialize(byte[] data, Class<T> clazz) {
Schema<T> schemaInstance = getSchemaInstance(clazz);
T object = schemaInstance.newMessage();
ProtostuffIOUtil.mergeFrom(data, object, schemaInstance);
return object;
}
private static <T> Schema<T> getSchemaInstance(Class<T> clazz) {
return (Schema<T>) SCHEMA_CACHE.computeIfAbsent(clazz.getName(), x -> RuntimeSchema.getSchema(clazz));
}
private ProtostuffSerializationUtils() {
// do nothing.
}
}
【Car】javabean
public class Car {
/**
* 编号
*/
private int id;
/**
* 名称
*/
private String name;
/**
* 产地
*/
private String birthAddr;
public static Car build(int id, String name, String birthAddr) {
Car car = new Car();
car.id = id;
car.name = name;
car.birthAddr = birthAddr;
return car;
}
@Override
public String toString() {
return "Car{" +
"id=" + id +
", name='" + name + '\'' +
", birthAddr='" + birthAddr + '\'' +
'}';
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getBirthAddr() {
return birthAddr;
}
public void setBirthAddr(String birthAddr) {
this.birthAddr = birthAddr;
}
}
【3】Jedis连接池
1)问题与解决方法:
- 问题:上述章节2,redis客户端Jedis使用直连方式连接到redis服务器;直连指的是Jedis每次都会新建tcp连接,使用后立即断开;若第2次使用Jedis,则又会重新建立tcp连接;每次使用Jedis都建立连接,网络io开销多,影响系统性能;
- 解决方法:使用连接池管理Jedis连接; 应用启动时,预先初始化Jedis连接并放到连接池JedisPool中;每次要连接Redis,直接从池中获取Jedis对象,用完之后把池化Jedis连接归还给Jedis连接池;
2)Jedis直连与连接池优缺点对比
优点 | 缺点 | |
---|---|---|
直连 | 简单方便,适用于少量长期连接的场景 | 1)存在每次新建或关闭tcp连接,网络io成本高; 2)连接数量无法控制,可能会导致连接泄露; 3)Jedis对象线程不安全; |
连接池 | 1)无需每次连接都生成Jedis对象,降低网络io; 2)使用连接池的形式保护与控制资源的使用; | 与直连相比,连接池使用相对麻烦;连接池资源的管理需要参数来保证, 若连接池参数设置不合理,可能产生其他问题; |
【3.1】Jedis连接池JedisPool代码实践
1)Jedis提供了JedisPool实现连接池, 同时使用Apache的通用对象池工具common-pool作为资源的管理工具; 代码实践如下。
【PooledJedisMain】池化redis测试入口类
public class PooledJedisMain {
public static void main(String[] args) {
PooledJedisFactory busiJedisFactoryUsingPool = PooledJedisFactory.build();
// 从jedis连接池获取jedis对象
Jedis jedis = busiJedisFactoryUsingPool.getJedis();
System.out.println(jedis);
try {
// 执行操作
jedis.set("user01", "zhagnsan01");
System.out.println(jedis.get("user01"));
// 执行操作
jedis.set("user02", "zhagnsan02");
System.out.println(jedis.get("user02"));
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
} finally {
if (Objects.nonNull(jedis)) {
// JedisPool连接池返回的Jedis对象,其close方法不是关闭连接,而是归还给连接池
jedis.close();
}
}
}
}
【日志】
Jedis{Connection{DefaultJedisSocketFactory{192.168.163.211:6379}}}
zhagnsan01
zhagnsan02
【PooledJedisFactory】池化redis工厂
public class PooledJedisFactory {
private JedisPool jedisPool;
public static PooledJedisFactory build() {
// 创建连接池配置
GenericObjectPoolConfig<Jedis> poolConfig = new GenericObjectPoolConfig<>();
// 设置连接池属性
poolConfig.setMaxTotal(10); // 最大连接数
poolConfig.setMaxIdle(5); // 最大空闲连接数
poolConfig.setMinIdle(1); // 最小空闲连接数
poolConfig.setJmxEnabled(true); // 开启jmx
poolConfig.setMaxWait(Duration.ofSeconds(3)); // 连接池没有连接后客户端的最大等待时间(单位毫秒)
PooledJedisFactory busiJedisFactoryUsingPool =
new PooledJedisFactory(poolConfig, "192.168.163.211", 6379);
return busiJedisFactoryUsingPool;
}
private PooledJedisFactory(GenericObjectPoolConfig<Jedis> poolConfig, String host, int port) {
jedisPool = new JedisPool(poolConfig, host, port);
}
public Jedis getJedis() {
return jedisPool.getResource();
}
}
【3.1.1】池化Jedis对象#close方法解析
1)Jedis#close方法源码
public void close() {
if (this.dataSource != null) { // 表示使用的是连接池
Pool<Jedis> pool = this.dataSource;
this.dataSource = null;
if (this.isBroken()) { // 判断当前连接是否已经断开
pool.returnBrokenResource(this);
} else {
pool.returnResource(this);
}
} else { // 表示直连
// 直接关闭jedis连接
this.connection.close();
}
}
【代码解说】
- dataSource != null: 表示使用的是连接池, 所以jedis.close() 方法表示归还连接给连接池,而jedis会判断当前连接是否已经断开;
- dataSource=null :表示直连, jedis.close() 方法表示直接关闭jedis连接;
【3.2】Jedis连接池JedisPool配置属性概览
1)Jedis连接池配置使用 apache的通用对象池工具common-pool中的GenericObjectPoolConfig类;
【4】redis客户端常见异常总结(共计8个)
【4.1】问题1-无法从连接池获取jedis连接
1)无法从连接池获取jedis连接的原因:
- 原因1-客户端: 高并发下连接池设置过小,供不应求;
- 原因2-客户端:没有正确使用连接池,jedis连接没有释放;
- 原因3-客户端:存在慢查询,慢查询会导致业务线程归还Jedis连接速度变慢,最终导致连接池被顶满;
- 原因4-服务端:redis服务器执行客户端命令时存在阻塞,与慢查询类似,会导致业务线程归还Jedis连接速度变慢,最终导致连接池被顶满;
【4.1.1】模拟无法从连接池获取连接
1)业务场景:5个线程从包含3个连接的连接池获取连接;
【PooledJedisConnTimeoutMain】带有连接超时时间的池化Jedis测试案例
public class PooledJedisGetConnFailMain01 {
public static void main(String[] args) {
// 注意: 连接超时时间给定为2000毫秒
PooledJedisWithConnSoTimeoutFactory pooledJedisFactory = PooledJedisWithConnSoTimeoutFactory.build(3, 2000);
// 新建带有10个线程的线程池
int threadCount = 5;
ExecutorService executorService = Executors.newFixedThreadPool(threadCount);
for (int i = 0; i < threadCount; i++) {
final int index = i;
executorService.execute((() -> pooledJedisFactory.getJedis(index)));
}
// 关闭连接池
executorService.shutdown();
}
}
【打印日志】
index=1, 耗时(秒)=0
index=3, 耗时(秒)=0
index=2, 耗时(秒)=0
index=4, 耗时(秒)=2 // 显然,我们设置的连接超时时间为2000毫秒=2秒
index=0, 耗时(秒)=2
Exception in thread "pool-1-thread-5" java.lang.RuntimeException: redis.clients.jedis.exceptions.JedisException: Could not get a resource from the pool
at com.tom.redisdiscover.jedispool.timeout.PooledJedisWithConnSoTimeoutFactory.getJedis(PooledJedisWithConnSoTimeoutFactory.java:42)
at com.tom.redisdiscover.jedispool.timeout.PooledJedisConnTimeoutMain.lambda$main$0(PooledJedisConnTimeoutMain.java:21)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
at java.base/java.lang.Thread.run(Thread.java:842)
Caused by: redis.clients.jedis.exceptions.JedisException: Could not get a resource from the pool
at redis.clients.jedis.util.Pool.getResource(Pool.java:42)
at redis.clients.jedis.JedisPool.getResource(JedisPool.java:378)
at com.tom.redisdiscover.jedispool.timeout.PooledJedisWithConnSoTimeoutFactory.getJedis(PooledJedisWithConnSoTimeoutFactory.java:40)
... 4 more
Caused by: java.util.NoSuchElementException: Timeout waiting for idle object, borrowMaxWaitDuration=PT2S
【PooledJedisWithConnSoTimeoutFactory】
public class PooledJedisWithConnSoTimeoutFactory {
private JedisPool jedisPool;
public static PooledJedisWithConnSoTimeoutFactory build(int maxTotal, int maxWaitMillis) {
// 创建连接池配置
GenericObjectPoolConfig<Jedis> poolConfig = new GenericObjectPoolConfig<>();
// 设置连接池属性
poolConfig.setMaxTotal(maxTotal); // 最大连接数
poolConfig.setMaxIdle(5); // 最大空闲连接数
poolConfig.setMinIdle(1); // 最小空闲连接数
poolConfig.setJmxEnabled(true); // 开启jmx
poolConfig.setMaxWait(Duration.ofMillis(maxWaitMillis)); // 连接池没有连接后客户端的最大等待时间(单位毫秒)
poolConfig.setBlockWhenExhausted(true); // 当连接池用尽后,调用者是否等待;该参数为true时,maxWait才起作用
return new PooledJedisWithConnSoTimeoutFactory(poolConfig, "192.168.163.211", 6379);
}
private PooledJedisWithConnSoTimeoutFactory(
GenericObjectPoolConfig<Jedis> poolConfig, String host, int port) {
jedisPool = new JedisPool(poolConfig, host, port);
}
public Jedis getJedis(int index) {
long start = System.currentTimeMillis();
try {
return jedisPool.getResource();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
long costOfSecond = (System.currentTimeMillis() - start) / 1000;
System.out.printf("index=%s, 耗时(秒)=%d\n", index, costOfSecond);
}
}
}
【补充】设置获取jedis连接超时时间的注意事项:
- 当blockWhenExhausted=true时,maxWaitMillis才会生效,否则不会生效;setter方法分别是setBlockWhenExhausted, setMaxWait;
- 当blockWhenExhausted=true时,而maxWaitMillis不设置,则默认maxWaitMillis为-1, -1表示永不超时; 这是有非常大的问题的;即redis连接不上,则应用启动一直阻塞;
【4.2】问题2-客户端读写超时
1)客户端读写超时原因:
- 读写超时时间设置过短;
- 命令本身执行慢;
- 客户端与服务器网络不正常;
- redis自身发生阻塞;
【4.2.1】模拟客户端读写超时场景
1)查询包含300w个元素的列表,超时时间设置为100ms,报Socket超时;
【JedisRwSocketTimeoutMain02】
public class JedisRwSocketTimeoutMain02 {
public static void main(String[] args) {
int connectTimeout = 3000;
int soTimeout = 100;
long start = System.currentTimeMillis();
try {
Jedis jedis = new Jedis("192.168.163.211", 6379, connectTimeout, soTimeout);
List<String> charList01 = jedis.lrange("charList01", 0, -1);
System.out.println(charList01.size());
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
long costOfMilliSecond = System.currentTimeMillis() - start;
System.out.printf("耗时(毫秒)=%d\n", costOfMilliSecond);
}
}
}
【日志】
耗时(毫秒)=211
Exception in thread "main" java.lang.RuntimeException: redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketTimeoutException: Read timed out
at com.tom.redisdiscover.jedispool.clientcommonexception.JedisRwSocketTimeoutMain02.main(JedisRwSocketTimeoutMain02.java:24)
【4.3】问题3-客户端连接超时
1)客户端连接超时原因:
- 连接超时设置过短;
- redis发生阻塞,导致 tcp-backlog 已满, 造成新的连接超时;
- 客户端与服务器网络不正常;
【4.3.1】客户端连接超时场景
1)业务场景:连接到一个不存在的redis服务器;(192.168.163.222 机器上没有部署redis服务器)
public class JedisConnectionTimeoutMain03 {
public static void main(String[] args) {
int connectTimeout = 3000;
int soTimeout = 2000;
long start = System.currentTimeMillis();
try {
Jedis jedis = new Jedis("192.168.163.222", 6379, connectTimeout, soTimeout);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
long costOfSecond = (System.currentTimeMillis() - start) / 1000;
System.out.printf("耗时(秒)=%d\n", costOfSecond);
}
}
}
【日志】
耗时(秒)=3
Exception in thread "main" java.lang.RuntimeException: redis.clients.jedis.exceptions.JedisConnectionException: Failed to connect to 192.168.163.222:6379.
at com.tom.redisdiscover.jedispool.timeout.JedisConnectionTimeoutMain.main(JedisConnectionTimeoutMain.java:20)
【4.4】问题4-客户端缓冲区异常
1)客户端缓冲区异常原因:
- 输出缓冲区满;
- 长时间闲置连接被服务端主动断开;
- 不正常并发读写:Jedis对象同时被多个线程并发操作,可能该异常;
【4.4.1】模拟客户端缓冲区异常场景
1)redis服务器的普通客户端的输出缓冲区设置为1k,最大2k,如下;
【 redis-6379.conf 】 redis服务器启动配置文件
port 6379
dir /redis/data
dbfilename "dump-6379.rdb"
bind 192.168.163.211
protected-mode no
## normal client conf (redis服务器的普通客户端的输出缓冲区设置为1k,最大2k)
client-output-buffer-limit normal 2kb 1kb 1
## slave node client conf
client-output-buffer-limit replica 256mb 64mb 60
## pubsub client conf
client-output-buffer-limit pubsub 32mb 8mb 60
注意: 若client-output-buffer-limit normal 配置为0 0 0 ,则表示不限制;
client-output-buffer-limit normal 0 0 0
2)从redis读取包含300w个元素的有序列表,报输出缓冲区异常
【JedisClientBufferExceptionMain04】 客户端缓冲区异常测试案例
public class JedisClientBufferExceptionMain04 {
public static void main(String[] args) {
int connectTimeout = 3000;
int soTimeout = 60000 * 3600;
long start = System.currentTimeMillis();
try {
Jedis jedis = new Jedis("192.168.163.211", 6379, connectTimeout, soTimeout);
for (int i = 0; i < 100000; i++) {
List<String> charList01 = jedis.lrange("charList01", 0, -1);
System.out.println(charList01.size());
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
long costOfMilliSecond = System.currentTimeMillis() - start;
System.out.printf("耗时(毫秒)=%d\n", costOfMilliSecond);
}
}
}
【日志】 Unexpected end of stream 表示客户端数据流异常
耗时(毫秒)=270
Exception in thread "main" java.lang.RuntimeException: redis.clients.jedis.exceptions.JedisConnectionException: Unexpected end of stream.
at com.tom.redisdiscover.jedispool.clientcommonexception.JedisRwSocketTimeoutMain02.main(JedisRwSocketTimeoutMain02.java:28)
Caused by: redis.clients.jedis.exceptions.JedisConnectionException: Unexpected end of stream.
【4.5】问题5-lua脚本正在执行(仅了解)
【4.6】redis正在加载持久化文件(仅了解)
【4.7】redis使用的内存超过maxmemory设置
1)原因:Jedis执行写操作时,如果redis的使用内存大于 maxmemory的设置,会报如下异常;
OOM command not allowed when used memory 'maxmemory'
【4.7.1】模拟redis使用的内存超过maxmemory设置
1)设置内存大小为10kb
【redis-6379.conf 】
# max memory conf
maxmemory 10kb
2)向redis写入26w个字符的value
【JedisClientOverMaxMemoryExceptionMain07】 redis使用的内存超过maxmemory设置测试案例
public class JedisClientOverMaxMemoryExceptionMain07 {
private static final String VALUE = "abcdefghijklmnopqrstuvwxyz";
public static void main(String[] args) {
int connectTimeout = 3000;
int soTimeout = 6000 * 3600;
long start = System.currentTimeMillis();
try {
Jedis jedis = new Jedis("192.168.163.211", 6379, connectTimeout, soTimeout);
int size = 10000;
StringBuilder result = new StringBuilder();
for (int i = 0; i < size; i++) {
result.append(VALUE + i).append("#");
}
// 执行写操作
jedis.set("bigKey", result.toString());
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
long costOfMilliSecond = System.currentTimeMillis() - start;
System.out.printf("耗时(毫秒)=%d\n", costOfMilliSecond);
}
}
}
【报错日志】
耗时(毫秒)=148
Exception in thread "main" java.lang.RuntimeException: redis.clients.jedis.exceptions.JedisDataException: OOM command not allowed when used memory > 'maxmemory'.
at com.tom.redisdiscover.jedispool.clientcommonexception.JedisClientOverMaxMemoryExceptionMain07.main(JedisClientOverMaxMemoryExceptionMain07.java:32)
Caused by: redis.clients.jedis.exceptions.JedisDataException: OOM command not allowed when used memory > 'maxmemory'.
【4.8】客户端连接数过大
1)若客户端连接数超过maxclients, 新申请的连接报如下异常。
[root@centos211 ~]# redis-cli -h 192.168.163.211 -p 6379
192.168.163.211:6379> set name1 tom1
(error) ERR max number of clients reached
这类问题比较棘手就, 因为无法执行redis命令修复问题。
2)解决方法:
- 方法1-客户端问题:若maxclient参数比较大,通常是由于应用服务对于redis客户端使用不当造成的。如应用服务是分布式架构,每个服务内部使用连接池操作redis,每个连接池的最大连接为10,若100个实例,则最大连接数为1000个; 【下线部分服务节点,把连接数降下来】
- 方法2-服务器问题:若客户端无法处理,而当前redis集群是哨兵或Cluster模式,可以考虑将当前redis做故障转移;
【4.8.1】模拟客户端连接数过大异常
1)设置最大连接数
# max clients conf
maxclients 5
2)打开5个linux客户端连接到redis,没有问题;
3)打开第6个linux客户端连接到redis,报错如下。
[root@centos211 ~]# redis-cli -h 192.168.163.211 -p 6379
192.168.163.211:6379> set name1 tom1
(error) ERR max number of clients reached
【JedisClientConnectionOverMaxClientsExceptionMain08】 jedis客户端连接到redis集群
public class JedisClientConnectionOverMaxClientsExceptionMain08 {
public static void main(String[] args) {
new Jedis("192.168.163.211", 6379, 3000, 1000);
}
}
报错如下:
Exception in thread "main" redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketException: 你的主机中的软件中止了一个已建立的连接。
at redis.clients.jedis.util.RedisInputStream.ensureFill(RedisInputStream.java:262)
at redis.clients.jedis.util.RedisInputStream.readByte(RedisInputStream.java:55)