一、为什么要使用缓存?
在信息爆炸的时代,缓存技术如同一个高效的“瞬时记忆”系统,将频繁访问的数据暂存在高速存储介质中,让每一次数据请求都变得轻盈迅捷 。它用空间换取时间,是现代计算架构中不可或缺的“加速器”。
缓存的核心价值在于其“空间换时间”的基本哲学。它将慢速存储设备(如数据库、远程API)中的常用数据,复制到快速存储设备(如内存)中,从而带来多重关键收益。
1、极速响应,提升体验
内存的读取速度可比磁盘快几个数量级。缓存将热点数据置于内存,用户请求无需经过漫长的磁盘I/O或网络往返,响应时间从毫秒级降至微秒甚至纳秒级,带来“瞬间加载”的流畅体验。
2、减轻负载,保护后端
数据库等后端服务每秒能处理的请求有限。缓存作为一道“缓冲墙”,拦截了大量重复查询,显著降低后端系统的计算压力、连接数和I/O消耗,使其能更专注于处理复杂事务和核心业务逻辑。
3、节省带宽,降低成本
对于网站和App,带宽是直接的成本。缓存使得相同数据无需反复从源站拉取,能“产生极小的网络流量”,有效降低运营成本。对用户而言,也意味着更少的数据消耗和更快的加载速度。
4、提升系统扩展性与可用性
在高并发场景下,缓存层可以水平扩展,独立承载巨大的访问流量,避免单点数据库成为瓶颈。即使后端服务暂时不可用,缓存中的数据也能保证部分核心功能的持续服务,提升系统整体韧性。
二、什么是缓存穿透?缓存击穿?缓存雪崩? 怎么解决?
🕳️ 穿透
不存在的数据被疯狂查询
💥 击穿
热点key失效瞬间被高并发击穿
🌪️ 雪崩
大量key同时失效导致系统崩溃
2.1、缓存穿透:不存在的数据被恶意攻击
什么是缓存穿透?
用户请求的数据在缓存和数据库中都不存在,导致每次请求都穿透到数据库。恶意攻击者会利用这个漏洞,发送大量不存在的数据请求,直接压垮数据库。 如下:缓存穿透攻击示意图

2.2、解决方案
布隆过滤器: 在缓存前加一层布隆过滤器,快速判断数据是否存在
缓存空对象: 数据库查询为空时,将空结果缓存起来(设置短过期时间)
参数校验: 对请求参数进行合法性校验,拦截非法请求

2.2、缓存击穿:热点key突然失效
什么是缓存击穿?
某个热点数据(如爆款商品)缓存刚好失效,大量并发请求同时穿透缓存,直接冲击数据库。就像演唱会门票售罄瞬间,大量粉丝涌向售票处。
解决方案
互斥锁: 使用分布式锁确保同一时间只有一个线程重建缓存
热点永不过期: 对热点数据设置永不过期,通过异步任务更新
提前续约: 在key过期前自动续约,避免失效瞬间
2.3、缓存雪崩:大规模key同时失效
什么是缓存雪崩?
大量缓存key在同一时间失效,导致所有请求全部打到数据库。就像雪山崩塌,瞬间压垮整个系统。常见原因包括:所有key设置相同过期时间、Redis服务宕机等。
解决方案
随机过期时间: 在基础TTL上增加随机偏移量(如30分钟+随机0-10分钟)
缓存预热: 系统启动时预先加载热点数据到缓存
高可用架构: 使用Redis集群、哨兵模式防止单点故障
三、如何保证Redis与数据库的数据一致?
1、如果先删缓存,再写数据库:
在高并发场景下,当第一个线程删除了缓存,还没有来得及写数据库,第二个线程来读取数据,会发现缓存中的数据为空,那就会去读数据库中的数据(旧值,脏数据),读完之后,把读到的结果写入缓存(此时,第一个线程已经将新的值写到缓存里面了),这样缓存中的值就会被覆盖为修改前的脏数据。
1)先操作缓存,但是不删除缓存。将缓存修改为一个特殊值(-999)。客户端读缓存时,发现是默认值,就休眠一小会,再去查一次Redis。 特殊值对业务有侵入。 休眠时间,可能会多次重复,对性能有影响。2)延时双删。 先删除缓存,然后再写数据库,休眠一小会,再次删除缓存。如果数据写操作很频繁,同样还是会有脏数据的问题。
2、先写数据库,再删缓存:
如果数据库写完了之后,缓存删除失败,数据就会不一致。总结: 始终只能保证一定时间内的最终一致性。
1)给缓存设置一个过期时间 问题:过期时间内,缓存数据不会更新。2)引入MQ,保证原子操作。
将热点数据缓存设置为永不过期,但是在value当中写入一个逻辑上的过期时间,另外起一个后台线程,扫描这些key,对于已逻辑上过期的缓存,进行删除。
四、如何设计一个分布式锁?如何对锁性能进行优化?
分布式锁的本质:
就是在所有进程都能访问到的一个地方,设置一个锁资源,让这些进程都来竞争锁资源。通常对于分布式锁,会要求响应快、性能高、与业务无关。
Redis实现分布式锁:
SETNX key value:当key不存在时,就将key设置为value,并返回1。如果key存在,就返回0。EXPIRE key locktime: 设置key的有效时长。 DEL key: 删除。 GETSET key value: 先GET,再SET,先返回key对应的值,如果没有就返回空。然后再将key设置value。
public boolean tryLock(RedisnConnection conn) {
long nowTime = System.currnetTimeMillis();
12
long expireTIme = nowTime + 1000;
if (conn.SETNX("mykey", expireTIme) == 1) {
conn.EXPIRE("mykey", 1000);
return true;
} else {
long oldVal = conn.get("mykey");
if (oldVal != null && oldVal < nowTime) {
long currentVal = conn.GETSET("mykey", expireTime);
if (oldVal == curentVal) {
conn.EXPIRE("mykey", 1000);
return true;
}
return false;
}
return false;
}
}
上面就形成了一个比较高效的分布式锁。分析一下,上面各种优化的根本问题在于SETNX和EXPIRE两个指令无法保证原子性。Redis2.6提供了直接执行lua脚本的方式,通过Lua脚本来保证原子性。redission。
五、Redis如何配置Key的过期时间?他的实现原理是什么?
1)定期删除: 每隔一段时间,执行一次删除过期key的操作。2)懒汉式删除: 当使用get、getset等指令去获取数据时,判断key是否过期。过期后,就先把key删除,再执行后面的操作。 Redis是将两种方式结合来使用。懒汉式删除定期删除:平衡执行频率和执行时长。
六、海量数据下,如何快速查找一条记录?
1、使用布隆过滤器
2、在Redis中建立数据缓存
缓存击穿:对不存在的数据也建立key。这些key都是经过布隆过滤器过滤的,所以一般不会太多。
缓存过期:将热点数据设置成永不过期,定期重建缓存。 使用分布式锁重建缓存。
查询优化:按槽位分配数据,自己实现槽位计算,找到记录应该分配在哪台机器上,然后直接去目标机器上找。
本文探讨了缓存使用的原因,详细解释了缓存穿透、击穿和雪崩问题及其解决方案。同时,讨论了如何保证Redis与MySQL数据的一致性,以及如何设计和优化分布式锁。此外,还介绍了Redis中Key过期时间的配置及其原理,并针对海量数据场景下的快速查询给出了思路。


被折叠的 条评论
为什么被折叠?



