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

红色战斗氛围下的Redis缓存数据库那些实操经验和坑点分享

主要来自我们团队去年做的一个大项目,当时整个项目组都处于一种“红色战斗氛围”下,就是那种时间紧、任务重、领导天天盯着、口号喊得震天响的状态,在这种高压环境下,我们重度依赖Redis来扛流量、提性能,可以说是被逼着积累了一堆血泪教训,我就直接说我们遇到的实际情况和踩过的坑。

第一个大坑,就是把Redis当成黑盒子,只set和get,不管它死活。 这是最要命的,战斗氛围下,大家只顾着往前冲,写代码飞快,觉得Redis永远不会挂,结果有一次晚高峰,业务量刚上来,接口响应突然变得巨慢,最后发现是Redis内存爆了,我们当时用的是默认配置,没设置内存上限,也没设置过期策略,Redis开始用swap,性能骤降,教训就是,上线前必须设好maxmemory,并且选择合理的淘汰策略,比如allkeys-lru,这件事之后,我们定了个死规矩:每个Key必须带过期时间,就算是长期存储的数据,也得设一个很长的过期时间,防止垃圾数据无限堆积,这个经验是当时从团队里一位老司机那里学来的,他说这是用Redis的“保命条款”。

第二个坑,关于持久化。 一开始我们为了极致性能,把持久化全关了,结果有次机房供电闪断,服务器重启,Redis内存数据全丢,虽然数据可以从数据库再加载,但那几个小时的缓存热点数据全没了,数据库瞬间被打爆,整个服务雪崩,后来我们研究了一下,在战斗氛围下,我们采用了AOF持久化,配置为appendfsync everysec,这是个折中方案,每秒同步一次,最多丢一秒的数据,性能损失也在可接受范围内,绝对不能在高并发下用appendfsync always,每条命令都刷盘,Redis根本扛不住,这个配置上的权衡,是我们在一次线上故障复盘后,参考了Redis官方文档和几个技术博客才定下来的。

第三个是热点Key问题。 我们有个活动页面,首页有个顶部的横幅信息,所有用户进来都会请求这个Key,平时没事,活动一开始,这个Key就成了超级热点,Redis虽然是内存操作,但也是单线程模型,对这个Key的并发访问太高,导致其他命令排队,整个Redis实例延迟增高。我们的解决办法很简单,就是把这个热点Key在应用层做本地缓存(比如缓存10秒),同时给Redis这个Key的过期时间设得短一点,比如1分钟,然后加个随机值,避免同时失效,这个“本地缓存+随机过期时间”的土办法,是当时一个同事从国内某大厂的技术分享里看到的,非常有效。

第四个坑是缓存穿透和雪崩,这两个词听起来专业,但现象很常见。 穿透就是频繁查一个数据库里根本不存在的数据,比如查一个不存在的用户ID,请求每次都穿过Redis打到数据库上。我们当时的应对方法是,即使数据库查不到,也在Redis里缓存一个空值(比如NULL),并设置一个较短的过期时间(比如1分钟),这样后续的请求在Redis层面就被挡住了,雪崩是指大量缓存Key在同一时间点失效,导致所有请求涌向数据库。解决方法是给缓存过期时间加个随机值,比如原本统一缓存1小时,现在改成缓存55分钟到65分钟之间的一个随机值,让Key错开失效,这些策略现在看是常识,但在当时紧张的项目节奏里,不到出问题根本想不起来做。

第五个是运维上的教训。 战斗氛围下,业务代码改得勤,但Redis的键名管理一团糟,今天张三存用户信息用user:123,明天李四用user_info_123,后来王五用u_123,非常混乱,线上出问题时,想快速定位和清理某个业务的缓存都非常困难,后来我们强制推行了键名规范,比如统一用业务模块:子模块:唯一标识的格式,例如user:base:info:123,这样通过keys user:base:info:*就能清晰管理,这个规范是我们用一次惨痛的线上排查代价换来的。

最后还有一个,大Key的问题。 我们有个功能需要缓存一个很大的列表,好几MB,每次序列化反序列化很耗时,传输也占带宽,删除它的时候还会导致Redis短暂卡顿,后来我们把它拆成了多个小Key,或者用Redis的hash结构分段存储。一定要避免一个Value存好几MB的情况。

在那种高压的“战斗氛围”里,最容易犯的错误就是只把Redis当工具用,而忽略了它本身也需要精心维护和配置。最重要的经验就是:第一,内存和过期策略必须配好,这是根基;第二,持久化要根据业务容忍度做权衡;第三,对热点Key、穿透、雪崩要有预防措施;第四,键名规范要从一开始就定好。 这些坑点和技术选型,很多都是我们结合了线上的实际教训,以及从一些技术社区(比如InfoQ、掘金上的一些实战文章)和内部老员工的分享中摸索出来的,不是书本上的理论,都是实打实摔打出来的。

红色战斗氛围下的Redis缓存数据库那些实操经验和坑点分享