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)

相关推荐
福公主的头号粉丝2 分钟前
Python进阶-10-Python根类&枚举类
开发语言·python
pursue.dreams19 分钟前
内网Ubuntu搭建minio
linux·运维·ubuntu
shykevin20 分钟前
Django Swagger文档库drf-spectacular
数据库·后端·python·django·sqlite
liuccn27 分钟前
ubuntu 22下解决Unment dependencies问题
运维·服务器·ubuntu
软件开发技术深度爱好者29 分钟前
python +tkinter绘制彩虹和云朵
开发语言·python
Code成立1 小时前
《Java核心技术II》简单约简
java·开发语言·python
辣椒酱.1 小时前
neo4j学习笔记
python·neo4j
tan6257472 小时前
github
运维
lennon_jlu2 小时前
1.4 java反射机制 简单的java反射机制实践
java·开发语言·python
鹧鸪云光伏与储能软件开发2 小时前
鹧鸪云运维软件焕新升级
运维·光伏运维