缓存和缓冲

缓存(cache):Cache 的核心作用是加快取用的速度。比如你一个很复杂的计算做完了,下次还要用结果,就把结果放手边一个好拿的地方存着,下次不用再算了。加快了数据取用的速度。
缓冲(buffer):Buffer 的核心作用是用来缓冲,缓和冲击。比如你每秒要写 100 次硬盘,对系统冲击很大,浪费了大量时间在忙着处理开始写和结束写这两件事嘛。用个 buffer 暂存起来,变成每 10 秒写一次硬盘,对系统的冲击就很小,写入效率高了,日子过得爽了。极大缓和了冲击。

Cache

缓存的核心价值

后端存储(如 MySQL、硬盘文件)的读写速度通常较慢(毫秒级甚至更慢),而应用的访问需求往往是高频且低延迟的(微秒级)

缓存的本质:在内存中临时存储 “热点数据”(频繁访问的数据),让应用优先从缓存读取,跳过慢速的后端存储,从而:

  • 提升响应速度(内存读写比磁盘快 1000 倍以上)
  • 降低后端存储的访问压力(减少数据库查询,文件 IO 等)

缓存的工作流程

  1. 应用需要数据时,先查询缓存
  2. 缓存中有数据,直接返回数据
  3. 缓存中无数据,去后端存储查询数据,将查询到的数据写入缓存,返回数据给应用

缓存的设计

如果设计不当,会出现 “缓存与存储不一致”“缓存失效导致系统崩溃”

适合存储的数据:

  • 高频访问(热点数据):比如电商的商品详情、首页推荐列表(访问量极大);
  • 低频修改:比如用户地区信息、配置参数(修改少,缓存后长期有效);
  • 计算昂贵:比如复杂的统计报表(计算一次耗时久,结果缓存后直接复用)。

缓存的存储位置

  • 客户端缓存:浏览器本地,APP 本地缓存
  • 应用层缓存:应用进程内的本地缓存(如 Java 的 Caffeine、Guava,Python 的 functools.lru_cache),适合单节点应用,速度最快(无需网络 IO);
  • 分布式缓存:独立部署的缓存服务,如 redis,多应用节点共享,适合分布式系统(跨服务 / 跨机器访问)。

注意事项

缓存中的数据是后端存储的 “副本”,当后端存储的数据更新时,缓存必须同步更新或删除,否则会出现 “缓存脏数据”(缓存与存储不一致)。

  1. 超时剔除:给缓存设置 “过期时间”,到期后自动删除,后续请求会重新从后端存储加载最新数据。
  2. 主动更新:当后端存储数据更新时,主动操作缓存(更新或删除),确保缓存与存储一致。
  3. 缓存空间有限(内存成本高),当缓存被写满时,需要按规则删除旧数据,腾出空间给新数据。
    常见淘汰算法:
  • LRU:删除 “最近最少使用” 的数据
  • LFU:删除 “访问频率最低” 的数据
  • FIFO:按写入顺序删除最早的数据

缓存的经典问题

缓存穿透

用户查询一个 “后端存储中不存在的数据”(比如查 ID=-1 的用户),导致每次请求都 “缓存未命中”,直接穿透到后端存储,大量此类请求会拖垮数据库。

解决方案

  1. 对不存在的数据,在缓存中存储一个 “空值”(如 null),并设置较短的过期时间(避免长期占用内存);
  2. 在读取缓存时过滤,对请求做一个检验

缓存击穿

一个 “热点 key”(如秒杀商品的库存)过期的瞬间,大量请求同时 “缓存未命中”,全部涌向后端存储,导致存储压力骤增

解决方案

  1. 互斥锁:当热点 key 过期时,只允许一个请求去后端存储加载数据并更新缓存,其他请求等待(用 Redis 的 setnx 实现分布式锁);

缓存雪崩

大量缓存 key 在同一时间过期,或缓存服务(如 Redis)突然宕机,导致所有请求瞬间穿透到后端存储,直接压垮数据库。

解决方案

  1. 给每个 key 的过期时间加一个随机数(如 10±2 秒),避免同时过期;
  2. 用缓存集群(如 Redis Cluster),避免单节点故障
  3. 服务熔断 / 降级:当后端存储压力过大时,暂时返回默认值(如 “系统繁忙”),保护数据库;

Buffer

缓冲是临时存储数据的 “中间区域”,核心作用是解决 “数据生产速度” 和 “数据消费速度不匹配” 的问题,或者 “不同设备 / 模块之间数据传输效率差异” 的问题

简单说:就像你往水桶里倒水(生产),但水桶的排水口比较小(消费慢),你可以先把水倒进一个盆里(缓冲),再让盆里的水慢慢流进排水口 —— 这个 “盆” 就是缓冲。

核心作用

  1. 削峰填谷:内存的读写速度是 GB/s 级,而硬盘是 MB/s 级(慢 100 倍以上)。如果直接让内存数据 “实时” 写入硬盘,内存会被硬盘的慢速度拖垮。这时用一块内存区域做 “磁盘缓冲”(如操作系统的 Page Cache),内存先快速把数据写到缓冲,缓冲再 “慢慢” 同步到硬盘,避免内存等待

  2. 减少 “低效操作” 的次数:很多场景下,“单次批量操作” 比 “多次零散操作” 效率高得多。缓冲可以把 “零散的小数据” 攒成 “批量的大数据”,减少底层操作次数。你用 BufferedWriter 写文件时,不是每次调用 write()就立即写磁盘(磁盘 IO 很慢),而是先写到内存缓冲,等缓冲满了(或主动调用 flush()),再一次性写入磁盘 —— 原本 1000 次小 IO,变成 1 次大 IO,效率提升显著。

  3. 数据暂存与同步:消息队列(如 RabbitMQ)的 “队列” 本质就是一个缓冲:生产者发送消息后不用等消费者处理,直接丢进队列(缓冲),消费者后续从队列里取消息即可。