你是一名新手后端程序员,你对计算机基础有一点理解,知道C/S架构(客户端/服务端)。
刚刚写了一个简单的商城增删改查程序,后端用 Java,数据库用 MySQL,感觉很完美符合需求,
使用体验
新的商品上架那就创建一个商品信息,保存到数据库,用户打开网站查询这个商品,浏览器向后端发送请求,查数据库这个商品的信息,返回给浏览器,正常执行。
不过随着商品越来越多,打开网站首页默认加载1000+商品信息,MySQL查询速度就变慢了,那么用户可能等待10秒钟页面才展示。
这时候我们需要用到缓存,加快系统性能,加速响应数据,提升用户体验,让网页秒开!
缓存
现在的服务用一张图表示如下:
服务端到数据库的访问中间可以加一个 redis 缓存,缓存是啥呢?咋就提速了?
首先要明白 MySQL 是要到硬盘中读取数据的,硬盘肯定就慢,而 redis 是在内存中读取数据的,所以比 MySQL 快了非常多,但是断电 redis 的数据会丢失,而 MySQL 不会。所以中间加一个 redis 缓存,把MySQL的数据加载到 redis 中,用户再次查询数据,响应就快了非常多,用户体验大大提升!
如下图:
随着也会带来缓存新的问题,如缓存穿透,缓存雪崩,缓存击穿,我们来逐个解决!
别被这些高大上的名词吓到,其实是再正常不过的场景了
缓存穿透
假如数据库中现有的商品 id 是1-1000,redis 也缓存了这 1000 条数据,而此时有一个恶意用户,疯狂访问 1001,这时候 redis 中没有这条数据,就会去 MySQL 中找,MySQL 有没有不重要,重要的是一定会执行 select * from goods where id = 1001
这条 sql, 如果这个恶意用户一秒钟发送1万条类似 id 为 null 的 sql ,那么 MySQL 就慢死了,或者宕机了。
看到这里缓存穿透其实就是越过了缓存,直接查数据库啊,没什么复杂的!
咋整呢?
如果有一种东西能判断一个数据在或不在一个集合中就好了,这样就轻易判断这个id 是否在 mysql 表里,还真有这么一种神奇的数据结构,布隆过滤器!
布隆过滤器:大海捞针,但能快速告诉你“可能没有”
想象一下,你有一个非常大的仓库(我们的数据集合),里面存放着各种各样的商品(数据库中的数据)。现在,有人拿着一张清单问你,仓库里有没有某个特定的商品。
- 普通查找: 你需要仔细地在仓库里搜索每一个角落,才能确定商品是否存在。如果仓库很大,这会非常耗时。
- 布隆过滤器的方式: 布隆过滤器就像你在仓库入口处设置的一个非常特别的检查站。这个检查站里有一些特殊的“探测器”(哈希函数)和一个很大的“记录板”(位数组)。
当你把一件商品放进仓库时,你会让这几个“探测器”分别对这个商品进行“探测”,每个“探测器”会在“记录板”上对应的位置打上一个标记。
当有人拿着清单上的商品来询问时,你也会让同样的“探测器”对这个商品进行“探测”,然后查看“记录板”上对应的位置是否都被标记了。
-
- 如果所有位置都被标记了: 这说明这个商品可能在仓库里(因为不同的商品经过“探测”后,有可能在“记录板”上留下相同的标记)。
- 如果任何一个位置没有被标记: 这说明这个商品一定不在仓库里。
布隆过滤器的原理:
布隆过滤器是一种概率型的数据结构,用于判断一个元素是否在一个集合中。它具有以下特点:
- 高效性: 判断一个元素是否存在的时间复杂度是常数级别的,非常快。
- 空间效率: 相比于存储所有元素本身,布隆过滤器占用的空间非常小。
- 误判率: 布隆过滤器判断一个元素存在时,可能会发生误判(实际上不存在),但它不会漏判(如果判断不存在,那一定不存在)。
过程图如下:
缓存雪崩
很简单,缓存是要设置过期时间的,我们不能让数据永久存在缓存,太占用内存了,redis 的空间也是有限的,但是如果这一千条数据在同一时间同时过期,那么恰好赶上双十一,百万用户同时访问这一千条数据,无数个 sql 蜂拥而至到 MySQL 中,MySQL 又要顶不住了,向你求助,避免这种情况发生。
这就简单了,给每条数据设置随机过期时间,避免同一时间多条数据同时过期就好了!
缓存击穿
还记得前面说的布隆过滤器吗?它用来防止“穿透”——那些根本不存在的商品。
而缓存击穿,恰恰是一个确实存在、非常热门的商品(比如某个 ID)突然从缓存中失效了!
结果?
这个商品本来有缓存,但某一瞬间缓存失效了,所有用户同时来抢,全部打到数据库。
这时候数据库就像开门大抢购——
- 大量请求冲向数据库;
- Redis 还没来得及重新缓存;
- MySQL 扛不住,宕机或响应超慢。
怎么办?热点数据失效怎么办?
✅ 1. 互斥锁(互斥更新缓存)
比如使用分布式锁(Redisson、Redis SETNX),只有一个线程能去数据库查并重建缓存,其他线程等待或返回旧数据。
if (redis.get("key") == null) {if (tryLock("key-lock")) {// 查询数据库并更新缓存redis.set("key", db.query());releaseLock("key-lock");} else {// 等一等再查缓存,或者返回默认值Thread.sleep(50);return redis.get("key");}
}
✅ 2. 永不过期 + 主动更新
一些特别重要的数据,比如商品详情、活动信息,可以缓存时设为永不过期(或者过期后异步延迟更新):
- 不轻易失效,避免击穿;
- 后台定时任务或监听变更主动更新缓存。
✅ 3. 提前预热缓存
活动开始前、流量高峰前,可以提前把热点数据加载进 Redis,防止被瞬间打爆。