[线上问题] "Redis客户端连接数一直降不下来"的问题解决

前段时间,上线了新的 Redis缓存Cache)服务,准备替换掉 Memcached。

为什么要将 Memcached 替换掉?

原因是 业务数据是压缩后的列表型数据,缓存中保存最新的3000条数据。对于新数据追加操作,需要拆解成[get + unzip + append + zip + set]这5步操作。若列表长度在O(1k)级别的,其耗时至少在50ms+。而在并发环境下,这样会存在“数据更新覆盖问题”,因为追加操作不是原子操作。(线上也确实遇到了这个问题)

针对“追加操作不是原子操作”的问题,我们就开始调研有哪些可以解决这个问题同时又满足业务数据类型的分布式缓存解决方案。

当前,业界常用的一些 key-value分布式缓存系统如下:

  • Redis
  • Memcached
  • Cassandra
  • Tokyo Tyrant (Tokyo Cabinet)

参考自:

  • 2010年的技术架构建议 – Tim Yang
  • From distributed caches to in-memory data grids
  • Cassandra vs MongoDB vs CouchDB vs Redis vs Riak vs HBase vs Couchbase vs OrientDB vs Aerospike vs Hypertable vs ElasticSearch vs Accumulo vs VoltDB vs Scalaris comparison

通过对比、筛选分析,我们最终选择了 Redis。原因有以下几个:

  • Redis 是一个 key-value 的缓存(cache)存储(store)系统(现在我们只用它来做缓存,目前还未当作DB用,数据存放在 Cassandra 里)
  • 支持丰富的数据结构List 就专门用于存储列表型数据,默认按操作时间排序。Sorted Set 可以按分数排序元素,分数是一种广义概念,可以是时间评分。其次,其丰富的数据结构为日后扩展提供了很大的方便。
  • 提供的所有操作都是原子操作,为并发天然保驾护航。
  • 超快的性能,见其官方性能测试《How fast is Redis?》。
  • 拥有比较成熟的Java客户端 - Jedis,像新浪微博都是使用它作为客户端。(官方推荐的Clients)

啰嗦了一些其它东西,现在言归正传。

Redis 服务上线当天,就密切关注 Redis 的一些重要监控指标(clients客户端连接数、memory、stats:服务器每秒钟执行的命令数量、commandstats:一些关键命令的执行统计信息、redis.error.log异常日志)。(参考自《Redis监控方案》)

观察到下午5点左右,发现“客户端连接数”一直在增长,最高时都超过了2000个(见下图),即使减少也就减1~2个。但应用的QPS却在 10 个左右,而线上应用服务器不超过10台。按理说,服务器肯定不会有这么高的连接数,肯定哪里使用有问题。

现在只能通过逆向思维反向来推测问题

  • Redis服务端监控到的“客户端连接数”表明所有客户端总和起来应该有那么多,所以首先到各个应用服务器上确认连接数量;
  • 通过“sudo netstat -antp | grep 6379 | wc -l”确认,有一台应用Redis的连接数都超过了1000个,另一台应用则在400左右,其它的都在60上下。(60上下是正常的)
  • 第一个问题:为什么不同的机器部署了同一个应用程序,表现出来的行为却是不一样?
  • 第二个问题:连接数超过1000个的那台,其请求量(140)是比其它机器(200+)要低的(因为它在Nginx中配置的权重低),那它的连接数为什么会这么高?到底发生了什么?
  • 对于“第二个问题”,我们通过各个应用的Redis异常日志(redis.error.log)知道发生了什么。最高那台应用的异常操作特别多,共有130+个异常,且存在“关闭集群链接时异常导致连接泄漏”问题;另一台较高的应用也存在类似的情况,而其它正常的应用则不超过2个异常,且不存在“连接泄漏”问题。这样,“第二个问题”算是弄清楚了。(“连接泄漏”问题具体如何修复见《[FAQ] Jedis使用过程中踩过的那些坑》)
  • 至此,感觉问题好像已经解决了,但其实没有。通过连续几天的观察,发现最高的时候,它的连接数甚至超过了3000+,这太恐怖了。(当时 leader 还和我说,要不要重启一下应用)
  • 即使应用的QPS是 20个/s,且存在“连接泄漏”问题,连接数也不会超过1000+。但现在连接数居然达到了3000+,这说不通,只有一个可能就是未正确使用Jedis
  • 这时候就继续反推,Redis的连接数反映了Jedis对象池的池对象数量。线上部署了2台Redis服务器作为一个集群,说明这台应用共持有(3000/2=1500)个池对象。(因为Jedis基于Apache Commons Pool的GenericObjectPool实现)
  • 第三个问题:根据应用的QPS,每秒钟请求需要的Active池对象也不会超过20个,那其余的1480个都是“空闲池对象”。为什么那么多的“空闲池对象”未被释放?
  • 现在就来反思:Jedis的那些配置属性与对象池管理“空闲池对象”相关,GenericObjectPool背后是怎么管理“空闲池对象”的?

由于在使用Jedis的过程中,就对Apache Commons Pool摸了一次底。对最后的两个疑惑都比较了解,Jedis的以下这些配置与对象池管理“空闲池对象”相关:

redis.max.idle.num=32768
redis.min.idle.num=30
redis.pool.behaviour=FIFO
redis.time.between.eviction.runs.seconds=1
redis.num.tests.per.eviction.run=10
redis.min.evictable.idle.time.minutes=5
redis.max.evictable.idle.time.minutes=1440

在上面说“每台应用的Jedis连接数在60个左右是正常的”的理由是:线上共部署了2台Redis服务器,Jedis的“最小空闲池对象个数”配置为30 (redis.min.idle.num=30)。

GenericObjectPool是通过“驱逐者线程Evictor”管理“空闲池对象”的,详见《Apache Commons Pool之空闲对象的驱逐检测机制》一文。最下方的5个配置都是与“驱逐者线程Evictor”相关的,表示对象池的空闲队列行为为FIFO“先进先出”队列方式,每秒钟(1)检测10个空闲池对象,空闲池对象的空闲时间只有超过5分钟后,才有资格被驱逐检测,若空闲时间超过一天(1440),将被强制驱逐。

因为“驱逐者线程Evictor”会无限制循环地对“池对象空闲队列”进行迭代式地驱逐检测。空闲队列的行为有两种方式:LIFO“后进先出”栈方式、FIFO“先进先出”队列方式,默认使用LIFO。下面通过两幅图来展示这两种方式的实际运作方式:

一、LIFO“后进先出”栈方式

二、FIFO“先进先出”队列方式

从上面这两幅图可以看出,LIFO“后进先出”栈方式 有效地利用了空闲队列里的热点池对象资源,随着流量的下降会使一些池对象长时间未被使用而空闲着,最终它们将被淘汰驱逐
而 FIFO“先进先出”队列方式 虽然使空闲队列里所有池对象都能在一段时间里被使用,看起来它好像分散了资源的请求,但其实这不利于资源的释放(因为空闲池对象的空闲时间只有超过5分钟后,才有资格被驱逐检测,分散资源请求的同时,也导致符合释放条件的空闲对象也变少了,而每个空闲对象都占用一个redis连接)。
这也是“客户端连接数一直降不下来”的根源之一

redis.pool.behaviour=FIFO
redis.time.between.eviction.runs.seconds=1
redis.num.tests.per.eviction.run=10
redis.min.evictable.idle.time.minutes=5

按照上述配置,我们可以计算一下,5分钟里到底有多少个空闲池对象被循环地使用过。
根据应用QPS 10个/s计算,5分钟里大概有10*5*60=3000个空闲池对象被使用过,正好与上面的“连接数尽然达到了3000+”符合,这样就说得通了。至此,整个问题终于水落石出了。(从监控图也可以看出,在21号晚上6点左右修改配置重启服务后,连接数就比较平稳了)

这里还要解释一下为什么使用FIFO“先进先出”队列方式的空闲队列行为?

因为我们在Jedis的基础上开发了“故障节点自动摘除,恢复正常的节点自动添加”的功能,本来想使用FIFO“先进先出”队列方式在节点故障时,对象池能快速更新整个集群信息,没想到弄巧成拙了。

修复后的Jedis配置如下:

redis.max.idle.num=32768
redis.min.idle.num=30
redis.pool.behaviour=LIFO
redis.time.between.eviction.runs.seconds=1
redis.num.tests.per.eviction.run=10
redis.min.evictable.idle.time.minutes=5
redis.max.evictable.idle.time.minutes=30

综上所述,这个问题发生有两方面的原因:

    1. 未正确使用对象池的空闲队列行为LIFO“后进先出”栈方式)
    2. 关闭集群链接时异常导致连接泄漏”问题

http://www.myexception.cn/internet/1849994.html

"Redis客户端连接数一直降不下来"的有关问题解决的更多相关文章

  1. "Redis客户端连接数一直降不下来"的有关问题解决 good

    [线上问题] "Redis客户端连接数一直降不下来"的问题解决 前段时间,上线了新的 Redis缓存(Cache)服务,准备替换掉 Memcached. 为什么要将 Memcach ...

  2. Redis客户端管理

    1.客户端管理 Redis提供了客户端相关API对其状态进行监控和管理,本节将深入介绍各个API的使用方法以及在开发运维中可能遇到的问题. 1.1 客户端API 1.client list clien ...

  3. 全球领先的redis客户端:SFedis

    零.背景 这个客户端起源于我们一个系统的生产问题. 一.问题的发生 在我们的生产环境上发生了两次redis服务端连接数达到上限(我们配置的单节点连接数上限为8000)导致无法创建连接的情况.由于这个系 ...

  4. redis客户端可以连接集群,但JedisCluster连接redis集群一直报Could not get a resource from the pool

    一,问题描述: (如题目)通过jedis连接redis单机成功,使用JedisCluster连接redis集群一直报Could not get a resource from the pool 但是使 ...

  5. Redis客户端——Jedis的使用

    本文介绍基于Java语言的Redis客户端——Jedis的使用,包括Jedis简介.获取Jedis.Jedis直连.Jedis连接池以及二者的对比的选择. Jedis简介 Jedis 是 Redis  ...

  6. Redis02 Redis客户端之Java、连接远程Redis服务器失败

    1 查看支持Java的redis客户端 本博文采用 Jedis 作为redis客户端,采用 commons-pool2 作为连接redis服务器的连接池 2 下载相关依赖与实战 2.1 到 Repos ...

  7. Redis学习笔记--Redis客户端(三)

    1.Redis客户端 1.1 Redis自带的客户端 (1)启动 启动客户端命令:[root@kwredis bin]# ./redis-cli -h 127.0.0.1 -p 6379 -h:指定访 ...

  8. spring 5.x 系列第8篇 —— 整合Redis客户端 Jedis和Redisson (代码配置方式)

    文章目录 一.说明 1.1 Redis 客户端说明 1.2 Redis可视化软件 1.3 项目结构说明 1.3 依赖说明 二.spring 整合 jedis 2.1 新建基本配置文件和其映射类 2.2 ...

  9. spring 5.x 系列第7篇 —— 整合Redis客户端 Jedis和Redisson (xml配置方式)

    文章目录 一.说明 1.1 Redis 客户端说明 1.2 Redis可视化软件 1.3 项目结构说明 1.3 依赖说明 二.spring 整合 jedis 2.1 新建基本配置文件 2.2 单机配置 ...

随机推荐

  1. 查看pid

    可以使用ps -ef | grep httpd查看PID 然后kill –l PID

  2. iOS 手机淘宝加入购物车动画分析

    1.最终效果 仿淘宝动画 2.核心代码 _cartAnimView=[[UIImageView alloc] initWithFrame:CGRectMake(_propView.frame.size ...

  3. PL/SQL Select into 异常处理

    在使用select into 为变量赋值时,如果变量是集合类型,不会产生异常,而如果是基本类型或记录类型,则会报异常. 异常产生了怎么办?当然是捕获并处理啦. 对于普通的代码块来说,在代码块的结尾处理 ...

  4. SQL:42601

    以前遇到SQL中触发器的问题, 添加一个字段后,发现以前的程序的SQL报错 看了下表定义,有这样的一行代码 CREATE TRIGGER chintai_keiyaku_audit AFTER INS ...

  5. apk文件解析,学习笔记

    Android 应用程序包文件 (APK) 是一种Android操作系统上的应用程序安装文件格式,其英文全称为 “application package file” . 如果懂得使用反编译工具,可以下 ...

  6. 学习java随笔第一篇:搭建java平台(java se)

    电脑系统:windows8 在这里介绍一下java平台的3个版本: Java SE--Java Standard Edition,Java的标准版,主要用于桌面级的应用和数据库开发. Java EE- ...

  7. 在线预览文件(pdf)

    1.flash版(借助flexpaper工具) 可以把pdf文件用pdf2swf工具转换成swf文件.下载地址http://www.swftools.org/download.html 转换代码如下: ...

  8. ITEXTSHARP学习整理

    学习的版本iTextSharp.5.5.5. 关于获取PDF中的图片资源 /// <summary> /// 将PDF中的图片资源转换成二进制 /// </summary> / ...

  9. javascript基础学习(十)

    javascript之数组 学习要点: 数组的介绍 定义数组 数组元素 数组的方法 一.数组的介绍 数组中的元素类型可以是数字型.字符串型.布尔型等,甚至也可以是一个数组. 二.定义数组 1.通过数组 ...

  10. 【BZOJ2049】【LCT】Cave 洞穴勘测

    Description 辉 辉热衷于洞穴勘测.某天,他按照地图来到了一片被标记为JSZX的洞穴群地区.经过初步勘测,辉辉发现这片区域由n个洞穴(分别编号为1到n)以及若干通 道组成,并且每条通道连接了 ...