【第四章 客户端】4.5、客户端常见异常

in with 0 comment

客户端常见异常

在客户端的使用过程中,无论是客户端使用不当还是Redis服务端出现问题,客户端 会反应出一些异常。本小节将分析一下Jedis使用过程中常见的异常情况。

  1. 无法从连接池获取到连接

    JedisPool中的Jedis对象个数是有限的,默认是8个。这里假设使用的默认配 置,如果有8个Jedis对象被占用,并且没有归还,此时调用者还要从 JedisPool中借用Jedis,就需要进行等待(例如设置了maxWaitMillis>0), 如果在maxWaitMillis时间内仍然无法获取到Jedis对象就会抛出异常。

    还有一种情况,就是设置了blockWhenExhausted=false,那么调用者发现池 子中没有资源时,会立即抛出异常不进行等待,下面的异常就是 blockWhenWxhausted=false时的效果:

    redis.clients.jedis.exceptions.JedisConnection: Counld not get a resource from the pool
    ...
    Caused by: java.util.NoSuchElementException:Pool exhausted
    at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:464)
    

    对于这个问题,需要重点讨论的是为什么连接池没有资源了,造成没有资源的原 因非常多,可能如下:

    • 客户端:高并发下连接池设置过小,出现供不应求,所以会出现上面的错误, 但是正常情况下只要比默认的最大连接数(8个)多一些即可,因为正常情况下 JedisPool以及Jedis的处理效率足够高。

    • 客户端:没有正确使用连接池,比如没有进行释放,例如下面代码所示。

    定义JedisPool,使用默认的连接池配置:

    GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
    JedisPool jedisPool = nzxew JedisPool(poolConfig, "127.0.0.1", 6379);
    

    像JedisPool借用8次连接,但是没有执行归还操作:

    for(int i = 0; i < 8; i++){
        Jedis jedis = null;
        try{
            jedis = jedisPool.getResource();
            jedis.ping();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    

    当调用者再想连接池借用Jedis是(如下操作),就会抛出异常:

    jedisPool.getResource().ping();
    
    • 客户端:存在慢查询操作,这些慢查询持有的Jedis对象归还速度会比较慢, 造成池子满了。

    • 服务端:客户端是正常的 ,但是Redis服务端由于一些原因造成了客户端命 令执行过程的阻塞,也会使得客户端抛出这种异常。

    可以看到造成这个异常的原因是多个方面的,不要被异常的表象所迷惑,而且并 不存在万能钥匙解决所有问题,开发和运维只能不断加强对于Redis的理解,顺 藤摸瓜逐渐找到问题所在。

  2. 客户端读写超时

    Jedis在调用Redis时,如果出现了读写超时后,会出现下面的异常:

    redis.clients.jedis.exceptions.JedisConnectionException:
    java.net.SocketTimeoutException: Read timed out
    

    造成该异常的原因也有以下几种:

    • 读写超时时间设置过短。
    • 命令本身就比较慢。
    • 客户端与服务端网络不正常。
    • Redis自身发生阻塞。
  3. 客户端连接超时

    Jedis在调用Redis时,如果出现了连接超时后,会出现下面的异常:

    redis.clients.jedis.exceptions.JedisConnectionException:
    java.net.SocketTimeoutException:connect timed out
    

    造成该异常的原因也有以下几种:

    1)连接超时设置得过短,可以通过下面代码进行设置:

    //毫秒
    jedis.getClient().setConnectionTimeout(time);
    

    2)Redis发生阻塞,造成tcp-backlog已满,造成新的连接失败。 3)客户端和服务端网络不正常。

  4. 客户端缓冲区异常

    Jedis在调用Redis时,如果出现客户端数据流异常,会出现线面的异常:

    redis.clients.jedis.exceptions.JedisConnetionException:Unexpected end of stream.
    

    造成这个异常的原因可能有如下几种:

    1)输出缓冲区满。例如将普通客户端的输出缓冲区设置为1M 1M 60:

    config set client-output-buffer-limit "normal 1048576 1048576 60 slave 268435456 67108864 60 pubsub 33554432 8388608 60"
    

    如果使用get命令获取一个bigkey(例如3M),就会出现这个异常。

    2)长时间限制连接被服务端主动断开。

    3)不正常并发读写:Jedis对象同时被多个线程并发操作,可能会出现上述异 常。

  5. Lua脚本正在执行

    如果Redis当前正在执行Lua脚本,并且超过了lua-time-limit,此时Jedis调 用Redis时,会收到下面的异常。

  6. Redis正在加载持久化文件

    Jedis调用Redis是,如果Redis正在加载持久化文件,那么会收到下面的异 常:

    redis.clients.jedis.exceptions.JedisDataException:LOADING Redis is loading the dataset in memory
    
  7. Redis使用的内存超过maxmemory配置

    Jedis执行写操作时,如果Redis的使用内存大于maxmemory的设置,会收到下 面的异常,此时应该调整maxmemory并找到造成内存增大的原因:

    redis.clients.jedis.exceptions.JedisDataException:OOM command not allowed when used memory > 'maxmemory'.
    
  8. 客户端连接数过大

    如果客户端连接数超过了maxclients,新申请的连接就会出现以下异常:

    redis.clients.jedis.exceptions.JedisDataException:ERR max numnber of clients reached
    

    此时新的客户端连接执行任何命令,返回结果都是如下:

    127.0.0.1:6379> get hello
    (error) ERR max number of clients reached
    

    这个问题可能会比较棘手,因为此时无法执行Redis命令进行问题修复,一般来 说可以从两个方面进行着手解决:

    • 客户端:如果maxclients参数不是很小的话,应用方的客户端连接数基本不 会超过maxclients,通常来看是由于应用方对于Redis客户端使用不当造成 的。此时如果应用方是分布式结构的话,可以通过下线部分应用节点(例如占用 连接较多的节点),使得Redis的连接数先降下来。从而让绝大部分节点可以正 常运行,此时再通过查找程序bug或者调整maxclients进行问题的修复。

    • 服务端:如果此时客户端无法处理,而当前Redis为高可用模式(例如Redis Sentinel 和Redis cluster),可以考虑将当前Redis做故障转移。

    此问题不存在确定的解决方式,但是无论从哪个方面进行处理,故障的快速恢复 极为重要,当然更为重要的是找到问题的所在,否则一段时间后客户端连接数依然会超过maxclients。