redis Jedis SDK使用故障复盘,redis不可用

##线上一次故障导致客户方的应用无法使用

现象描述

业务切换到云redis后,出现业务不可用情况。通过监控看到 千兆带宽流量打满 ,内存占用情况:

查看 info memory 信息,

used_memory_human:198.23M,

used_memory_overhead:10443988934 ,也就说overhead占用超过10g左右,监控信息均正常,开始怀疑 lua,但是

used_memory_lua_human:37.00K


那么分析源码getMemoryOverheadData函数

redis Jedis SDK使用故障复盘,redis不可用

看到 overhead内存占用主要是

初始化之后的内存(这部分很少都是一些redis出事数据结构占用)+backlog(历史增量日志一般也就几Mb)+主从复制连接读写缓存+client 读写缓存+AOF 写缓冲和 rewrite写缓存+ 数据部分的 (hash entry,object )占用。


那么查看 client list 可以看到如图,omem,占用大量内存但是不释放。参数调整到了 256mb 30秒,所以outputbuffer没有及时超过阈值释放。

client-output-buffer-limit normal 513741824 256870912 30

所以这个时候大概定位内存占用高的直接指标信息了。

redis Jedis SDK使用故障复盘,redis不可用

#分析客户方无法使用的原因

我们在分析 client的连接,看到客户方已经迁走业务,到自建redis,但是 redis的tcp队列依然有发送队列挤压数据问题,并且看到 这些client的 omem几乎没有变化,

redis Jedis SDK使用故障复盘,redis不可用


这是为什么?恶意使用?经过和客户方的沟通查看到 客户方的 采用jedis代码调用,但是排查代码发现 采用 java的 timeout 1秒钟作为基础,那么就释放掉jedis的连接。排查jedis代码发现,从jedis连接池中拿出来的 连接 close调用 会直接归还连接池中。



  public void close() {
    //如果使用了连接池,检查客户端状态,归还到池中
    if (dataSource != null) {
      if (client.isBroken()) {
        this.dataSource.returnBrokenResource(this);
      } else {
        this.dataSource.returnResource(this);
      }
    } else {
      //未使用连接池,直接关闭
      client.close();
    }
  } 


完整还原:

经过分析大概是这样,客户端通过jedis发送大量的请求导致带宽打满,由于带宽打满导致 redis连接的缓冲区无法及时发送到 jedis客户端,导致业务代码超时,从而释放jedis连接,从而导致归还连接池后,再次从连接池中拿数据会导致连接中结果读取错误无法解析,从而导致业务逻辑全部都除了问题。


优化方案:jedis 超时判断这需要把jedis 直接client.close(),就不能调用连接池的close方法。还有一个就是 将大批量的带宽占用调整成集群,分摊带宽压力从而减少这类异常的发生。

上一篇:Android中修改键盘布局或者按键映射时的注意点


下一篇:HTTP POST GET 本质区别详解