什么是缓存命中?一文理解 Cache Hit
在软件系统中,我们经常会听到“缓存命中”“缓存未命中”“缓存命中率”等概念。无论是浏览器缓存、Redis、CDN,还是 CPU 缓存,它们背后的核心思想其实非常相似:把经常访问的数据暂时存放在一个访问速度更快的位置,从而减少重复计算或重复读取。
而所谓的“缓存命中”,就是整个缓存机制中最理想的一种情况。
一、什么是缓存?
在理解缓存命中之前,首先需要知道什么是缓存。
缓存(Cache)可以理解为一个临时的数据存储区域。它通常保存那些访问频率较高、获取成本较大的数据。
例如,一个网站需要展示商品详情。
正常情况下,服务器可能需要:
用户请求 → 查询数据库 → 获取商品数据 → 返回给用户
如果一个热门商品每分钟被访问几万次,那么数据库就需要不断执行相同的查询。
这会产生大量重复工作。
因此,我们可以在数据库之前增加一层缓存,例如 Redis:
用户请求 → 查询 Redis → 查询数据库
第一次访问商品时,如果 Redis 中没有数据,就去数据库查询,然后将查询结果保存到 Redis。
之后其他用户再访问同一个商品时,就可以直接从 Redis 获取数据,而不需要再次查询数据库。
这就是缓存的基本作用。
二、什么是缓存命中?
缓存命中,英文通常称为 Cache Hit。
简单来说:
当程序请求某个数据时,如果缓存中已经存在这个数据,就叫做缓存命中。
例如:
用户请求商品 ID 为 1001 的商品信息。
程序首先检查 Redis:
GET product:1001
如果 Redis 中已经存在:
product:1001 → iPhone 17 Pro
那么程序就可以直接返回这个数据。
整个过程就是:
用户请求
↓
查询缓存
↓
缓存中存在数据
↓
直接返回
这种情况就叫做:
缓存命中(Cache Hit)
它最大的特点是:不需要访问后端真正的数据源。
例如数据库、文件系统或者远程 API。
因此,缓存命中通常意味着更低的响应时间和更小的系统压力。
三、什么是缓存未命中?
与缓存命中相对应的是:
缓存未命中(Cache Miss)
也就是说,程序去缓存中查询数据,但是没有找到。
例如:
GET product:1002
Redis 返回:
nil
说明缓存中没有这个商品。
此时系统通常需要继续查询数据库:
用户请求
↓
查询缓存
↓
缓存中没有数据
↓
查询数据库
↓
获得数据
↓
写入缓存
↓
返回数据
这个过程就是一次缓存未命中。
假设数据库返回商品信息:
product:1002 = MacBook Pro
程序通常还会把它保存到 Redis:
SET product:1002 "MacBook Pro"
这样下一次再访问 product:1002 时,就可以直接从缓存中读取。
也就是说:
第一次请求:Cache Miss
第二次请求:Cache Hit
缓存系统正是通过这种机制逐渐提高访问效率。
四、什么是缓存命中率?
衡量缓存效果时,一个非常重要的指标叫做:
缓存命中率(Cache Hit Rate)
计算公式是:
缓存命中率 = 缓存命中次数 ÷ 总请求次数 × 100%
例如某个系统在一分钟内有 10000 次查询。
其中:
缓存命中:9000 次
缓存未命中:1000 次
那么缓存命中率就是:
9000 ÷ 10000 = 90%
也就是说:
缓存命中率 = 90%
这意味着 90% 的请求都不需要访问数据库。
数据库实际只处理了 10% 的请求。
因此,在高并发系统中,提高缓存命中率通常可以显著降低数据库压力。
五、为什么缓存命中很重要?
缓存最主要的价值可以归纳为三个方面。
1. 提高访问速度
一般来说,不同存储介质的访问速度存在明显差异。
例如:
CPU Cache
↓
内存
↓
本地磁盘
↓
数据库
↓
远程网络服务
通常越往下,访问成本越高。
如果数据已经存在于更高速的缓存中,程序就不需要访问较慢的数据源。
因此缓存命中可以显著降低响应时间。
2. 降低数据库压力
假设一个接口每秒有 100000 个请求。
如果所有请求都访问数据库:
100000 QPS
↓
数据库
数据库可能很快就会成为整个系统的性能瓶颈。
如果缓存命中率达到 95%,那么真正访问数据库的请求可能只有:
100000 × 5% = 5000
也就是说:
数据库压力从约 100000 QPS 降到了约 5000 QPS。
这也是 Redis 等缓存系统在高并发架构中被广泛使用的重要原因。
3. 减少重复计算
缓存不一定只缓存数据库数据。
一些计算成本较高的结果也可以缓存。
例如:
用户画像计算
排行榜计算
复杂 SQL 查询
机器学习推理结果
搜索结果
推荐结果
如果同一个结果会被频繁请求,就可以把计算结果缓存起来。
下一次遇到相同请求时直接返回已有结果。
因此缓存本质上是在做一件事情:
用空间换时间。
六、一个简单的缓存示例
假设我们有一个用户查询接口:
GET /user/1001
没有缓存时:
请求
↓
数据库查询
↓
返回用户信息
每次请求都会执行 SQL:
SELECT * FROM users WHERE id = 1001;
如果加入 Redis:
请求
↓
查询 Redis
↓
是否存在?
如果存在:
Redis
↓
直接返回用户数据
这就是缓存命中。
如果不存在:
Redis
↓
没有数据
↓
查询数据库
↓
写入 Redis
↓
返回用户数据
这就是缓存未命中。
对应的伪代码可以写成:
def get_user(user_id):
user = redis.get(f"user:{user_id}")
if user:
return user
user = database.query(user_id)
redis.set(f"user:{user_id}", user)
return user
其中:
if user:
return user
对应的就是一次缓存命中。
七、缓存为什么会未命中?
缓存未命中其实非常正常。
常见原因包括:
第一种情况是数据从来没有被访问过。
例如:
第一次访问 product:1001
缓存自然还没有这个数据。
第二种情况是缓存已经过期。
缓存通常都会设置 TTL,也就是过期时间。
例如:
SET product:1001 data EX 3600
表示缓存一小时之后自动删除。
如果一小时之后再次访问:
GET product:1001
缓存已经不存在,因此会发生 Cache Miss。
第三种情况是缓存被主动删除。
例如商品信息发生变化:
商品价格从 100 元变成 80 元
为了避免缓存中的旧价格继续存在,系统可能主动删除:
DEL product:1001
下一次请求时就会重新查询数据库。
第四种情况是缓存容量不足。
缓存空间通常不是无限的。
当 Redis、CPU Cache 或其他缓存达到容量上限时,系统可能根据某种淘汰策略删除部分数据。
例如:
LRU
LFU
FIFO
被淘汰的数据再次被访问时,也会产生缓存未命中。
八、缓存命中率是不是越高越好?
通常来说,较高的缓存命中率意味着缓存正在发挥较好的效果。
但并不是所有系统都应该盲目追求接近 100% 的命中率。
例如某些数据访问本身就非常随机:
用户第一次访问的数据很多
相同数据重复访问较少
那么缓存价值可能就没有那么高。
另外,如果为了提高缓存命中率而保存大量几乎不会再次访问的数据,也会消耗大量内存。
因此真正应该关注的是:
缓存成本
+
命中率
+
数据更新频率
+
后端查询成本
+
数据一致性要求
需要综合考虑,而不是单纯追求一个很高的百分比。
九、生活中的缓存命中
缓存其实并不是一个很难理解的概念。
可以用一个生活中的例子来理解。
假设你办公时经常需要使用一本资料。
如果每次使用都去公司仓库拿:
需要资料
↓
去仓库
↓
找到资料
↓
拿回来使用
非常浪费时间。
于是你把这本资料放在自己的桌子上。
下一次使用:
需要资料
↓
桌子上有
↓
直接使用
这就是一次“缓存命中”。
如果桌子上没有:
需要资料
↓
桌子上没有
↓
去仓库寻找
这就是一次“缓存未命中”。
在这个例子中:
桌面 = 缓存
仓库 = 数据库
找到了 = Cache Hit
没找到 = Cache Miss
这就是缓存机制最核心的思想。
十、总结
缓存命中本质上是一个非常简单的概念:
当系统请求某个数据时,如果这个数据已经存在于缓存中,就叫做缓存命中(Cache Hit)。
如果缓存中不存在,则叫:
缓存未命中(Cache Miss)。
缓存系统通常遵循下面的流程:
请求数据
↓
检查缓存
↓
┌───────────────┐
│ │
有数据 没数据
│ │
Cache Hit Cache Miss
│ │
直接返回 查询数据库
↓
写入缓存
↓
返回数据
而衡量缓存效果的重要指标之一就是缓存命中率:
缓存命中率
=
缓存命中次数
÷
总请求次数
× 100%
理解了缓存命中、缓存未命中和缓存命中率,也就基本理解了缓存系统最核心的工作机制。
从浏览器缓存、CDN,到 Redis、数据库缓存,再到 CPU Cache,本质上都在解决一个类似的问题:
把经常使用的数据放到距离使用者更近、访问速度更快的位置。
当需要的数据恰好已经在那里时,就是一次缓存命中。
- 点赞
- 收藏
- 关注作者
评论(0)