当前位置:首页 > 问答 > 正文

Redis现在读写数据出问题了,数据操作变得很困难,真不知道咋解决

Redis现在读写数据出问题了,数据操作变得很困难,真不知道咋解决,这事儿真是让人头疼,我最近就碰到了,感觉像是服务器突然变得特别迟钝,平时毫秒级就能返回的操作,现在要等上好几秒,甚至有时候还会直接报错,提示超时或者连接失败,我一开始以为是网络波动,但检查了网络配置和监控,发现一切正常,然后我又怀疑是代码逻辑有变动,但回滚到之前的稳定版本后,问题依旧存在,这种无从下手的感觉真的很糟糕。

Redis现在读写数据出问题了,数据操作变得很困难,真不知道咋解决

后来,我静下心来,开始一步步排查,首先想到的是最基础的东西:服务器资源,我登录到运行Redis的服务器上,用top命令看了一下,发现CPU使用率并不高,但是内存使用情况有点奇怪,系统总内存是16G,Redis进程显示占用了大概14G,这看起来似乎没问题,因为我的maxmemory配置就是14G,但问题在于,可用内存(available memory)显示得非常低,几乎快用尽了,而且交换分区(swap)的使用量在持续增加,这让我意识到,可能是操作系统本身的内存压力很大,导致开始使用硬盘上的swap空间,而一旦发生内存交换,磁盘I/O就会成为瓶颈,因为硬盘的读写速度远远比不上内存,这就会导致所有操作的延迟急剧上升,这大概就是所谓的“交换风暴”(swap storm)吧,虽然我不太喜欢用术语,但这个词确实很形象地描述了当时的情况:系统在内存和硬盘之间疲于奔命地倒腾数据,Redis的性能自然就一落千丈。(来源:基于Linux系统内存管理机制的普遍认知)

Redis现在读写数据出问题了,数据操作变得很困难,真不知道咋解决

找到了这个方向,我就开始查为什么系统内存会这么紧张,除了Redis,服务器上还运行着几个其他的应用,我用ps aux命令按内存使用排序,发现有一个后台数据处理服务,不知道从什么时候开始出现了内存泄漏,它占用的内存一直在缓慢增长,最终挤占了原本属于操作系统的空闲内存,操作系统为了保持运行,只好把一些“不常用”的内存页,其中可能就包括Redis的部分数据,挪到了swap区,当Redis需要读写这些被换出的数据时,就不得不等待缓慢的磁盘操作,解决方法相对直接:我先是重启了那个有问题的后台服务,暂时缓解了内存压力,更重要的是,我调整了Redis的maxmemory-policy配置,之前我设置的是noeviction(不淘汰),意思是当内存用满时,新写入的操作会报错,但不会删除旧数据,这在内存充足时是好的,可以防止数据丢失,但在内存紧张的环境下风险很高,我把它改成了allkeys-lru,这样当内存接近上限时,Redis会自动淘汰掉最近最少使用的键,为新数据腾出空间,这个策略虽然可能导致部分缓存失效,但能保证服务的整体可用性,避免整个Redis因为内存问题而彻底卡死。(来源:Redis官方文档关于内存管理的章节)

Redis现在读写数据出问题了,数据操作变得很困难,真不知道咋解决

内存问题解决后,延迟确实降低了不少,但偶尔还是会出现短暂的尖峰延迟,这让我觉得问题可能不止一个,接下来我把目光投向了Redis的持久化机制,Redis为了数据安全,会把数据定期保存到磁盘上,主要有两种方式:RDB(快照)和AOF(追加日志),我检查了一下配置,发现我同时开启了RDB和AOF,RDB是每隔一小时保存一次,而AOF是每秒同步一次(appendfsync everysec),听起来AOF的每秒同步已经很快了,但在磁盘写入压力大的时候,仍然可能引起阻塞,我查看了Redis的日志文件,果然发现了一些提示信息,Background saving started”和“Background saving terminated”,这是RDB持久化过程的记录,在监控工具里,我也看到了在RDB生成期间,Redis的响应时间有明显的上升。

这是因为,在执行RDB快照时(尤其是数据量大的时候),Redis会fork一个子进程来负责将数据写入硬盘,fork操作本身在数据量大时可能会耗时,而且如果服务器是虚拟化环境,开销可能更大,子进程在写入数据时,虽然不会阻塞主进程处理请求,但会占用大量的磁盘I/O带宽,如果此时AOF日志也需要同步写入磁盘,那么磁盘I/O就可能成为瓶颈,导致主进程在写入AOF缓冲区或执行其他I/O操作时变慢,为了解决这个问题,我考虑了几个方案,一是将RDB的快照频率降低,比如从一小时一次改为六小时一次,毕竟我的业务对数据恢复的实时性要求不是极端高,二是评估是否可以关闭AOF,只使用RDB,但这样会有丢失最后一次快照之后数据的风险,权衡之后,我决定保持AOF开启,但将AOF的重写(auto-aof-rewrite-percentage和auto-aof-rewrite-min-size)条件调整得更苛刻一些,减少AOF重写(这是一个类似RDB的耗时操作)发生的频率,我考虑给Redis配备一块性能更好的SSD硬盘,专门用于持久化操作,这样可以显著减少I/O等待时间。(来源:Redis官方文档关于持久化的章节以及多位技术博客作者对RDB和AOF性能影响的讨论)

除了服务器资源和持久化,连接数也是一个常见的坑,有一天,我突然收到大量连接失败的报警,用redis-cli连上去执行info clients命令,发现连接数已经接近了我设置的maxclients上限(默认是10000),我进一步用client list命令查看,发现有很多连接处于空闲(idle)状态很长时间,但一直没有断开,这明显是客户端的问题,可能是连接池配置不当,创建了连接但没有正确释放,导致了连接泄漏,Redis需要为每个连接维护一些内核资源和内存结构,连接数过多本身就会消耗资源,更重要的是,当Redis需要遍历客户端列表进行处理时(比如执行CLIENT KILL命令或者定时检查超时),大量的连接会加重CPU的负担,我联系了相关应用的开发团队,让他们检查并修复了客户端连接池的代码,确保空闲连接能够及时关闭,我也在Redis配置中适当调低了timeout参数的值,让Redis服务端能够主动关闭那些长时间空闲的连接。(来源:Redis官方文档关于客户端管理的说明)

经过这一系列的排查和调整——解决操作系统内存压力、优化Redis内存淘汰策略、调整持久化配置和频率、修复客户端连接泄漏——Redis的性能终于稳定了下来,读写操作恢复了往常的流畅,这个过程让我深刻体会到,Redis的问题往往不是孤立的,它和操作系统资源、硬件性能、客户端行为以及自身的配置都紧密相关,当遇到问题时,不能盲目地去调参数,最好的办法就是从最简单的系统监控开始,比如CPU、内存、磁盘I/O和网络,一步步缩小范围,结合Redis自身的日志和info命令输出的丰富信息,像侦探破案一样,最终找到那个真正的“罪魁祸首”,虽然过程很折腾,但解决问题的成就感也是实实在在的。