[线上问题] "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. Java 调用Dll

    Java 中怎么能调用到dll中的函数呢? 关键是java中生的本地函数名參数和dll中的本地函数名參数一模一样. 这个程序是java中调用dll中的求和函数. 一,java代码部分操作 1.新建pr ...

  2. Android Studio 2.2 External Build

    今天在用studio写Native程序时发现2.2版本引入了一个 External Build来进行Native项目的构建. 最直观的表现就是c/c++的源码文件不用跟java文件在一个项目文件夹下了 ...

  3. 10.5 noip模拟试题

    2bc*cosA=b^2+c^2-a^2 数学题QAQ 开始π精度不够40分 怪我喽~ #include<iostream> #include<cstdio> #include ...

  4. 【转】Angularjs Controller 间通信机制

    在Angularjs开发一些经验总结随笔中提到我们需要按照业务却分angular controller,避免过大无所不能的上帝controller,我们把controller分离开了,但是有时候我们需 ...

  5. Android 使用弹出对话框,报Unable to add window错误

    今天在使用Android弹出对话框的时候,报了一个unable to add window错误,我的代码如下 new AlertDialog.Builder(getApplicationContext ...

  6. 开源的Android开发框架-------PowerFramework使用心得(四)数据库管理DBFarmer

    DBFarmer是PowerFramework数据库管理工具的集合. 可以进行对象的存储,添加了setter和getter的参数会被收录到数据库中,每个参数作为一个项,int类型的id或_id会被作为 ...

  7. 如何配置visual studio 2013进行负载测试-万事开头难

    声明:工作比较忙,文章写得不好,有时间再整理. 起因:最近众包平台因迁移到azure之后一直有网站慢的情况,让老板挨批了,但是测试环境一切正常,而且生产环境也没发现有卡顿和慢的情况,所以干脆来一次负载 ...

  8. Spring通过SchedulerFactoryBean实现调度任务的配置

    http://blog.csdn.net/hu_shengyang/article/details/19815201(里面是配置) 介绍SchedulerFactoryBean http://blog ...

  9. oracle触发器调试

    1.如下图位置点击触发器,会出现调试窗口 2.执行编译并调试 3.点击小虫,将画红位置,加入会触发此触发器的语句.如果触发器执行成功,不会出现第4个图,不成功,会出现数据调试信息,具体报错位置会定位到 ...

  10. java Web Services搭建环境时遇到的各种问题,记录一下。 java.lang.OutOfMemoryError: PermGen space,org/apache/struts2/util/ObjectFactoryDestroyable

    情况:在同一个,myEclipes 下加载俩个项目,一个seriver端,一个client端. 必备: myEclipes    ,apache-tomcat-7.0.42,apache-tomcat ...