它们是一套贯穿浏览器、网络传输、应用逻辑和数据存储层的临时副本存储体系,主要分为浏览器缓存、CDN缓存、反向代理缓存(如Nginx/Varnish)、应用层缓存(如Redis/Memcached)和数据库缓存五大类。
服务器缓存技术有哪些常见类型
很多人一提到缓存就只想到Redis,实际上服务器缓存是一个分层协作的系统,我习惯把它们想象成不同级别的”快递驿站”:离用户越近的驿站,取货越快,但能存的东西越少、越容易过期;离仓库越近的驿站,存的东西越全,但每次往返的时间成本更高。
浏览器缓存:最容易被忽略的第一道关卡
浏览器缓存是用户电脑上的临时存储空间,当你第一次访问一个网站,浏览器会把静态资源(图片、CSS、JS文件)按照服务器返回的Cache-Control和Expires头信息存到本地,第二次访问时,浏览器直接读取本地副本,连网络请求都不用发出。
实际操作中,这个配置对网站响应速度的影响是立竿见影的,对于一个图片较多的页面,合理设置浏览器缓存可以让回访用户的加载时间缩短数倍,常见的做法是在Nginx中配置:
location ~ .(jpg|jpeg|png|gif|ico|css|js)$ {
expires 30d;
add_header Cache-Control "public, no-transform";
}
反向代理缓存:服务器门口的快速通道
反向代理缓存部署在Web服务器前面,比如Nginx FastCGI Cache、Squid、Varnish,当第一个用户请求某个页面,反向代理把生成的完整页面存一份;第二个用户请求相同页面时,直接从代理服务器返回,后端PHP或Java应用完全不用重新执行。
这里的核心价值在于缓存整个页面输出,如果你的网站动态内容多但变化不频繁(比如新闻列表、产品展示页),反向代理缓存的命中率可以达到相当高的比例,后端服务器的负载压力能减轻一个数量级,Nginx配置FastCGI Cache的参考步骤:
fastcgi_cache_path /var/cache/nginx levels=1:2 keys_zone=mycache:100m inactive=60m;
server {
location ~ .php$ {
fastcgi_cache mycache;
fastcgi_cache_valid 200 60m;
include fastcgi_params;
}
}
应用层缓存:Redis与Memcached的用武之地
当页面的一部分是动态的、但又不适合请求数据库时,应用层缓存就派上用场了,Redis和Memcached把热点数据放在内存中,读写速度是磁盘的几十倍,常见的应用场景包括用户登录Session、购物车、热点新闻列表、排行榜等。
这类缓存的特点是需要程序员在代码里主动读写,属于”手工精细化运营”的模式,比如查询商品信息时,先查Redis,如果Redis没有,再查MySQL,然后把结果写回Redis。
数据库缓存与查询缓存
MySQL自带的查询缓存和InnoDB Buffer Pool,以及PostgreSQL的shared_buffers,都属于数据库层的缓存机制,数据库缓存的意义在于减少磁盘I/O,InnoDB Buffer Pool把频繁访问的数据页直接放在内存中,
相当一部分查询可以完全绕过磁盘读取。
h3>服务器缓存和CDN有什么区别
两者经常被混淆,但工作位置完全不同,CDN是部署在全球各地的边缘节点,缓存的是距离用户最近的副本,假设你在北京访问一个部署在上海的网站,CDN会把图片和静态文件提前放到北京或天津的节点上,用户直接就近获取。
服务器缓存则是在源站(你自己的服务器)上做文章,优化的是从服务器到CDN或直接到用户这一段的效率。
服务器缓存和CDN有什么区别,如何配合使用
这两种技术不是替代关系,而是协作关系,理解它们的分工有助于设计出合理的缓存体系。
CDN处理的是跨地域的网络延迟,能把静态资源的加载距离从上千公里缩短到几十公里,而服务器缓存处理的是源站的计算压力,让后端应用少干活、快速响应,行业共识认为,一个高可用网站的标准缓存架构应该是:
- 第一层:CDN承接全国乃至全球的静态资源分发
- 第二层:Nginx反向代理缓存整个页面
- 第三层:Redis缓存热点业务数据
- 第四层:数据库自身缓存(InnoDB Buffer Pool)
当你发现用户反馈”图片加载慢”时,优先考虑CDN配置;当你发现”页面能打开但数据加载慢”或者”服务器CPU经常跑满”时,优先检查服务器缓存配置。
Redis和Memcached怎么选更适合你的场景
这是很多团队纠结的问题,其实选型标准并不复杂,Memcached是个纯粹的内存缓存工具,初始化时就把内存分配好,键值对过期后直接淘汰,它不支持持久化,重启后数据清空,但好处是简单、性能开销极低,适合存储一次性验证码、临时会话数据这类允许丢失的内容。
Redis则是一个全能型选手,支持字符串、列表、集合、有序集合、哈希等丰富的数据结构,还支持数据持久化和主从复制,如果你的业务需要把排行榜、关注关系、最新文章列表都塞进缓存,Redis用起来更顺手,业界普遍认为Redis可以通过RDB或AOF两种方式避免重启后缓存”冷启动”而导致的缓存雪崩。
| 对比项 | Memcached | Redis |
|---|---|---|
| 数据类型 | 仅字符串 | 字符串、哈希、列表、集合等 |
| 持久化 | 不支持 | RDB/AOF |
| 内存淘汰策略 | LRU | 多种策略(volatile-lru、allkeys-lru等) |
| 适用场景 | 简单键值缓存 | 复杂数据结构、需要持久化的缓存 |
| 单线程模型 | 多线程 | 单线程(但性能依然极高) |
实战中的选择逻辑很简单:数据能被删掉就选Memcached,数据丢了会影响业务就选Redis。
缓存更新策略与常见失效场景
缓存不是配好就完事的,过期策略处理不好反而会引发更严重的故障。
缓存穿透、击穿与雪崩的具体表现
- 缓存穿透:用户反复查询数据库中根本不存在的数据,每次请求都得打到数据库,解决办法是缓存空值,或者用布隆过滤器拦截,这是业内专家指出的最常用手段。
- 缓存击穿:某个热点Key在过期瞬间,大量并发请求同时涌向数据库,解决办法是设置热点数据永不过期,或者使用互斥锁(Mutex)。
- 缓存雪崩:大量Key在同一时间段集中过期,导致数据库压力突然暴增,解决办法是给过期时间加随机偏移量,比如
base + random(0, 300)秒,让过期时间错开。
主动更新与失效策略如何执行
处理缓存数据与数据库数据的一致性,没有绝对完美的方案,只有最适合业务场景的做法。
常见的三种模式是:
- Cache Aside:读操作先查缓存,未命中则查数据库并回填;写操作先更新数据库,然后删除缓存。
- Read Through:缓存层自己维护数据加载逻辑,应用层只与缓存交互。
- Write Back:数据只写入缓存,由后台任务异步批量写回数据库,适合点赞数、访问量这种允许丢一点点的场景。
实际业务中,Cache Aside是最直观可落地的方案,因为它的逻辑简单、容易排查问题,但要注意更新数据库和删除缓存的操作要保证最终一致,常见做法是通过消息队列异步重试。
缓存命中率如何观察
上线缓存后必须盯命中了命率,Redis里可以用INFO stats命令查看keyspace_hits和keyspace_misses两个字段,算一下命中率:hits / (hits + misses),Nginx反向代理缓存则可以在日志中记录$upstream_cache_status,观察HIT和MISS的比例。
如果命中率长期偏低,说明缓存的数据不是用户真正频繁访问的数据,这时候要检查Key的设计是否合理,比如把用户ID作为Key是不是粒度太细了,导致每个Key只被一个人访问到。
实际部署步骤与操作路径参考
以一台生产环境的CentOS服务器为例,假设Web服务是Nginx + PHP-FPM,数据库是MySQL,需要做一套常规缓存的完整部署,步骤大致如下。
第一步:配置Nginx FastCGI Cache,把整体页面缓存住,在nginx.conf的http块中定义缓存路径,在server块中开启缓存并设置过期时间,站点目录下运行curl -I http://你的域名,看响应头中是否有X-Cache: HIT,有就说明缓存页面生效了。
第二步:启动Redis并验证连接,安装Redis后,在php代码中通过Predis或PhpRedis扩展连接测试,命令行里先用redis-cli ping确认返回PONG,再执行redis-cli set test_key "hello"验证写入。
第三步:修改业务代码,接入缓存逻辑,拿商品详情页举例,代码逻辑示意如下:
function getProduct($id) {
$cacheKey = 'product:' . $id;
$data = $redis->get($cacheKey);
if (!$data) {
$data = $db->query("SELECT FROM products WHERE id = $id")->fetch();
if ($data) {
$redis->setex($cacheKey, 600, json_encode($data));
}
}
return json_decode($data, true);
}
第四步:监控与调优,观察Redis的maxmemory设置,切记别让内存用尽导致OOM,同时检查info stats中的evicted_keys,如果这个数字持续增长,说明内存不够用了,要么扩容,要么优化数据的TTL。
网站访问慢的时候优先排查哪个缓存层
这是一个经常在服务器运维群里被问到的问题,排查思路应该从用户端往服务器端推:
先用浏览器的开发者工具看Network面板,如果某个资源显示from disk cache或者200 OK (from memory cache),说明浏览器层没问题,然后看资源的响应头,确认是否命中CDN节点(一般会有X-Cache: HIT或Via头字段),再通过curl -I请求源站地址,携带Host头,查看Nginx返回的X-Cache状态,判断反向代理是否生效。
如果以上都正常但性能仍不理想,最后才深入排查Redis的慢查询日志和MySQL的慢查询日志,实践中,绝大多数性能瓶颈集中在数据库查询优化和缓存Key设计,而不是缓存中间件本身的问题。
Q&A:服务器缓存技术有哪些常见问题
配置了服务器缓存之后,用户看到的内容不是最新的怎么办?
这大概率是因为缓存过期时间设置过长,或者更新数据库后没有主动清理缓存,解决办法是对编辑后台的保存接口做额外处理:每当管理员修改内容,立刻调用$redis->del($cacheKey)删除对应缓存,让下一个用户请求时重新回填,如果用的是Nginx FastCGI Cache,则可以用purge模块或者通过第三方插件(如ngx_cache_purge)指定URL进行清理。
Redis的内存淘汰策略应该怎么配置?
在redis.conf中,maxmemory-policy选项决定了内存达到上限后的行为,业务数据基本都是热点缓存、允许被覆盖时,使用allkeys-lru是最省心的,Linux中配置路径是vim /etc/redis/redis.conf修改后重启生效。
为什么用了Redis缓存,数据库压力还是居高不下?
首先检查缓存是否真的命中了,看keyspace_hits与keyspace_misses比例,如果Miss比例较高,用户请求的Key可能都是冷数据,比如带有当前时间戳的搜索词,这类数据本身就难以被缓存,其次要看代码逻辑中是否真的先查了缓存,有的开发人员因为上线前没有做压测,导致部分高频接口仍然直连数据库,最后检查缓存的是否是完整数据,如果利用缓存保存的是获取到的数据库连接信息而非查询结果,那对数据库的压力不会减少。
首发原创文章,作者:王坚,如若转载,请注明出处:https://idctop.com/article/758453.html





