你有没有遇到过这样的情况?公司新上的促销活动页面,刚一上线,服务器直接卡成PPT。用户刷不出来,客服电话被打爆,老板在会议室里脸色铁青。其实,很多时候问题不在数据库,而在没把缓存用好。
缓存不是万能药,但不用真不行
后端系统面对高并发,最怕的就是每次请求都直奔数据库。数据库再强,也扛不住成千上万次重复查询。缓存的作用,就是把热点数据提前捞出来,放在离应用更近的地方。下次再要,直接从内存拿,速度能差十倍不止。
比如商品详情页,全国几千万人可能都在看同一个爆款手机。如果每个请求都查一次数据库,那纯粹是给自己找麻烦。不如把这条数据缓存起来,设置个5分钟过期,既保证基本实时,又大大减轻数据库压力。
常见的几种缓存方式
最常用的还是 Redis。它支持多种数据结构,持久化可配,集群方案成熟。部署一个 Redis 实例,写几行代码就能把查询结果存进去。
GET /api/product/1001
// 伪代码示例
if (redis.exists("product:1001")) {
return redis.get("product:1001");
} else {
data = db.query("SELECT * FROM products WHERE id = 1001");
redis.setex("product:1001", 300, data); // 缓存5分钟
return data;
}
Memcached 也有人用,特别适合纯KV场景,轻量快速。不过它不支持复杂数据结构,也没有持久化,适合做临时缓冲层。
缓存穿透:查不存在的数据
有人恶意请求 id=-1 的用户信息,数据库没有,缓存也不会存,每次都会打到数据库。时间一长,数据库照样崩。解决办法是“布隆过滤器”或者缓存空值。
比如查询用户,发现数据库里没有,那就往缓存里塞个 null,过期时间短一点,比如30秒。这样同样的非法请求进来,先查缓存就知道没戏,不用碰数据库。
缓存雪崩:大面积失效
想象一下,半夜12点整,所有缓存刚好同时过期。下一波请求全涌向数据库,瞬间压垮。避免这种情况,可以在设置过期时间时加个随机偏移。
// 不要全设成300秒
redis.setex(key, 300, data);
// 改成 250~350 秒之间的随机值
int expire = 300 + rand(-50, 50);
redis.setex(key, expire, data);
缓存击穿:热点Key失效
某个明星突然发微博,他主页的访问量暴增。这个Key原本在缓存里,但刚好过期了。大量请求同时发现缓存没了,一起查数据库,数据库扛不住。解决方案是加互斥锁。
当缓存失效时,只放一个请求去查数据库,其他请求等着。等数据回来,大家一起用。虽然稍微慢一点,但系统稳住了。
更新策略怎么定
数据变了,缓存要不要跟着变?常见做法是“先更新数据库,再删缓存”。这样下次请求来,发现缓存没了,自动触发一次回源,拿到最新数据。
还有一种是双写模式,数据库和缓存一起更新。但容易出错,比如缓存写失败,数据就乱了。所以大多数系统选择“删缓存”而不是“改缓存”。
缓存不是堆上去就完事了。用得好,系统稳如老狗;用不好,反而添乱。关键是根据业务特点选策略,别照搬别人的方案。毕竟,每个系统都有自己的脾气。