缓存基础概念与原理

缓存基础概念与原理

这是一个小白的学习记录

边学边练,把踩过的坑都记下来


先说下我为什么要学这个

公司项目最近要搞性能优化,领导让我负责调研缓存方案。拿到手一看,好家伙,缓存这东西天天听,但具体是啥原理,我还真说不清楚。

没办法,只能从基础开始学起,顺便把学习笔记整理出来。

如果你也是新手,希望这篇笔记能帮到你。


什么是缓存?

我当时看到这个概念都懵了,啥是缓存?

说人话就是

缓存就像你买个手机,手机自带个充电器。你每次要充电,直接从抽屉里拿充电器就行,不用每次都去商店买新的。

技术上的解释

缓存就是把经常访问的数据,放在一个访问速度更快的地方。下次再访问同样的数据,就不用去原来的地方(比如数据库)取了,直接从缓存里拿。

我的理解

  • 缓存 = 快速访问的数据副本
  • 核心思想 = 空间换时间
  • 目的 = 减少数据库压力,提高访问速度

为什么需要缓存?

我当时也困惑,数据库不是挺快的吗?为啥还要搞缓存?

后来查资料才明白,主要是这几个原因:

1. 性能问题

数据库查询慢啊!特别是复杂查询、多表关联的时候。

举个例子

复制代码
无缓存:
用户请求 → 数据库查询(100ms) → 返回结果

有缓存:
用户请求 → 缓存查询(1ms) → 返回结果

好家伙,100 倍的性能差距!

2. 数据库压力

想象一下,如果 1 万个人同时访问同一个商品详情,每次都查数据库,数据库不得崩溃?

就像排队结账

  • 无缓存:1 万个人都去收银台结账,收银员累死
  • 有缓存:收银员把常见商品的价格贴在墙上,大部分人直接看墙就行

3. 成本问题

数据库扩容贵啊!加内存、加 CPU、加机器,哪样不要钱?

缓存便宜多了,用内存换性能,划算!


缓存的工作原理

这个我一开始也没搞懂,后来一点点啃,总算是明白了。

基本流程


没有
用户请求
缓存中有吗?
直接从缓存返回
查询数据库
写入缓存
返回结果

说人话就是

  1. 用户来要数据
  2. 先去缓存里找
  3. 找到了,直接给(命中缓存)
  4. 没找到,去数据库查,查完顺便放缓存里(缓存未命中)
  5. 返回结果

关键概念

命中率

公式

复制代码
命中率 = 命中缓存的次数 / 总请求次数 * 100%

我的理解

  • 命中率越高,缓存效果越好
  • 一般要达到 80% 以上才算合格
  • 100% 不可能,总有一些冷门数据
淘汰策略

缓存空间有限,满了怎么办?得扔掉一些不用的数据。

常见的淘汰策略

策略 说明 适用场景
LRU 最近最少使用的先淘汰 通用场景(推荐)
LFU 最不经常使用的先淘汰 热点数据明显
FIFO 先进先出 简单场景
TTL 过期时间到了就淘汰 时效性数据

我的感受

  • LRU 最常用,效果也最好
  • LFU 适合热点数据明显的场景
  • TTL 适合有过期时间的数据(比如验证码)

缓存的优缺点

这个我得说实话,缓存不是万能的,有优点也有缺点。

优点

  1. 性能提升:访问速度快,响应时间短
  2. 数据库压力小:减少数据库查询次数
  3. 成本低:比数据库扩容便宜
  4. 可扩展性好:可以水平扩展

缺点

  1. 数据一致性问题:缓存和数据库数据可能不一致
  2. 缓存穿透/击穿/雪崩:搞不好会出大问题
  3. 增加复杂度:代码更难维护
  4. 内存占用:缓存要占内存

我的感受

  • 缓存是把双刃剑,用好了性能飞升,用不好问题一堆
  • 一定要想清楚再用,别为了用而用

常见的缓存策略

这个我一开始也懵,什么 Cache-Aside、Read-Through,啥跟啥啊?

后来才明白,就是几种不同的缓存使用方式。

1. Cache-Aside(旁路缓存)

最常用的模式

流程

复制代码
读操作:
1. 先读缓存
2. 缓存没有,读数据库
3. 把数据写入缓存
4. 返回结果

写操作:
1. 先更新数据库
2. 再删除缓存

代码示例

java 复制代码
// 读操作
public User getUser(Long id) {
    // 1. 先读缓存
    User user = cache.get(id);
    if (user != null) {
        return user;
    }
    
    // 2. 缓存没有,读数据库
    user = userDao.findById(id);
    
    // 3. 写入缓存
    if (user != null) {
        cache.put(id, user);
    }
    
    // 4. 返回结果
    return user;
}

// 写操作
public void updateUser(User user) {
    // 1. 先更新数据库
    userDao.update(user);
    
    // 2. 再删除缓存
    cache.delete(user.getId());
}

我的感受

  • 最简单,最常用
  • 适合读多写少的场景
  • 我推荐新手就用这个

2. Read-Through(读穿透)

说人话就是

缓存自己负责从数据库加载数据,应用不用管。

流程

复制代码
1. 应用读缓存
2. 缓存没有,缓存自己从数据库加载
3. 返回结果

代码示例

java 复制代码
// 使用 Caffeine 的 CacheLoader
Cache<Long, User> cache = Caffeine.newBuilder()
    .maximumSize(1000)
    .expireAfterWrite(10, TimeUnit.MINUTES)
    .build(new CacheLoader<Long, User>() {
        @Override
        public User load(Long id) {
            // 缓存没有时,自动从数据库加载
            return userDao.findById(id);
        }
    });

// 应用只需要读缓存
User user = cache.get(id);

我的感受

  • 代码更简洁
  • 适合读多写少的场景
  • 需要缓存库支持(比如 Caffeine)

3. Write-Through(写穿透)

说人话就是

应用只写缓存,缓存自己负责同步到数据库。

流程

复制代码
1. 应用写缓存
2. 缓存同步写数据库
3. 返回结果

代码示例

java 复制代码
// 使用 Caffeine 的 CacheWriter
Cache<Long, User> cache = Caffeine.newBuilder()
    .maximumSize(1000)
    .expireAfterWrite(10, TimeUnit.MINUTES)
    .writer(new CacheWriter<Long, User>() {
        @Override
        public void write(Long id, User user) {
            // 写缓存时,同步写数据库
            userDao.update(user);
        }
    });

// 应用只需要写缓存
cache.put(id, user);

我的感受

  • 代码更简洁
  • 适合写多读少的场景
  • 需要缓存库支持

4. Write-Behind(写后置)

说人话就是

先写缓存,缓存异步写数据库。

流程

复制代码
1. 应用写缓存
2. 缓存立即返回成功
3. 缓存异步写数据库

我的感受

  • 性能最好
  • 风险也最大(可能丢数据)
  • 适合对数据一致性要求不高的场景

缓存策略对比

策略 优点 缺点 适用场景
Cache-Aside 简单、灵活 代码繁琐 通用场景(推荐)
Read-Through 代码简洁 需要缓存库支持 读多写少
Write-Through 代码简洁、数据一致 写性能差 写多读少
Write-Behind 写性能最好 可能丢数据 对一致性要求不高

我的建议

  • 新手就用 Cache-Aside,最稳妥
  • 读多写少用 Read-Through
  • 写多读少用 Write-Through
  • Write-Behind 慎用,除非你很清楚风险

验证步骤

1. 理解概念

看完这篇,你应该能回答:

  • 什么是缓存?
  • 为什么需要缓存?
  • 缓存的工作原理是什么?
  • 常见的缓存策略有哪些?

2. 画图理解

自己画一下缓存的流程图,加深理解。

3. 思考场景

想想你的项目中,哪些地方适合用缓存?


总结

我踩过的坑

  1. 一开始不理解缓存策略:后来画图才明白
  2. 以为缓存越快越好:其实要考虑一致性问题
  3. 不知道选哪种策略:新手就用 Cache-Aside

核心要点

  • 缓存是什么:快速访问的数据副本
  • 为什么用缓存:提高性能,减少数据库压力
  • 工作原理:先查缓存,没有再查数据库
  • 常见策略:Cache-Aside(推荐)、Read-Through、Write-Through、Write-Behind

最后说两句

其实缓存基础也没多难,就是多画图,多思考。

我刚开始也懵,后来一点点试,总算是搞定了。

肯定有理解不对的地方,欢迎大佬指正。

如果你也遇到类似问题,希望这篇笔记能帮到你。

相关推荐
一只大袋鼠2 小时前
MyBatis 特性(三):缓存、延迟加载、注解开发
java·数据库·笔记·sql·缓存·mybatis
晨曦夜月5 小时前
高并发内存池——单例模式在缓存的作用
缓存·单例模式
jeCA EURG6 小时前
一、安装Redis(win11环境下)
数据库·redis·缓存
AILabNotes7 小时前
016、性能与安全权衡:网关的缓存、中继与匿名化策略
安全·缓存
小江的记录本9 小时前
【分布式】分布式核心组件——分布式ID生成:雪花算法、号段模式、美团Leaf、百度UidGenerator、时钟回拨解决方案
分布式·后端·算法·缓存·性能优化·架构·系统架构
摇滚侠1 天前
短信验证码登录 Redis实战 黑马程序员
数据库·redis·缓存
014-code1 天前
Redis Stream:消息队列的进阶之路
数据库·redis·缓存
IntMainJhy1 天前
【Flutter for OpenHarmony 】第三方库 实战:`cached_network_image` 图片缓存+骨架屏鸿蒙适配全指南✨
flutter·缓存·harmonyos
JoshRen1 天前
Window下Redis的安装和部署详细图文教程(Redis的安装和可视化工具的使用)
数据库·redis·缓存