前段时间,上线了新的 Redis缓存(Cache)服务,准备替换掉 Memcached。
为什么要将 Memcached 替换掉?
原因是 业务数据是压缩后的列表型数据,缓存中保存最新的3000条数据。对于新数据追加操作,需要拆解成[get + unzip + append + zip + set]这5步操作。若列表长度在O(1k)级别的,其耗时至少在50ms+。而在并发环境下,这样会存在“数据更新覆盖问题”,因为追加操作不是原子操作。(线上也确实遇到了这个问题)
针对“追加操作不是原子操作”的问题,我们就开始调研有哪些可以解决这个问题同时又满足业务数据类型的分布式缓存解决方案。
当前,业界常用的一些 key-value分布式缓存系统如下:
参考自:
- 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“先进先出”队列方式 虽然使空闲队列里所有池对象都能在一段时间里被使用,看起来它好像分散了资源的请求,但其实这不利于资源的释放。而这也是“客户端连接数一直降不下来”的根源之一。
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
综上所述,这个问题发生有两方面的原因:
- 未正确使用对象池的空闲队列行为(LIFO“后进先出”栈方式)
- “关闭集群链接时异常导致连接泄漏”问题
相关推荐
"Redis客户端连接、最大连接数查询与设置" Redis是当前最流行的NoSQL数据库之一,广泛应用于各种web应用程序和游戏开发中。其中,客户端连接是Redis的核心组件之一,负责处理来自客户端的请求。然而,随着系统的...
Redis是世界上最受欢迎的内存数据存储系统之一,常用于构建高性能、低延迟的数据缓存和数据库。...在实际工作中,结合Redis的各种特性和RedisDesktopManager的功能,可以有效地利用Redis解决各种数据存储和处理问题。
在实际应用中,为了方便管理和操作Redis服务器,我们通常会使用专门的Redis客户端连接工具。这里,我们重点讨论标题中提到的"redis客户端连接工具",特别是压缩包内的`redis-desktop-manager-0.8.3.3850.exe`。 ...
日常使用linux版连接redis客户端Another-Redis-Desktop-Manager.1.3.9
分布式锁是 Redis 在分布式系统中的重要应用场景,它可以解决多节点共享资源的问题,防止并发操作导致的数据不一致。Redis 提供了 SETNX (Set if Not eXists) 和 EXPIRE 命令组合实现简单版本的分布式锁。在 Java ...
在本场景中,"redis客户端,连接查看redis数据库"指的是使用特定的客户端工具来连接到Redis服务器,以便管理和操作存储在Redis中的数据。这里我们主要讨论如何连接Redis数据库以及常用的Redis客户端。 1. **Redis...
同时,通过监控工具观察Redis的性能指标,如内存使用、命令执行速率等,有助于及时发现和解决问题。 总之,"windows redis客户端连接工具, Redis-x64-3.2.100, 亲测可用"提供了一种在Windows环境中与Redis交互的...
redis客户端的连接工具,mac版本,版本号0.9.3,通过该软件可以再mac上查询redis数据库中的相关数据,比起命令行方便很对
RedisClient是Redis客户端的GUI工具,使用Java swt和jedis编写,可以方便开发者浏览Redis数据库。该软件支持简体中文,非常适合国内用户使用,不需要汉化就可以直接使用。RedisClient将redis数据以资源管理器的界面...
2. **创建连接**:使用cpp-redis,你可以创建一个`redis_client`对象来建立与Redis服务器的连接。例如: ```cpp cpp_redis::redis_client client; client.connect("127.0.0.1", 6379); ``` 这里`127.0.0.1`是...
Redis是一款开源、高性能的键值对存储系统,常被用作数据库、缓存和消息中间件。...不过,虽然这类工具简化了工作流程,但理解Redis本身的基础概念和命令仍然是非常重要的,这有助于更好地利用其特性并解决潜在问题。
7. **性能监控**:高级版本的RedisClient可能提供服务器状态监视,包括内存使用、命令执行速率、连接数等关键指标,帮助优化Redis服务的性能。 8. **导入导出数据**:对于数据迁移或者备份,RedisClient可能提供数据...
6. **监控与性能分析**:高级的Redis客户端工具可能包含监控功能,显示服务器状态、内存使用情况、命令执行速率等,帮助用户了解系统的运行状况。 提到的"DesktopManag"标签可能指的是该工具是桌面应用程序,即它...
Redis 是一个高性能的键值数据库,它以键值对的形式存储数据,广泛应用于缓存、消息中间件、实时分析等领域。在 Windows 环境下,通常需要通过安装过程来设置 Redis 服务,但这里提供的资源是“redis 免安装”,意味...
- **图形界面客户端**:启动`Redis Desktop Manager`,配置连接详情(IP、端口、密码等),然后可以直观地查看键值对,执行CRUD操作,以及监控服务器状态。 3. **基本操作**: - **设置键值**:使用`SET key ...
标题提到的是一个免费的Redis客户端.zip文件,这意味着它提供了一个无需付费且无功能限制的解决方案,解决了开发者寻找开源Redis客户端的困扰。 Redis客户端是用于连接和管理Redis服务器的应用程序,它们通常提供了...
Redis 是一个开源的、基于键值对的数据存储系统,常用于数据库、缓存和消息中间件等场景。在 macOS 平台上,为了方便管理和操作 Redis ...对于经常需要处理 Redis 数据的用户而言,这样的客户端工具是不可或缺的。