[线上问题] "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. app 的内存优化

    这篇文章是笔者在开发App过程中发现的一些内存问题, 然后学习了YYKit框架时候也发现了图片的缓存处理 (YYKit 作者联系了我, 说明了YYKit重写imageNamed:的目的不是为了内存管理 ...

  2. php代码优化技巧

    搬运: 1. 尽量采用大量的PHP内置函数. 2. echo 比print 快. 3. 不要把方法细分得过多,仔细想想你真正打算重用的是哪些代码? 4. 在执行for循环之前确定最大循环数,不要每循环 ...

  3. 第五篇:python基础之循环结构以及列表

    python基础之循环结构以及列表   python基础之编译器选择,循环结构,列表 本节内容 python IDE的选择 字符串的格式化输出 数据类型 循环结构 列表 简单购物车的编写 1.pyth ...

  4. OSI 网络七层模型(笔记)

    一直以来我们都在使用着互联网,每天聊着qq,上着淘宝,但是却不了解怎么运行的呢,充满了好奇.今天同过了解来总结一下OSI网络七层模型: 上一张图 OSI (open system interconne ...

  5. MEF依赖注入调试小技巧!

    自从哥的项目使用MEF以来,天天那个纠结啊,甭提了.稍有错误,MEF就报错,但就不告诉你哪错了,大爷的. 后来看了MEFX的相关调试方法,感觉也不太理想,根本不够直观的看到错误原因,也许是没有深入学习 ...

  6. 初识 Angular 体会

    一句话描述:一个前端的类似MVC框架的JS库 刚接触2天,刚一看感觉和asp.net mvc能实现的功能有点重复. 虽然asp.net的表单验证,Razor语法使其在前端开发有较大提升,但要实现比较高 ...

  7. "库未注册"(Library not registered)异常.

    启发链接:http://social.msdn.microsoft.com/Forums/vstudio/en-US/f25b80bc-ecd4-4c37-8be3-9106a765b072/libr ...

  8. Oracle利用dbms_metadata.get_ddl查看DDL语句

    当我们想要查看某个表或者是表空间的DDL的时候,可以利用dbms_metadata.get_ddl这个包来查看. dbms_metadata包中的get_ddl函数详细参数 GET_DDL函数返回创建 ...

  9. MySQL增删改查的常用操作指令总结

    总结: 1.数据库操作: 创建库: create database db_name; 查询库: show databases; //显示所有的数据库 show create databases db_ ...

  10. hdoj 1686 kmp

    题目:   Sample Input 3 BAPC BAPC AZA AZAZAZA VERDI AVERDXIVYERDIAN   Sample Output 1 3 0     代码:   #in ...