1. 缓存基础概念:什么是缓存、为什么需要缓存、缓存的工作原理、缓存与数据库的关系
1.1 什么是缓存?说白了就是「快照」
缓存这个概念,其实你每天都在用。CPU 有 L1/L2/L3 缓存,浏览器有本地缓存,DNS 有解析缓存。到了后端服务层面,缓存就是把数据放在一个读写更快的存储介质里,下次再用的时候直接取,不用重新算一遍。
我个人习惯把缓存理解成「快照」。你想想看,数据库里存的是全量数据,但大部分请求只关心最近、最热的那一小部分。缓存就是把这些热数据拍个快照,放在离应用程序更近的地方。
核心定义:缓存是一种用空间换时间的策略。用额外的存储空间,换取更快的读取速度。
1.2 为什么需要缓存?
我在项目中遇到过好几次这样的场景:数据库 CPU 飙到 90%,慢查询堆积,接口响应时间从 10ms 变成 2s。一查,全是重复查询同一个热点数据。加了一层缓存之后,数据库负载直接降到 20%。
具体来说,缓存解决了三个核心问题:
- 性能瓶颈:数据库的磁盘 I/O 再快,也快不过内存。缓存把数据放在内存里,读取速度能差两个数量级。
- 高并发压力:一个数据库实例能扛的并发连接数有限。缓存层可以挡住 90% 以上的读请求,让数据库专心处理写操作。
- 响应延迟:用户等不了 500ms 的接口响应。缓存能把延迟降到 1ms 以内,用户体验完全不一样。
| 对比维度 | 数据库(MySQL) | 缓存(Redis) |
|---|---|---|
| 读写速度 | 毫秒级(磁盘 I/O) | 微秒级(内存操作) |
| 并发能力 | 几百~几千 | 几万~十万 |
| 存储成本 | 低(磁盘便宜) | 高(内存贵) |
| 数据持久性 | 强(ACID 事务) | 弱(宕机丢数据) |
1.3 缓存的工作原理
缓存的工作流程其实很简单,就三步:
- 查缓存:请求来了,先去缓存里找数据。
- 缓存命中:找到了,直接返回,完事。
- 缓存未命中:没找到,去数据库查,然后把结果写回缓存,再返回。
嗯,这里要注意一个关键点:缓存未命中时的回源操作。如果并发请求同时发现缓存里没数据,全部打到数据库上,这就是缓存穿透的雏形。后面我们会专门讲怎么防。
我的经验:缓存未命中时,一定要加「互斥锁」或者「分布式锁」,保证只有一个请求去查数据库,其他请求等待。否则高并发下数据库瞬间就挂了。
下面这张图展示了缓存读写的完整流程:
1.4 缓存与数据库的关系
很多人觉得缓存和数据库是「替代关系」,其实不是。它们是互补关系。数据库负责数据的完整性和持久性,缓存负责数据的读取速度和并发能力。
我曾经接手过一个项目,团队把所有数据都放在 Redis 里,连订单数据都不落库。结果 Redis 宕机了一次,丢了 3 个小时的订单数据。嗯,从那以后我深刻理解了:缓存不是数据库的替代品,而是数据库的加速器。
避坑指南:千万不要把缓存当成「万能存储」。缓存的数据可以丢,但数据库的数据不能丢。缓存只存「可重建」的数据,比如从数据库查出来的结果、计算后的中间值。原始数据一定要落库。
它们的关系可以用一句话总结:
- 读多写少的场景,缓存挡在前面,数据库做兜底。
- 写多读少的场景,缓存做异步写入的缓冲层,数据库做最终持久化。
- 数据一致性要求高的场景,缓存只做「读加速」,写操作直接走数据库。
下面这张表总结了常见的缓存策略:
| 策略 | 读流程 | 写流程 | 适用场景 |
|---|---|---|---|
| Cache-Aside | 先查缓存,未命中查 DB 再回写 | 先写 DB,再删除缓存 | 通用场景,最常用 |
| Read-Through | 缓存层自动回源 DB | 直接写 DB | 需要缓存层封装逻辑 |
| Write-Through | 同 Read-Through | 先写缓存,缓存同步写 DB | 强一致性要求 |
| Write-Behind | 同 Read-Through | 先写缓存,异步批量写 DB | 写性能要求高,可接受延迟 |
我的建议:刚开始做缓存设计,直接用 Cache-Aside 模式最稳妥。它逻辑清晰,容易排查问题。等你对业务和数据一致性要求有了更深的理解,再考虑其他模式。
最后说一句:缓存不是银弹。加缓存之前,先问问自己——这个数据真的需要缓存吗?如果每秒只有几十次查询,数据库完全扛得住,加缓存反而增加了系统复杂度和维护成本。我见过太多「为了缓存而缓存」的项目,最后缓存比数据库还难维护。