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

Redis那些常见问题和解决办法,给你最实用的答案分享

内存占用越来越高,快把服务器撑爆了

这是最让人头疼的问题之一,Redis是内存数据库,数据都放在内存里,如果只存不删,内存迟早用完。

来源参考: 根据 Redis 官方文档和众多运维实践,内存增长无外乎几个原因。

解决办法:

  1. 设置过期时间: 这是最基本也是最重要的方法,在存入数据时,就预估好它存活的时间,比如短信验证码(5分钟)、用户登录Token(7天)、热点数据缓存(1小时),使用 SET key value EX seconds 命令,或者在程序代码中设置,让Redis自动清理过期数据。
  2. 检查是否有“大Key”: 所谓“大Key”,是指一个Key对应的Value非常大,比如一个包含几万元素的List、Hash或ZSet,这种Key在过期删除、持久化时都会引起卡顿,并且可能因为无法及时删除而占用大量内存,解决办法是拆分大Key,比如把一个大的Hash拆分成多个小的Hash,通过Key名关联(如 user:info:1, user:info:2)。
  3. 选择合适的数据结构: 比如存储用户信息,用多个String类型的Key(user:1:name, user:1:age)会比用一个Hash(user:1)占用更多内存,因为每个Key都会有额外的元数据开销,应优先使用Hash、List、Set等集合类型。
  4. 配置内存淘汰策略: 当内存达到上限时,Redis的行为由 maxmemory-policy 参数控制,默认是 noeviction(不淘汰,写操作报错),这很危险,通常建议设置为 allkeys-lru,即尝试淘汰最近最少使用的Key,为新数据腾出空间,还有其他策略如 volatile-lru(只淘汰设了过期时间的Key)等,根据业务场景选择。

响应突然变慢,查询卡顿

Redis本应是飞快的,但如果突然变慢,体验会非常差。

来源参考: Redis官方关于延迟(Latency)的排查指南以及社区常见案例。

解决办法:

  1. 首要怀疑:持久化阻塞

    • RDB快照: 当执行bgsave做内存快照时,Redis会fork一个子进程,如果内存数据量大,fork操作本身可能会很耗时,导致主线程短暂阻塞,对于虚拟机环境,这个问题更明显,解决办法是合理设置快照触发条件,避免在业务高峰触发,并确保机器有足够的内存。
    • AOF日志: 如果AOF的写回策略是 always(每个写命令都刷盘),会非常慢,通常建议使用 everysec(每秒刷盘一次),在性能和安全性之间取得平衡,AOF文件过大会触发重写,重写过程也和RDB一样有fork开销。
  2. 使用复杂度过高的命令: 比如一次性获取一个包含几万元素的Set的所有成员(SMEMBERS),或者对一个大Key进行删除操作,这些操作会消耗大量CPU资源,阻塞后续命令,解决办法是避免使用KEYS *这种全量遍历命令(用SCAN替代),对于大集合的查询,考虑使用SSCANHSCAN进行分批遍历。

  3. 内存不足,触发Swap: 操作系统内存不足时,会把一部分内存数据换到硬盘的Swap分区,一旦Redis的数据被换出,下次读取就需要从慢速的硬盘加载,导致延迟飙升。解决办法是绝对禁止使用Swap! 通过系统配置 vm.overcommit_memory=1,并确保Redis有足够的内存。

  4. 网络问题: 检查客户端和Redis服务器之间的网络带宽和延迟,是否有其他应用占用了大量带宽?是否发生了网络丢包?可以使用 ping 命令检查延迟。

缓存数据与数据库不一致

我们用Redis做缓存,是希望它能加速查询,但如果Redis里的数据和后台数据库的数据对不上,就会导致业务逻辑错误。

来源参考: 这是分布式缓存架构中的经典问题,在《数据密集型应用系统设计》等资料中有深入讨论。

解决办法:

  1. 先更新数据库,再删除缓存: 这是最常用的策略,当数据发生变更时,先成功更新数据库,然后使Redis中对应的缓存失效(删除Key),下次查询时,发现缓存不存在,就从数据库加载新值并回填到Redis,这种方式简单有效,虽然在某些极端并发场景下仍可能出现短暂不一致,但概率很低。
  2. 设置合理的缓存过期时间: 即使更新逻辑出了问题,只要给缓存设置了过期时间(比如几分钟到几小时),最终数据也会通过缓存失效、重新从数据库加载的方式达到一致,这是一种兜底方案。
  3. 避免在缓存中执行复杂的更新逻辑: 有些人喜欢在更新数据库后,立即去计算并更新缓存,这会使业务代码变得复杂,而且如果计算缓存需要联合多个表,很容易出错,相比之下,“直接删除”是最简单、最不容易出错的方案。

缓存雪崩、击穿、穿透

这三个概念听起来很像,但问题和解决办法不同。

来源参考: 这些是缓存应用中的典型场景模式,在技术社区和高并发架构文章中被广泛讨论。

解决办法:

  1. 缓存雪崩: 指大量缓存数据在同一时间过期失效,导致所有请求瞬间都打到数据库上,造成数据库压力激增。

    • 解决: 给缓存数据的过期时间加上一个随机值(比如基础过期时间+一个随机分钟数),让Key的失效时间点均匀分布,避免集体失效。
  2. 缓存击穿: 指某一个热点Key(访问量巨大)在过期失效的瞬间,持续的高并发请求穿破缓存,直接访问数据库。

    • 解决: 使用“互斥锁”,当第一个发现缓存失效的请求,先去获取一个分布式锁,然后才去数据库加载数据并回填缓存,在此期间,其他请求等待或返回默认值,加载完成后,释放锁,后续请求就能从缓存中获取数据,也可以对热点Key设置“永不过期”,由后台任务定期更新。
  3. 缓存穿透: 指查询一个根本不存在的数据(比如数据库里没有的ID),缓存中自然也没有,每次请求都会打到数据库上,可能被恶意攻击利用。

    • 解决:
      • 参数校验: 在API层对请求参数做基础校验,比如ID是否为正整数。
      • 缓存空值: 即使从数据库没查到,也把一个空结果(如null)缓存起来,并设置一个较短的过期时间(比如5分钟),这样短时间内同样的请求就不会再访问数据库。
      • 使用布隆过滤器(Bloom Filter): 在访问缓存和数据库之前,先用布隆过滤器判断Key是否存在,如果布隆过滤器说Key不存在,那肯定不存在,直接返回,这是一种空间效率很高的数据结构,专门用于这种判断。

连接数过多,无法创建新连接

Redis能支持的连接数是有限的,默认是10000个,如果客户端使用后没有正确关闭连接,或者遇到高并发场景,可能导致连接数被占满。

解决办法:

  1. 使用连接池: 这是客户端的责任,在应用程序中,务必使用连接池来管理Redis连接,避免每次操作都创建和关闭连接的开销,连接池会维持一个可控的连接数量供程序复用。
  2. 检查连接泄漏: 检查应用程序代码,确保在所有操作路径(包括异常情况)下,连接都被正确返还给连接池。
  3. 调整系统限制: 如果确实需要更多连接,可以调整Redis的 maxclients 配置参数,同时也要调整操作系统的文件描述符限制。

希望这些直接、实用的答案能帮助你更好地应对Redis使用中的常见挑战。

Redis那些常见问题和解决办法,给你最实用的答案分享