背景

事情的起因来源于生产环境的redis告警,主要是连接池耗尽(Could not get a resource since the pool is exhausted)和request count reach max Redirections(No more cluster attempts left.) 的告警,本文就是记录下排除的过程以及原因

查找真相

第一直觉是是不是短时间内有大量的redis请求导致的连接耗尽?其实这个想法一开始就不应该出现,因为如果有大量的redis请求最多只会导致连接池耗尽而不会导致(No more cluster attempts left.)的出现,因为No more cluster attempts left.出现意味着命令多次重试也不会成功。

所以我又转向了另一个怀疑的方向:是网络抖动吗?网络抖动确实会引起No more cluster attempts left.异常,但是他不至于会导致连接池耗尽的告警:Could not get a resource since the pool is exhausted,他的告警应该是无法创建网络连接,故网络的问题再一次被排除在外.

无奈,只能查看了redis服务器的满查询命令,突然发现大量的zrangByScore慢查询出现在眼前,恍然大悟,那么redis的慢查询命令为什么可以导致连接池耗尽和redis的命令的No more cluster attempts left.告警?

首先,慢查询命令会导致执行该命令的连接没法释放(假设这里占据了一条连接),但是还不仅仅如此,其他快查询的命令如果也是落到慢查询所在的redis实例上面的话,会导致本来是这个快查询的命令等待慢查询命令的执行,这样快查询的命令自然因为排队的原因也就变成了慢查询了。这样这些执行快查询命令的连接也没法释放(这里可能会有大量的连接hold住),从而导致连接池耗尽。

其次,慢查询命令导致命令执行时出现No more cluster attempts left.告警,这对于本来就是慢查询命令的redis命令来说自然如此,最重要的是他会引起雪崩效应,也就会会引起本来是快查询的命令由于等待慢命令的执行而变慢,原因是因为redis服务器是单线程的,同一时间只能执行一条命令,其他的命令再快也需要等待慢命令的执行,自然这些本来是快查询的命令变成慢命令后就会出现No more cluster attempts left.的告警.

这里注意一下,redis命令执行超时也会导致重试,一直重试到最大重试次数耗尽,参考代码:

socket网络超时是JedisConnectionException异常:

      } catch (JedisConnectionException jce) {
        lastException = jce;
        ++consecutiveConnectionFailures;
        LOG.debug("Failed connecting to Redis: {}", connection, jce);
        // "- 1" because we just did one, but the attemptsLeft counter hasn't been decremented yet
        boolean reset = handleConnectionProblem(attemptsLeft - 1, consecutiveConnectionFailures, deadline);
        if (reset) {
          consecutiveConnectionFailures = 0;
          redirect = null;
        }
  private static void sendCommand(final RedisOutputStream os, final byte[] command,
      final byte[]... args) {
    try {
      os.write(ASTERISK_BYTE);
      os.writeIntCrLf(args.length + 1);
      os.write(DOLLAR_BYTE);
      os.writeIntCrLf(command.length);
      os.write(command);
      os.writeCrLf();

      for (final byte[] arg : args) {
        os.write(DOLLAR_BYTE);
        os.writeIntCrLf(arg.length);
        os.write(arg);
        os.writeCrLf();
      }
    } catch (IOException e) {
      throw new JedisConnectionException(e);
    }
  }