惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Microsoft Azure Blog
Microsoft Azure Blog
C
Cisco Blogs
WordPress大学
WordPress大学
H
Hackread – Cybersecurity News, Data Breaches, AI and More
The Cloudflare Blog
小众软件
小众软件
Recent Commits to openclaw:main
Recent Commits to openclaw:main
I
Intezer
Cyberwarzone
Cyberwarzone
T
The Blog of Author Tim Ferriss
博客园 - Franky
F
Fortinet All Blogs
C
Cyber Attacks, Cyber Crime and Cyber Security
G
Google Developers Blog
Recent Announcements
Recent Announcements
I
InfoQ
T
Threat Research - Cisco Blogs
V
V2EX
T
Tenable Blog
H
Help Net Security
Cisco Talos Blog
Cisco Talos Blog
T
Tailwind CSS Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
The GitHub Blog
The GitHub Blog
P
Privacy & Cybersecurity Law Blog
A
Arctic Wolf
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
D
DataBreaches.Net
罗磊的独立博客
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
D
Docker
T
Tor Project blog
Attack and Defense Labs
Attack and Defense Labs
P
Proofpoint News Feed
H
Heimdal Security Blog
Engineering at Meta
Engineering at Meta
雷峰网
雷峰网
Martin Fowler
Martin Fowler
AWS News Blog
AWS News Blog
IT之家
IT之家
Google DeepMind News
Google DeepMind News
NISL@THU
NISL@THU
Google Online Security Blog
Google Online Security Blog
Vercel News
Vercel News
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
SecWiki News
SecWiki News
GbyAI
GbyAI
P
Proofpoint News Feed
月光博客
月光博客
Schneier on Security
Schneier on Security

Aimee's Blog

微服务与服务拆分:何时拆、怎么拆 异步与事件驱动架构:把协作从「打电话」改成「发消息」 高可用设计:怎么让系统尽量不宕机 可扩展性设计:怎么让系统加机器就能扛更多 高并发三板斧:限流、熔断、降级 架构设计到底在设计什么 —— 从单体到微服务的演进 服务成本账:一个服务一个月烧多少钱 可观测性:线上出问题怎么查 API 设计:好接口长什么样 消息队列:为什么要 MQ,以及丢失、重复、顺序怎么破 查询优化:慢查询怎么治 —— 索引 + 预聚合 缓存:为什么快、三大坑、和数据库怎么保持一致 存储与基建选型 —— 传统业务 vs AI 大模型时代 权限系统怎么设计?—— 从 RBAC 到 Google Zanzibar 鉴权怎么落地?—— 常见业务场景的方案选型与避坑 JWT、OAuth、Token 刷新 —— 一个全栈工程师的鉴权入门笔记 我把跑了五年的旧博客升级到了 3.0 大模型 AIGC 满天飞 推荐一些学习和资料网站 2024 冒个泡 冒个泡 阿里云新机器安装 自定义配置 vue 的 webpack.config.js 从输入url到页面展示 React API 使用demo 欢迎加入 aimee前端技术交流群 github actions 自动化部署 很久以前的美食美荟 无穷之路纪录片还不错哦 vue 模板编译和组件化 vue2 响应式原理 vue2 虚拟DOM原理 vue2 源码解析-初始化 vue2.0 router及简易实现 防抖和节流 Proxy 和 Object.defineProperty git rebase
缓存架构:多级缓存怎么搭
Aimee · 2026-06-17 · via Aimee's Blog

"慢了?加个 Redis"——单层缓存确实能扛很久。但流量再往上走,你会发现 Redis 也不是万能的:它也有带宽和连接上限,一个超级热点 key 能把某个 Redis 节点单独打爆,每次访问还都要走一趟网络。这时候问题就从"用不用缓存"变成了"缓存该摆在系统的哪几个位置"。

《缓存》篇讲的是缓存组件本身——为什么快、Cache-Aside 怎么用、穿透击穿雪崩怎么防、和数据库怎么保持一致。这一篇换个视角,站到架构层看:一个大流量系统怎么把缓存分层布局,从用户的浏览器一路排到数据库,每一层各挡一段流量。组件的坑还是请回《缓存》篇,这里只讲"位置"。


一、为什么单层缓存不够 —— Redis 也有它的上限

是什么。 单层缓存,就是应用前面架一个 Redis(或集群),所有读请求未命中就回填,大部分场景这一层足够好用。多级缓存则是在这条"应用 → Redis → DB"的链路上,再插入若干层缓存,让流量一层层被拦截、越往后越少。

为什么单层会到瓶颈。 Redis 快,但它不是没有天花板。单个实例的吞吐有上限(通常一台 Redis 单线程主流程,几万到十几万 QPS 量级),网卡带宽也有限;当某个 key 热到一定程度——比如一篇全网刷屏的内容——所有请求会集中砸向存这个 key 的那一个节点,这就是业界常说的**超热点 key(hotkey)**问题,加再多分片也分摊不掉,因为它就在一个分片上。

还有两笔隐性成本。 一是网络开销:应用每次读 Redis 都要走一趟 TCP 往返,单次零点几毫秒看着不痛,但乘上每秒几十万次就很可观;二是:Redis 集群越堆越大、内存越买越多,成本是线性甚至更陡地涨。

于是有了分层的思路。 既然一层挡不住,就多设几道闸:能在用户浏览器里命中的,就别回源;能在应用进程内存里命中的,就别打 Redis;能在 Redis 命中的,就别碰数据库。每一层的目标都一样——把流量尽量挡在离用户更近、成本更低的地方,别让它流到下一层。

一句话:多级缓存不是"缓存的高级版",而是把缓存从一个点,铺成一条纵深防线

二、多级缓存的层次 —— 从浏览器一路排到数据库

是什么。 一个典型的大流量读链路,缓存大致排成这样五层,流量从上往下逐层衰减:

客户端 / CDN → 网关缓存 → 本地缓存(进程内)→ 分布式缓存(Redis)→ 数据库

下面逐层看它挡的是什么流量典型场景是什么。

第一层:客户端 / CDN。 离用户最近的一层。浏览器自己的 HTTP 缓存(靠 Cache-ControlETag 这类响应头控制)能让用户根本不发请求;再往后是 CDN(内容分发网络)——把静态资源(图片、JS、CSS、视频)缓存到全国各地的边缘节点,用户就近取,请求压根到不了你的机房。CDN 主要扛静态资源和不常变的内容,这部分流量量极大,挡在这里最划算。

第二层:网关缓存。 请求进了机房,第一道是接入层 / API 网关(Nginx、APISIX 这类)。它可以对一些读多写少、且短时间内结果相同的接口做缓存(比如首页配置、热门榜单),命中就直接在网关返回,不进后端应用。这一层挡的是重复的接口请求

第三层:本地缓存(进程内)。 缓存直接放在应用进程自己的内存里(Java 里常见 Caffeine、Guava Cache)。它没有网络开销,读的就是本机内存,快到纳秒级。它的拿手好戏正是超热点 key——把那几个被疯狂访问的 key 放进每台应用机的本地缓存,流量就在本机消化掉了,根本不会去挤 Redis 的那个热点分片。代价是每台机器各存一份、容量有限、且天然有一致性问题(后面第四节讲)。

第四层:分布式缓存(Redis)。 这是大家最熟的那层,也是扛起大部分读的主力——所有应用机共享同一份缓存数据,容量大、可集群扩展。前面几层没挡住的、变化相对频繁的业务数据(商品、用户信息),绝大多数都在这里命中。Redis 这层的用法、三大坑、和 DB 的一致性,全在《缓存》篇。

第五层:数据库。 缓存的最后兜底。理想情况下,只有"缓存全都没命中"的少量请求才真正落到 DB。多级缓存做得好,DB 的 QPS 能被前面四层削掉一两个数量级——这正是分层的全部意义:让最贵、最脆弱的数据库,只接它必须接的那一小撮流量。

直觉上记一句:越往上(离用户越近)越便宜、越快、越能挡量;越往下越贵、越权威、越扛不住量。 分层就是让流量尽量死在上面。

三、业界主流方案 —— 各层用什么,读写模式怎么选

本地缓存:Caffeine / Guava Cache。 Java 生态里,本地缓存的事实标准是 Caffeine——它基于一种叫 W-TinyLFU 的淘汰算法,命中率和性能都很好,是 Spring 默认推荐的本地缓存。Guava Cache 是更早的方案,API 类似但性能和命中率不如 Caffeine,新项目一般直接上 Caffeine。其他语言也有各自的进程内缓存库,思路一致:一块带淘汰策略的本机内存

分布式缓存:Redis。 跨进程共享的那层,业界几乎是 Redis 的天下(早年也有 Memcached)。集群、持久化、丰富的数据结构,使它既能当"数据库的快副本",也能当"短命数据的主存储"——这两种角色《缓存》篇讲过。

CDN:云厂商的标准件。 CDN 基本不用自己造,直接买云厂商的(阿里云 CDN、AWS CloudFront 等),配好回源规则和缓存时间即可。它和上面两层最大的区别是:CDN 缓存的是 HTTP 响应(尤其静态资源),按地理位置就近分发

缓存读写模式:决定"谁负责读写缓存"。 这是多级缓存里很容易被忽略、但很关键的一组概念——同样是缓存,谁来填、谁来更新有几种固定套路,客观陈述如下:

模式谁读写缓存一句话适合
Cache-Aside(旁路)应用代码自己管读未命中→应用查库回填;写→更新库+删缓存最通用,绝大多数业务;《缓存》篇主讲的就是它
Read-Through(读穿透)缓存层自己管读未命中时,由缓存组件自动去加载数据源、回填把回填逻辑收进缓存库,应用只管读(Caffeine 的 loading cache 就是这思路)
Write-Through(写穿透)缓存层自己管写时同步写缓存+写数据源,两边一起落要求缓存和源强一致、且能接受写变慢
Write-Behind(回写 / 异步写)缓存层异步管先只写缓存,攒一批再异步刷回数据源写极频繁、能容忍短暂不一致和宕机丢数据的风险(如计数器)

怎么选,一句话点破: 读多写少、对一致性要求一般 → Cache-Aside / Read-Through(最常见);写也要强一致、不在乎写慢 → Write-Through;写量大到源扛不住、且数据丢一点能忍 → Write-Behind。注意 Write-Behind 用先写缓存换吞吐,代价是宕机可能丢掉还没刷回的数据,用在钱、订单这类地方要极其谨慎。

四、注意事项 —— 多级带来的新麻烦

层与层之间的一致性,是多级缓存最大的代价。 单层 Redis 时,一致性只需操心"Redis 和 DB"两边;一上多级,变成了"本地缓存、Redis、DB"三方甚至更多方对不齐。最棘手的是本地缓存:它散在每台应用机里,你更新了 DB、删了 Redis,但十几台机器各自的本地缓存还揣着旧值,且它们之间互相不知道。业界常见两种处理:一是给本地缓存设很短的 TTL(比如几秒),用"忍受几秒旧数据"换简单;二是用消息广播(如借助消息队列或 Redis 的发布订阅)通知所有应用机失效本地缓存。核心权衡:本地缓存只适合放能容忍短暂不一致的数据,强一致的东西(余额、库存)别往本地缓存放。

缓存预热,别让系统冷启动就裸奔。 服务刚上线或缓存刚被清空时,缓存是空的,海量请求会直接砸穿到 DB(本质上就是一次人造雪崩)。缓存预热就是在流量进来前,提前把热点数据加载进缓存——比如大促开始前先把爆款商品灌进 Redis 和本地缓存。多级缓存下,预热往往要逐层预热,尤其别忘了进程内的本地缓存。

超热点 key,这正是本地缓存存在的理由。 前面说过,hotkey 加分片也分摊不掉,因为它就在一个分片上。架构层的标准解法,就是把它提到本地缓存——让它在每台应用机的内存里被消化,根本不去挤 Redis。识别 hotkey 通常靠监控统计访问频次,自动把高频 key 推到本地缓存。(雪崩、击穿这类"key 失效"层面的防护,见《缓存》篇。)

本地缓存的容量与淘汰:LRU / LFU。 本机内存很小,装不下全部数据,所以本地缓存必须配淘汰策略,满了就按规则踢掉一些。两种最常见的规则:LRU(Least Recently Used,淘汰最久没被访问的)和 LFU(Least Frequently Used,淘汰访问次数最少的)。Caffeine 用的 W-TinyLFU 就是 LFU 思路的改良版,对"偶尔来一次的冷数据"抵抗力更强。关键:本地缓存的容量上限一定要设(按条数或内存),否则缓存涨到把应用进程内存撑爆,反而把服务搞挂——这个坑很容易被忽略。

和 DB 的最终一致,沿用《缓存》篇那套。 多级缓存没有改变一个底层事实:缓存只能做到最终一致。Redis 与 DB 之间"更新库 + 删缓存""延迟双删",以及对钱 / 库存这类极高一致性数据"要么不缓存、要么订阅 binlog 同步"的处理,全部沿用《缓存》篇,本篇不重复。多级只是在这之上,多叠了一层"本地缓存也要跟着失效"的问题而已。

五、AI 应用的缓存:语义缓存

调大模型又慢又贵,AI 应用特别想缓存它的回答——但有个新问题:传统缓存靠 key 精确匹配,可用户问"今天天气咋样"和"今天天气如何",意思一样、字面不同,精确匹配根本命中不了。

语义缓存(Semantic Cache) 换了匹配方式:把问题转成向量(embedding),按语义相似度找——只要意思够接近,就命中缓存里那条答案直接返回,省掉一次大模型调用(省钱又省时:大模型按 token 收费、还要等几秒,命中缓存几乎零成本、毫秒返回)。

怎么搭: 在应用和大模型之间加一层——查询先转向量,去向量库(Milvus、pgvector)找相似的历史问答,相似度超过阈值就返回缓存答案,否则才真调大模型、再把新问答存回去。本质还是 Cache-Aside,只是"命中判断"从"key 相等"换成了"向量相似"。

注意:相似度阈值是把双刃剑——太松会把"看着像、其实不一样"的问题答错,太紧又命中不了;精确性要求高的场景(法律、医疗)慎用。

六、一张表:各级缓存(挡什么 / 典型方案 / 注意)

把全文收进一张表——这就是多级缓存这条纵深防线的"布防图":

层级挡什么流量典型方案主要注意事项
客户端 / CDN静态资源、不常变内容,挡在机房外浏览器 HTTP 缓存、云 CDN(阿里云 CDN / CloudFront)缓存时间设置、刷新 / 回源规则
网关缓存读多写少、短时结果相同的接口Nginx、APISIX 网关缓存只缓存能容忍短暂旧值的接口
本地缓存(进程内)超热点 key、零网络开销读Caffeine、Guava Cache必设容量上限 + 淘汰(LRU/LFU);多机一致性靠短 TTL / 广播失效
分布式缓存(Redis)大部分业务读(主力)Redis(集群)三大坑、与 DB 一致性见《缓存》篇
数据库兜底,只接没命中的少量请求关系型 / NoSQL别让缓存全失效时被打穿(预热、限流降级)

一句话收尾:多级缓存的全部功夫,就是让流量从上到下越走越少,把最贵最脆弱的数据库,保护在最后。


名词解释

  • 多级缓存(Multi-Level Cache):在"应用 → 缓存 → DB"链路上铺多层缓存(客户端/CDN、网关、本地、分布式),让流量逐层被拦截、越往后越少。
  • 本地缓存 / 进程内缓存(Local / In-Process Cache):缓存直接放在应用进程自己的内存里,无网络开销,快但容量小、每台机器各一份、有一致性问题。
  • 分布式缓存(Distributed Cache):跨进程共享的缓存(如 Redis),所有应用机读同一份,容量大、可集群,是扛读的主力层。
  • CDN(内容分发网络):把静态资源缓存到各地边缘节点,用户就近获取,请求不进源站机房。
  • 超热点 key(Hotkey):被极高频访问的单个 key,集中压在某一个缓存分片上,加分片也分摊不掉,标准解法是提到本地缓存。
  • Cache-Aside(旁路缓存):由应用代码自己管缓存——读未命中回填、写时删缓存,最通用的模式(详见《缓存》篇)。
  • Read-Through(读穿透):读未命中时,由缓存组件自动去数据源加载并回填,应用只管读。
  • Write-Through(写穿透):写时同步写缓存和数据源,两边一起落,强一致但写变慢。
  • Write-Behind / Write-Back(回写):先只写缓存,异步攒批刷回数据源,吞吐高但宕机可能丢未刷回的数据。
  • 缓存预热(Cache Warming):在流量进来前,提前把热点数据加载进缓存,避免冷启动被打穿。
  • LRU(Least Recently Used):淘汰最久未被访问的条目。
  • LFU(Least Frequently Used):淘汰访问次数最少的条目;W-TinyLFU 是其改良版(Caffeine 采用)。
  • 网关缓存:在接入层 / API 网关对读多写少、短时结果相同的接口做缓存,命中即返回,不进后端。

本文属《研发都要懂的事》·服务端架构设计系列——架构层的缓存布局。缓存组件本身(穿透/击穿/雪崩、与数据库一致性)见《缓存》篇;本篇只回答"缓存在系统架构里摆在哪几层"。完整代码与系列在 GitHub · backend-notes