当前位置: 首页 > article >正文

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)



http://www.kler.cn/a/457456.html

相关文章:

  • VBA 64位API声明语句第005讲
  • cka考试-02-节点维护
  • 【模型】Qwen2-VL 服务端UI
  • Java 性能调优实战
  • 杰盛微 JSM4056 1000mA单节锂电池充电器芯片 ESOP8封装
  • kubernetes学习-kubectl命令、探针(二)
  • python学习笔记——类和对象
  • 道路倒角 三角网 两侧偏移
  • 2025最新植物大战僵尸杂交版3.0.2版本
  • 埃斯顿机器人程序案例多个点位使用变量
  • 微服务架构与 Spring Cloud 相关技术解读
  • 程序猿成长之路之设计模式篇——结构型设计模式
  • C 中如何在For中生成不重复的随机数
  • 【Chrome】浏览器提示警告Chrome is moving towards a new experience
  • 邮件传输协议
  • Linux系统编程(一)--基础指令
  • 【Spring】基于注解的Spring容器配置——基于Java类的配置(@Configuration与@Bean)
  • 【MySQL篇】使用Java操作MySQL实现数据交互
  • 基于python的ai五子棋游戏
  • .net core 的算法与数据结构
  • 【华为OD-E卷-取出尽量少的球 100分(python、java、c++、js、c)】
  • 【Pandas】pandas Series to_numpy
  • 路由器刷机TP-Link tp-link-WDR5660 路由器升级宽带速度
  • TCP/IP网络协议攻击
  • PyTorch快速入门教程【小土堆】之TensorBoard的使用
  • 深度学习的魔法世界