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

redis那些坑和难题,从零摸索到全搞定的答案合集

主要参考了网络上多位开发者的实践经验分享,尤其是知乎、CSDN等技术社区的热门讨论帖,以及《Redis开发与运维》等书籍中的常见问题总结)

刚开始用Redis的时候,觉得它简直太快太方便了,就是个超级快的键值对柜子,往里扔数据、取数据,速度飞起,但用着用着,尤其是在用户量和数据量上来之后,各种意想不到的问题就冒出来了,踩了不少坑,也解决了不少难题,下面就是我一路摸索过来的答案合集。

第一个大坑:内存不够用了怎么办?

Redis是把所有数据都放在内存里的,所以内存大小是硬限制,一开始可能觉得几个G够用了,但业务跑起来,数据增长很快,突然就报错了,说“OOM command not allowed when used memory > ‘maxmemory’”,这时候才想起来没好好配置内存淘汰策略。

答案: 不能等到内存满了才着急,首先要做的就是预估数据量,给Redis分配合理的内存,并务必设置maxmemory-policy这个参数,这个参数就是告诉Redis,当内存快满的时候,该怎么“丢”数据来腾地方,常用的策略有:

  • allkeys-lru:尝试淘汰最近最少使用的键,不管这个键有没有设置过期时间,这是最常用的策略。
  • volatile-lru:只从设置了过期时间的键中淘汰最近最少使用的。
  • allkeys-random:随机淘汰所有键。
  • noeviction:不淘汰,当内存不够时,新写入的操作会报错,这是默认策略,但生产环境一般不用,不然一满就不可写了。 选择哪种策略要看业务场景,比如缓存数据可以用allkeys-lru,而一些重要的、不能丢的数据可能得放在持久化存储里,或者确保它们有较长的过期时间并配合volatile-lru

第二个难题:数据突然全没了?

Redis有持久化功能,可以把内存数据存到硬盘上,防止重启后数据丢失,但默认情况下,RDB持久化(快照)是打开的,AOF持久化(记录每一条写命令)是关闭的,如果服务器突然宕机,你可能会丢失从上次快照到宕机时刻之间的所有数据。

答案: 根据数据的重要性级别,配置合适的持久化方案。

  • 如果可以容忍几分钟的数据丢失:用RDB就够了,可以调整save配置,比如save 60 10000,表示60秒内至少有10000个键被改动就触发快照,RDB恢复大数据集速度很快。
  • 如果一点数据都不能丢:必须开启AOF,设置appendonly yes,为了平衡性能和数据安全,可以设置appendfsync everysec,每秒同步一次,这样最多丢一秒的数据,极端情况下可以设appendfsync always,但会严重影响性能。
  • 最保险的做法:RDB和AOF同时开启,用RDB做冷备,用AOF保证数据完整性,重启时会优先加载AOF文件来恢复,因为AOF通常数据更完整。

第三个经典的坑:缓存雪崩、击穿、穿透。

这三个词听起来很像,但问题不一样。

  • 缓存雪崩:指大量的缓存数据在同一时间过期失效,导致所有请求瞬间都打到了数据库上,数据库压力激增甚至崩溃。
    • 答案:给缓存数据的过期时间加上一个随机值,比如基础过期时间是1小时,然后加上一个0到5分钟的随机数,这样就能避免大量数据同时过期。
  • 缓存击穿:指一个非常热点的key(比如明星爆款商品)在失效的瞬间,持续的大并发请求穿破缓存,直接访问数据库。
    • 答案:使用互斥锁,当第一个请求发现缓存失效时,它先去获取一个锁(比如用Redis的SETNX命令),然后才去查询数据库并重建缓存,其他请求在此期间要么等待,要么直接返回默认值,这样只有一个请求会去查数据库。
  • 缓存穿透:指查询一个根本不存在的数据(比如数据库里也没有),缓存中查不到,每次都会去查数据库,相当于绕过了缓存。
    • 答案:1. 对请求参数做校验,比如不合法的ID直接拦截返回,2. 即使从数据库没查到,也把一个空值(比如null)或者特殊标记写进缓存,并设置一个较短的过期时间(比如1-5分钟),这样后续请求在缓存层就返回了,不会访问数据库,更复杂一点的可以用布隆过滤器来提前判断数据是否存在。

第四个难题:主从复制延迟导致数据不一致。

用了Redis主从架构做读写分离后,写操作在主库,读操作在从库,但主库的数据同步到从库有毫秒级的延迟,如果用户在写操作后立刻读,如果这个读请求被分配到从库,就可能读到旧数据。

答案:这个问题没有完美的银弹,只能根据业务场景做权衡。

  • 对一致性要求不高的场景:比如文章阅读数,晚几秒看到真实数据没关系,读写分离没问题。
  • 对一致性要求高的场景:比如用户刚下单付款后查看订单状态,这种操作可以强制走主库读取,可以在代码层面做一些路由,对于特定操作,强制从主库读。
  • 等待同步:还有一种思路是,写操作完成后,客户端等待一小段时间(比如100-200毫秒,这个时间要大于平均的主从延迟)再去读,这样大概率能读到新数据,但这会影响性能,而且等待时间不好确定。

第五个容易忽略的坑:Keys命令引发的服务卡顿。

Redis是单线程的,所有命令排队执行,如果你在生产环境使用了keys *这个命令来查找所有匹配的键,当键数量特别多时,这个命令会长时间阻塞Redis,导致其他所有命令都无法执行,服务就像卡死了一样。

答案:绝对禁止在生产环境使用keys命令,如果需要扫描键,请使用SCAN命令。SCAN命令是增量式的、游标方式的遍历,每次只返回一小部分键,不会长时间阻塞服务器,虽然它可能会返回重复的键(需要客户端去重),并且不是实时精确的,但对于统计、清理等后台任务来说是完全可接受的。

第六个难题:Big Key的问题。

如果一个key对应的value非常大,比如一个hash里存了几十万个字段,或者一个list里存了上百万个元素,这种key就叫Big Key,它会带来很多问题:网络传输慢、阻塞其他请求(因为Redis单线程)、内存分配不均可能导致内存溢出。

答案

  • 设计时避免:在设计数据结构时就要有意识拆分,比如用户消息列表,不要用一个key存所有消息,可以按用户ID+时间分拆成多个key。
  • 发现与处理:使用redis-cli --bigkeys命令可以扫描出示例性的Big Keys,对于已存在的Big Key,需要业务端配合,逐步将其拆分为多个小的key,或者用其他方式优化数据结构。

总结一下,用好Redis不仅仅是会set和get,更要理解它的内存模型、持久化原理、单线程特性以及网络架构,很多坑都是因为对这些底层机制不了解,凭感觉使用造成的,提前预防、合理配置、针对业务场景选择解决方案,才能让Redis真正成为高性能服务的利器。

redis那些坑和难题,从零摸索到全搞定的答案合集