redis开发与运维-redis04-redis客户端Jedis与连接池及客户端异常模拟

文章目录

【README】

本文总结自《redis开发与运维》,作者付磊,张益军,墙裂推荐;


【1】redis客户端通信协议

1)redis客户端介绍:

  • redis客户端与服务器之间的通信协议是在tcp协议上建立的;
  • redis制定了 RESP(REdis Serialization Protocol, redis序列化协议)实现客户端与服务器的交互,该协议简单高效,能够被机器解析,也对开发者友好;

2)RESP协议的报文演示:

shell 复制代码
[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依赖

XML 复制代码
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
    <dependency>
      <groupId>redis.clients</groupId>
      <artifactId>jedis</artifactId>
      <version>5.2.0</version>
    </dependency>

2)新建jedis简单客户端操作redis

java 复制代码
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);
        }
    }
}

【运行结果】

c++ 复制代码
tom1
耗时(秒)=0

【2.2】Jedis操作5种数据类型代码实践

1)Jedis操作5种数据类型代码实践:

java 复制代码
/**
 * @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依赖如下:

java 复制代码
<!-- 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】

java 复制代码
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();
            }
        }
    }
}

【日志】

c++ 复制代码
Car{id=2, name='tesla', birthAddr='shanghai'}

【ProtostuffSerializationUtils】Protobuf序列化工具

java 复制代码
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

java 复制代码
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测试入口类

java 复制代码
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();
            }
        }
    }
}

【日志】

c++ 复制代码
Jedis{Connection{DefaultJedisSocketFactory{192.168.163.211:6379}}}
zhagnsan01
zhagnsan02

【PooledJedisFactory】池化redis工厂

java 复制代码
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方法源码

java 复制代码
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测试案例

java 复制代码
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();
    }
}

【打印日志】

c++ 复制代码
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】

java 复制代码
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】

java 复制代码
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);
        }
    }
}

【日志】

c++ 复制代码
耗时(毫秒)=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服务器)

java 复制代码
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);
        }
    }
}

【日志】

c++ 复制代码
耗时(秒)=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服务器启动配置文件

shell 复制代码
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 ,则表示不限制;

shell 复制代码
client-output-buffer-limit normal 0 0 0

2)从redis读取包含300w个元素的有序列表,报输出缓冲区异常

【JedisClientBufferExceptionMain04】 客户端缓冲区异常测试案例

java 复制代码
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 表示客户端数据流异常

c++ 复制代码
耗时(毫秒)=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的设置,会报如下异常;

shell 复制代码
OOM command not allowed when used memory 'maxmemory'

【4.7.1】模拟redis使用的内存超过maxmemory设置

1)设置内存大小为10kb

【redis-6379.conf 】

shell 复制代码
# max memory conf
maxmemory 10kb

2)向redis写入26w个字符的value

【JedisClientOverMaxMemoryExceptionMain07】 redis使用的内存超过maxmemory设置测试案例

java 复制代码
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);
        }
    }
}

【报错日志】

c++ 复制代码
耗时(毫秒)=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, 新申请的连接报如下异常。

shell 复制代码
[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)设置最大连接数

shell 复制代码
# max clients conf
maxclients 5

2)打开5个linux客户端连接到redis,没有问题;

3)打开第6个linux客户端连接到redis,报错如下。

shell 复制代码
[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集群

java 复制代码
public class JedisClientConnectionOverMaxClientsExceptionMain08 {

    public static void main(String[] args) {
        new Jedis("192.168.163.211", 6379, 3000, 1000);
    }
}

报错如下:

c++ 复制代码
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)

相关推荐
程序员三藏11 分钟前
Selenium无法定位元素的几种解决方案
自动化测试·软件测试·python·selenium·测试工具·职场和发展·测试用例
前端小趴菜~时倾12 分钟前
自我提升-python爬虫学习:day04
爬虫·python·学习
小罗和阿泽13 分钟前
接口测试系列 接口自动化测试 pytest框架(三)
开发语言·python·pytest
maosheng11468 小时前
RHCSA的第一次作业
linux·运维·服务器
猿界零零七8 小时前
pip install mxnet 报错解决方案
python·pip·mxnet
旺仔.2918 小时前
Linux 信号详解
linux·运维·网络
Hoshino.419 小时前
基于Linux中的数据库操作——下载与安装(1)
linux·运维·数据库
不只会拍照的程序猿10 小时前
《嵌入式AI筑基笔记02:Python数据类型01,从C的“硬核”到Python的“包容”》
人工智能·笔记·python
恒创科技HK10 小时前
通用型云服务器与计算型云服务器:您真正需要哪些配置?
运维·服务器
Jay_Franklin10 小时前
Quarto与Python集成使用
开发语言·python·markdown