用Redis缓存点击数,网页流量突然蹭蹭往上涨了呢
- 问答
- 2026-01-09 01:31:49
- 4
最近网站出了个怪事,我们为了提升性能,把文章和页面的点击量、访问次数这些数据,从直接写数据库改成了先放到Redis里面,然后再定时存到数据库,这么做的初衷是好的,因为Redis特别快,能抗住瞬间的高并发访问,数据库的压力就小多了,刚开始一切正常,网站速度也确实快了,但没过多久,我们就发现不对劲了,后台统计的某些页面流量数据开始变得有点“浮夸”,尤其是几个热门内容,那点击量蹭蹭往上涨,涨幅明显不太符合常理,像是被凭空放大了好几倍。

我们一开始有点懵,以为是内容突然爆火,或者被什么大V推荐了,但查了来源和用户行为路径,又对不上,后来技术团队开始排查,这一查,才发现问题就出在我们觉得“万无一失”的Redis缓存方案上,问题不是Redis本身不行,而是我们用的时候,忽略了一些细节。
根据技术团队的分析和网上一些开发者分享的经验(比如在一些技术社区像知乎、CSDN上看到的类似案例),我们踩的坑主要有这么几个:

第一个坑,叫做“缓存穿透”,这个名词听起来有点唬人,但说白了就是,有人(可能是恶意爬虫,也可能是正常的请求)在疯狂请求一个我们网站上根本不存在的文章ID或者页面地址,因为数据在Redis里没有,每次请求都会穿透Redis,直接打到后面的数据库去查询,数据库查不到,返回空结果,我们程序里设计的逻辑是,对于这种不存在的数据,就不往Redis里写了,免得浪费空间,这下好了,这个恶意请求每次都能绕过Redis,直接压垮数据库,虽然这听起来更像是数据库压力大的问题,但它间接影响了我们整个计数服务的稳定性,如果恶意请求模拟的是大量不同的、不存在的热门ID,就会造成一种混乱。
但让我们流量“虚高”的更直接原因,是另一个坑,我们可能遇到了“缓存雪崩”的轻微版本,我们当时给这些点击数据设置的过期时间是一样的,都设置成一小时后同步到数据库并清空,想象一下,在某个时间点,一大批缓存的计数键同时失效了,这时候,如果突然涌进来一大波正常用户的访问请求,这些请求发现Redis里的计数缓存没了,于是所有的请求都会同时去执行一个动作:从数据库读出当前的点击量(比如是1000),然后在程序里加上1(变成1001),再把这个新值设置回Redis,在极高并发下,成百上千个请求可能在同一毫秒内都做了“读取1000,计算1001,写入1001”这个操作,实际上可能有一万个真实的点击,但最终Redis里只记录了1001次,而数据库最终也只从Redis里拿到了1001这个数,这显然不对,但更可怕的是另一种情况,如果我们的逻辑写得稍有不同,比如是“读取,加一,写入”,没有处理好并发,可能会导致数据错乱,但有时异常的逻辑反而可能造成计数被重复累加,从而虚高。
根据我们程序员的深入排查,最可能导致我们这次流量虚涨的,是一个更隐蔽的问题,和Redis的持久化机制有关,我们用的是RDB持久化方式,就是隔一段时间把内存里的数据拍个快照存到硬盘上,问题就出在这里,在那一刻,Redis内存中记录的某个文章点击数是5000,Redis开始执行BGSAVE命令,在后台创建RDB快照,这个创建过程需要一点时间,在这段时间里,前端仍然有大量的新点击进来,这个数字可能已经从5000涨到了6000,RDB快照里保存的,还是开始创建那一刻的5000,等RDB快照生成完毕,内存里最新的6000数据还没来得及等到下一次快照,Redis服务器可能因为某种原因(比如机器资源紧张、部署更新)突然重启了,重启之后,Redis会用最近一次保存的RDB快照(记录的是5000)来恢复数据,从5000涨到6000之间的那1000次真实的点击,就这么凭空消失了!不对,等等,消失是减少,我们遇到的是增加啊,别急,关键点来了:我们的应用程序逻辑是,如果Redis里找不到这个文章的计数,它会去数据库里把最新的值查出来,然后加载到Redis里,如果这个“查数据库”的逻辑写得不够严谨,或者在重启恢复期间,有大量的请求涌入,程序可能会误判,以为Redis里没有数据是因为从来没写过,而不是因为重启丢失了数据,它可能不是去数据库查询最新值,而是直接初始化了一个0或者一个错误的值,然后开始重新计数,这样,之前积累的5000次点击就全没了,新的点击又从零开始计算,但这依然不是虚高。
真正的“虚高”可能源于更复杂的并发竞争条件,在Redis重启后数据恢复的短暂瞬间,多个请求同时发现计数键不存在,它们可能都去数据库查询,但数据库的值可能还是旧的(比如还没从之前的RDB中恢复过来,或者主从同步有延迟),然后这几个请求都拿到了一个基础值,比如A请求拿到5000,B请求也拿到5000,它们各自在本地加1,然后都去Redis执行设置操作,A设为5001,B也设为5001,这本来已经少计数了,但如果我们的代码逻辑是为了防止计数丢失,采用了“增量累加”的方式,比如不是直接SET新值,而是命令Redis在原有值上增加一个增量(INCR命令),而在键不存在时,INCR会从0开始,在键不存在的瞬间,成百上千个请求都执行了INCR命令,Redis会忠实地为每一个请求都加1,但这个计数是从0开始暴增的,完全丢失了历史基数,这样,当数据库最终从Redis同步数据时,就会得到一个远远超过实际数字的“虚高”流量。
这件事让我们深刻体会到,用一个强大的工具 like Redis,如果只是简单地“拿来就用”,而不深入了解它的特性、配置以及在高并发场景下可能出现的各种边界情况,很容易就会掉到坑里,看似简单的点击计数,背后也藏着不少学问,我们正在重新审视和优化这套缓存方案,比如考虑使用不同的过期时间避免集中失效、加强防止恶意请求的校验、优化缓存失效后的数据重建逻辑,以及根据业务重要性选择合适的持久化策略等,希望能让数据变得更准确。

本文由符海莹于2026-01-09发表在笙亿网络策划,如有疑问,请联系我们。
本文链接:https://www.haoid.cn/wenda/77149.html
