文章目录
- 【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),数字(整数,浮点数),二进制(图片,音视频),值最大不超过512Mjedis.set("name01", "tom01");System.out.println(jedis.get("name01")); // tom01// 1.1 数字System.out.println(jedis.incr("counter01")); // 4System.out.println(jedis.get("counter01")); // 4System.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")); // tom01System.out.println(jedis.hgetAll("person"));// {name=tom01, addr=chengdu}System.out.println("\n========== list有序列表 ==========");// 3 list有序列表// 列表类型定义: 用来存储多个有序的字符串, 如a,b,c这3个元素从左到右组成了一个有序列表jedis.del("userList"); // 先删除keyjedis.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")); // tom03System.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;}@Overridepublic 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); // 开启jmxpoolConfig.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 poolat 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 poolat 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); // 开启jmxpoolConfig.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 outat 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)