当前位置: 首页 > news >正文

前端缓存好还是后端缓存好?缓存方案实例直接用

文章目录

  • 怎么选?
  • 典型分层方案
  • 什么时候偏前端?
  • 什么时候偏后端?
  • 关键实践清单
  • 结论
  • 缓存方案实例
    • 一、目标与分工
    • 二、架构与关键路径
    • 三、Nginx 网关缓存(可直接用)
    • 四、FastAPI 应用层缓存(可复制运行)
      • 1) 依赖与初始化
      • 2) 缓存键与装饰器(含防击穿锁、随机 TTL 抗雪崩、标签失效)
      • 3) 示例接口
      • 4) 主动失效(按标签/按键)
      • 5) 负缓存/穿透防护(可选)
    • 五、Redis 策略与配置
    • 六、Docker Compose(开箱即用)
    • 七、前端/浏览器层建议(可选做)
    • 八、测试用例(马上验证)
    • 九、监控与告警
    • 十、常见坑处理
    • 十一、选型建议

都重要。前端缓存负责“离用户最近的静态与短期数据”,后端缓存负责“跨用户复用与复杂查询的结果”。最佳实践通常是分层缓存(CDN/浏览器 → 网关/服务端 → 数据库),各司其职。

怎么选?

维度前端缓存(浏览器/Service Worker/CDN 边缘)后端缓存(反向代理/应用层/Redis/数据库缓存)
适用数据公共静态资源、低敏感公开数据、列表页骨架等计算密集/查询昂贵结果、个性化但可控的数据片段
粒度资源级(HTML/CSS/JS/图片),也可做接口响应片段业务对象/查询结果/页面片段
命中范围强(同一资源所有用户受益,尤其 CDN)强(同一查询或对象多用户受益)
一致性难做强一致;适合最终一致 + 短 TTL更易控制一致性与失效策略
安全/隐私需谨慎(避免把私有数据缓存到共享层)可基于用户维度安全隔离(如按 user_id 分桶)
失效控制依赖 HTTP 缓存头、URL 版本号、SW 逻辑由应用主动失效,粒度更细(键精确删除)
成本/复杂度较低(合理设置 Cache-Control/ETag)中等(键设计、淘汰策略、回源风暴治理)

典型分层方案

  1. CDN/浏览器层(首选减载):

    • 静态资源用文件指纹+Cache-Control: max-age=31536000, immutable
    • 接口数据若可公开,用stale-while-revalidate实现“先快后准”。
    • PWA/Service Worker 可缓存离线壳与常用接口响应(注意私有数据隔离)。
  2. 边缘/网关层(如 Nginx/反向代理)

    • 公开 GET 接口设置短 TTL(5–120s)吸收突刺流量。
    • 对热门页面做页面片段缓存(ESI/SSI)或整页短缓存。
  3. 应用/数据层(如 Redis)

    • 读多写少的聚合查询、排行榜、配置字典等做对象/查询结果缓存。
    • 使用写穿/写回/写旁路策略与主动失效(按主键、按业务域批量)。
    • 缓存击穿/穿透/雪崩:热点键互斥重建、布隆过滤、TTL 分散、预热。

什么时候偏前端?

  • 静态资源、公共接口、SEO 友好的落地页、对“首屏 TTFB/FCP”极敏感的场景。
  • 全球流量、带宽贵:CDN 命中能显著降本提速。

什么时候偏后端?

  • 用户私有/强一致要求高(订单、余额、权限)。
  • 复杂聚合查询或昂贵计算(报表、推荐结果)。
  • 需要精细失效(如某商品更新只影响相关键)。

关键实践清单

  • HTTP 缓存头Cache-Control(含 s-maxagestale-while-revalidate)、ETag/If-None-MatchLast-Modified
  • 缓存键设计:包含影响结果的所有维度(语言/地区/版本/用户/权限/查询条件)。
  • TTL 策略:公共数据长 TTL + 版本哈希;动态数据短 TTL + 主动失效。
  • 一致性:对强一致读,绕过缓存或采用短 TTL + 回源校验;对最终一致,接受微小延迟换取性能。
  • 风暴治理:单飞(singleflight)/互斥重建、限速、降级兜底。
  • 监控与命中率:命中率、回源量、P95/P99、重建耗时、错误率。

结论

  • 静态/公共 → 前端(CDN/浏览器)长缓存 + 版本号。
  • 动态/昂贵/需控制失效 → 后端(Redis/代理)短缓存 + 主动失效。
  • 两者组合:前端“兜头部延迟”,后端“护数据库压力”。如果只能选一个,先上后端缓存更稳;有条件再加前端层吃满提速与降本。

缓存方案实例

Nginx + FastAPI + Redis 的可落地分层缓存方案(前端/CDN、网关、应用、数据层)。内容含:架构、Nginx 配置、FastAPI 代码(含装饰器/锁/失效)、Redis 策略、Docker Compose、测试与监控清单。


一、目标与分工

  • CDN/浏览器层:静态资源长缓存 + 公开接口短缓存(SWR)。
  • Nginx(边缘/网关):公开 GET 接口短 TTL、吸收突刺、记录命中率。
  • 应用层(FastAPI):昂贵查询对象/结果缓存、精细失效(按键/按标签)、防击穿/雪崩。
  • Redis:高性能 KV 缓存、互斥锁(防风暴)、可选布隆/短期负缓存。

二、架构与关键路径

Browser/PWA ──> CDN(可选) ──> Nginx(反向代理+proxy_cache)└──> FastAPI(App缓存装饰器/主动失效/ETag/SWR)└──> Redis(对象/查询结果缓存、锁、标签集)└──> DB/外部服务

三、Nginx 网关缓存(可直接用)

作用:公开 GET 接口短缓存(例如 30~120s),并输出命中状态;静态资源用指纹+长缓存。

# /etc/nginx/conf.d/cache.conf
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=apicache:100mmax_size=5g inactive=10m use_temp_path=off;map $request_method $bypass_non_get {default 1;GET     0;
}# 对登录态/私有请求绕过缓存(有 Authorization/ Cookie)
map $http_authorization $bypass_auth { default 1; "" 0; }
map $http_cookie        $bypass_cookie { default 1; "" 0; }server {listen 80;server_name _;# 静态资源(带文件指纹)location ~* \.(?:css|js|png|jpg|jpeg|gif|svg|woff2?)$ {root /var/www/html;add_header Cache-Control "public, max-age=31536000, immutable";try_files $uri =404;}# 公开接口(示例:/api/public/**)location ^~ /api/public/ {proxy_pass         http://app:8000;proxy_set_header   Host $host;proxy_set_header   X-Forwarded-For $remote_addr;# 缓存键:考虑查询串proxy_cache_key "$scheme$host$request_uri";# 仅 GET 且无鉴权/无 Cookie 才缓存set $bypass 0;if ($bypass_non_get) { set $bypass 1; }if ($bypass_auth)    { set $bypass 1; }if ($bypass_cookie)  { set $bypass 1; }proxy_no_cache       $bypass;proxy_cache_bypass   $bypass;proxy_cache          apicache;proxy_cache_valid    200  60s;      # 命中 60sproxy_cache_valid    301 302 10m;proxy_cache_valid    any  30s;proxy_ignore_headers Set-Cookie;add_header X-Cache-Status $upstream_cache_status;add_header Cache-Control "public, s-maxage=60, stale-while-revalidate=120";proxy_headers_hash_max_size 512;proxy_headers_hash_bucket_size 128;# 回源慢时使用陈旧缓存proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504 updating;}# 私有/敏感接口(默认不缓存)location /api/ {proxy_pass       http://app:8000;proxy_set_header Host $host;add_header Cache-Control "no-store";}
}

监控命中率$upstream_cache_status 会显示 HIT/MISS/BYPASS/EXPIRED/STALE


四、FastAPI 应用层缓存(可复制运行)

1) 依赖与初始化

pip install fastapi uvicorn redis[async] orjson python-multipart
# app/main.py
import asyncio, hashlib, json, time
from typing import Any, Callable, Dict, Optional
from fastapi import FastAPI, Request, Depends, HTTPException
from fastapi.responses import ORJSONResponse
from redis import asyncio as aioredisapp = FastAPI(default_response_class=ORJSONResponse)
redis = aioredis.from_url("redis://redis:6379/0", encoding="utf-8", decode_responses=True)APP_CACHE_TAG_PREFIX = "tag:"
APP_CACHE_LOCK_PREFIX = "lock:"
APP_CACHE_KEY_PREFIX = "resp:"

2) 缓存键与装饰器(含防击穿锁、随机 TTL 抗雪崩、标签失效)

def _stable_key(parts: Dict[str, Any]) -> str:raw = json.dumps(parts, sort_keys=True, separators=(",", ":"))h = hashlib.sha256(raw.encode()).hexdigest()[:32]return f"{APP_CACHE_KEY_PREFIX}{h}"async def _with_mutex_lock(key: str, ttl: int = 10) -> bool:# SETNX + EX,拿到锁返回 True,拿不到 Falsereturn await redis.set(f"{APP_CACHE_LOCK_PREFIX}{key}", "1", ex=ttl, nx=True)async def _unlock(key: str):await redis.delete(f"{APP_CACHE_LOCK_PREFIX}{key}")def cacheable(ttl: int = 60, tags: Optional[list[str]] = None, vary_user: bool = False):"""- ttl: 秒;会自动加入随机抖动 ±20% 防雪崩- tags: 业务标签(如 ["product:123","list:home"]),用于批量失效- vary_user: 是否按用户维度缓存(私有但可控)"""def decorator(func: Callable):async def wrapper(request: Request, *args, **kwargs):user_id = request.headers.get("X-User-Id") if vary_user else Nonekey_parts = {"path": request.url.path,"query": dict(request.query_params),"user": user_id,"ver": request.headers.get("X-App-Version", "v1"),  # 版本/地域/语言等可加入}key = _stable_key(key_parts)# 读缓存cached = await redis.get(key)if cached:payload = json.loads(cached)return ORJSONResponse(payload, headers={"Cache-Control": f"public, max-age=30, stale-while-revalidate=120","ETag": payload.get("_etag",""),})# 防击穿:仅一个并发去重建if await _with_mutex_lock(key):try:data: Dict[str, Any] = await func(request, *args, **kwargs)# ETag(弱校验可用 hash)etag = hashlib.md5(orjson.dumps(data)).hexdigest()data["_etag"] = etag# TTL 抖动jitter = max(1, int(ttl * 0.2))real_ttl = ttl + (int(time.time()) % (2*jitter) - jitter)await redis.set(key, json.dumps(data), ex=max(1, real_ttl))# 标签索引(tag -> set(keys))if tags:for tag in tags:await redis.sadd(f"{APP_CACHE_TAG_PREFIX}{tag}", key)return ORJSONResponse(data, headers={"Cache-Control": f"public, max-age=30, stale-while-revalidate=120","ETag": etag,})finally:await _unlock(key)else:# 其他并发短等,或返回兜底for _ in range(20):await asyncio.sleep(0.05)cached2 = await redis.get(key)if cached2:payload = json.loads(cached2)return ORJSONResponse(payload, headers={"Cache-Control": f"public, max-age=30, stale-while-revalidate=120","ETag": payload.get("_etag",""),})# 兜底直查data = await func(request, *args, **kwargs)return ORJSONResponse(data, headers={"Cache-Control": "no-store"})return wrapperreturn decorator

3) 示例接口

@app.get("/api/public/products")
@cacheable(ttl=60, tags=["products:list"])
async def list_products(request: Request):# TODO: 实际查询DB/外部服务(这里返回假数据)return {"items": [{"id": 1, "name": "A"}, {"id": 2, "name":"B"}]}@app.get("/api/public/product/{pid}")
@cacheable(ttl=120, tags=lambda req,pid: [f"product:{pid}"])  # 也可用固定列表
async def get_product(request: Request, pid: int):return {"id": pid, "name": f"product-{pid}", "price": 99}

可选:把 tags 支持成 Union[List[str], Callable],上例里我放了一个思路,你也可以直接写固定列表:tags=[f"product:{pid}"](运行时构造)。

4) 主动失效(按标签/按键)

async def invalidate_by_tags(tags: list[str]):for tag in tags:tkey = f"{APP_CACHE_TAG_PREFIX}{tag}"members = await redis.smembers(tkey)if members:await redis.delete(*members)await redis.delete(tkey)@app.post("/admin/product/{pid}/update")
async def update_product(pid: int):# 1) 执行DB更新...# 2) 失效相关缓存await invalidate_by_tags([f"product:{pid}", "products:list"])return {"ok": True}

5) 负缓存/穿透防护(可选)

  • 查询不存在对象时,缓存一个**短 TTL(10~30s)**的“空标记”(如 {"_none":1});
  • 再次请求直接返回 404 或空,避免反复打 DB。

五、Redis 策略与配置

  • maxmemory & 策略:根据机器内存设置 maxmemory,使用 allkeys-lruvolatile-lru
  • 键设计:包含影响结果的所有维度(语言/地区/版本/用户/权限/分页/筛选)。
  • TTL 分散:应用层已做 ±20% 抖动,避免雪崩。
  • 热点互斥:上面装饰器里的 SETNX 锁即可。
  • 标签索引tag:<biz>Set(key1,key2,...),方便批量失效。

redis.conf 关键项(示例):

maxmemory 1gb
maxmemory-policy allkeys-lru

六、Docker Compose(开箱即用)

# docker-compose.yml
version: "3.8"
services:redis:image: redis:7-alpinecommand: ["redis-server","--appendonly","yes","--maxmemory","1gb","--maxmemory-policy","allkeys-lru"]ports: ["6379:6379"]volumes: ["./data/redis:/data"]app:image: python:3.11-slimworking_dir: /appvolumes: ["./app:/app"]command: bash -lc "pip install fastapi uvicorn[standard] redis[async] orjson && uvicorn main:app --host 0.0.0.0 --port 8000"depends_on: [redis]ports: ["8000:8000"]nginx:image: nginx:1.27-alpinevolumes:- ./nginx/cache.conf:/etc/nginx/conf.d/default.conf:ro- ./static:/var/www/html:ro- nginx_cache:/var/cache/nginxdepends_on: [app]ports: ["80:80"]volumes:nginx_cache:

七、前端/浏览器层建议(可选做)

  • 静态资源文件指纹app.0a1b2c.js)+ immutable
  • PWA/Service Worker:对公开接口stale-while-revalidate(注意不要缓存私有数据)。
  • HTML 不建议长缓存;可用短 TTL + ETag。

八、测试用例(马上验证)

# 1) 静态资源头部
curl -I http://localhost/app.0a1b2c.js# 2) 公开接口(观察 X-Cache-Status)
curl -i "http://localhost/api/public/products"
curl -i "http://localhost/api/public/products"# 3) 私有请求绕过缓存
curl -i -H "Authorization: Bearer xxx" "http://localhost/api/public/products"# 4) 更新后主动失效
curl -i -X POST "http://localhost/admin/product/2/update"
curl -i "http://localhost/api/public/product/2"

九、监控与告警

  • Nginx:开启日志字段(已添加 X-Cache-Status),统计命中率/回源率/P95。
  • 应用:埋点 cache.hit/miss/rebuild/lock_wait;记录重建耗时。
  • Redis:监控 used_memory, keyspace_hits/misses, evicted_keys, expired_keys
  • 告警:命中率异常下降、锁等待过长、回源突增、Redis 内存接近上限等。

十、常见坑处理

  • 鉴权/私有数据绝不走共享缓存:已通过 Authorization/Cookie 绕过。
  • 一致性:强一致读(如余额/订单)→ no-store 或非常短 TTL + 读校验。
  • 大对象:避免把超大 JSON 直接放缓存;可按片段/分页缓存。
  • 缓存键遗漏维度:变更语言/版本/过滤项没进键 → 脏命中。务必统一键生成。

十一、选型建议

  • 静态资源:前端指纹 + Nginx 长缓存。
  • 公开 GET 列表/详情:Nginx 60s + 应用 60~120s(有标签失效)。
  • 昂贵聚合/报表:只做应用层缓存(Redis),TTL 120~300s + 主动失效。
  • 私有/强一致:默认不缓存或极短 TTL,并由后端控制。
http://www.dtcms.com/a/405824.html

相关文章:

  • 小九源码-springboot050-基于spring boot的苏蔚家校互联管理系统
  • 陕西西安网站建设公司大学生网页设计
  • Redis 面试常考问题(高频核心版)
  • 开发时如何彻底禁用浏览器表单自动填充缓存
  • 零基础新手小白快速了解掌握服务集群与自动化运维(七)Nginx模块--Nginx反向代理与缓存功能(二)
  • 【项目实战 Day7】springboot + vue 苍穹外卖系统(微信小程序 + 微信登录模块 完结)
  • python+springboot+uniapp基于微信小程序的停车场管理系统 弹窗提示和车牌识别
  • -bash: ssh-copy-id: command not found的两种解决方法
  • 电商网站新闻怎么做即速应用小程序官网
  • 上海网站建设接单互联网+大学生创新创业项目官网
  • 我是如何用Claude Code打造通用AI Agent的
  • 使用Nexus Repository Manager搭建私有自建 pip 源
  • 9.二叉树(上)
  • DNS 服务器与 DHCP 服务器详解及配置指南
  • 中国建设银行官网站招聘频道如何不花钱做网站
  • 恢复快照(需先暂停 / 关闭虚拟机,避免数据不一致)
  • 九、OpenCV中视频的录制
  • ASP.NET网站建设实战企业网页模板图片
  • 分布式机器人多机协同巡检系统设计
  • 滑动窗口题目:统计「优美子数组」
  • list 迭代器:C++ 容器封装的 “行为统一” 艺术
  • 专题:2025年AI Agent智能体行业洞察报告|附110+份报告PDF、数据仪表盘汇总下载
  • docker部署使用
  • 信息安全基础知识:05物理与环境安全
  • 【双机位A卷】华为OD笔试之【队列】双机位A-篮球游戏【Py/Java/C++/C/JS/Go六种语言】【欧弟算法】全网注释最详细分类最全的华子OD真题题解
  • 考研复习-线性代数-第二章-矩阵
  • wordpress主题样式表绵阳做seo网站公司
  • Answer+cpolar:企业知识共享的远程协作方案
  • 在SSL证书是有效的前提下,依旧显示“资源不安全
  • 鸿蒙NEXT系统Picker全解析:安全高效的用户资源访问之道