graphql缓存需在应用层或网关层实现:应用层通过规范化查询+变量生成语义化缓存键,仅缓存query操作并绑定上下文;网关层将post伪get化、签名重写url并注入cache-control头;同时配合字段级缓存、依赖式失效、混合ttl及客户端cache-and-network策略,兼顾性能、一致性与体验。

GraphQL统一走POST请求,天然不适用浏览器默认的GET缓存机制,但并不意味着无法做精细化缓存。关键在于把“查询语义”从请求体中提取出来,映射为可缓存、可失效、可共享的键值单元。这种缓存必须落在应用层或网关层,且需兼顾一致性、粒度控制和跨客户端复用。
应用层:基于查询内容生成语义化缓存键
在Laravel、Apollo Server或NestJS等框架中,可在解析前拦截请求体,对GraphQL查询字符串+变量做标准化处理,再生成稳定缓存键。
- 先规范化查询:去除空格、排序变量键名、扁平化嵌套字段(如用
user{id,name,posts{title}}代替带换行缩进的原始格式) - 再哈希生成键:用SHA-256或MD5对规范化后的
query + JSON.stringify(variables)计算摘要,避免键过长 - 区分读写操作:仅对
query类型操作缓存,跳过mutation和subscription - 绑定上下文信息:如用户角色、租户ID、语言区域等,避免缓存污染(例如
tenant_us_user_123_query_profile)
网关层:将POST请求“伪GET化”并注入缓存策略
API网关(如Kong、Traefik、Nginx + Lua)可在转发前重写请求,把GraphQL查询转化为带签名的只读URL,从而启用HTTP缓存基础设施。
- 提取并签名:网关解析POST body,提取
query和variables,拼接后用密钥HMAC-SHA256签名,生成唯一token - 重写为GET:将请求重写为
GET /graphql?_q=xxx&_s=yyy,其中_q是base64编码的查询片段,_s是签名 - 响应头注入:服务端返回时,网关统一添加
Cache-Control: public, max-age=300及Vary: X-User-ID, X-Tenant,支持CDN分级缓存 - 支持条件刷新:客户端可通过
Cache-Control: no-cache头强制绕过缓存,服务端响应ETag便于304协商
缓存粒度与失效协同设计
单纯缓存整条响应容易导致数据陈旧,推荐按字段/对象粒度缓存,并与数据源联动失效。
- 字段级缓存:在resolver中对
user(id: 123)单独缓存,键为user:123;对posts(limit: 10)缓存为posts:limit_10 - 依赖式失效:当
User模型被更新,触发清除所有以user:开头的键;也可用Redis的PUB/SUB广播变更事件 - 混合TTL策略:静态配置类字段设长TTL(如86400秒),用户个人数据设短TTL(如600秒),热点榜单加
stale-while-revalidate兜底 - 避免缓存穿透:对不存在的id(如
user(id: 999999))也缓存空结果(null或{}),设置较短过期时间(如60秒)
客户端配合:提升缓存命中率与体验一致性
服务端缓存需与前端配合,才能发挥最大价值。Apollo Client等库已内置分层缓存能力,可与服务端策略对齐。
- 启用
cache-and-network策略:优先读缓存渲染,再发起网络请求更新,兼顾速度与新鲜度 - 标准化查询结构:避免同一逻辑因字段顺序、别名不同产生多个缓存键,建议用
graphql-codegen自动生成规范查询 - 携带缓存提示:在请求头中添加
X-Cache-Hint: prefer-cache或X-Request-ID,便于网关/服务端做灰度或调试追踪 - 离线友好:结合
apollo-cache-persist将部分只读查询持久化到IndexedDB,实现弱网/离线场景下的可用性降级











