首页
/ GraphQL-Yoga 响应缓存机制深度解析

GraphQL-Yoga 响应缓存机制深度解析

2025-07-07 01:59:32作者:胡唯隽

概述

在现代Web应用开发中,性能优化是一个永恒的话题。GraphQL-Yoga作为一款功能强大的GraphQL服务器实现,提供了响应缓存(Response Caching)功能来显著提升GraphQL API的性能表现。本文将深入探讨GraphQL-Yoga的响应缓存机制,帮助开发者理解并有效利用这一功能。

响应缓存基础

响应缓存是一种通过缓存GraphQL查询操作结果来减少服务器负载的技术。当接收到具有相同变量值的GraphQL查询操作时,系统会直接从缓存返回响应,而不是重新执行查询。

快速入门

要使用响应缓存功能,首先需要安装相关插件包:

npm install @graphql-yoga/plugin-response-cache

下面是一个基础示例,展示了一个慢查询字段(Query.slow)的缓存实现:

import { createServer } from 'node:http'
import { setTimeout as setTimeout$ } from 'node:timers/promises'
import { createSchema, createYoga } from 'graphql-yoga'
import { useResponseCache } from '@graphql-yoga/plugin-response-cache'

const yoga = createYoga({
  schema: createSchema({
    typeDefs: /* GraphQL */ `
      type Query {
        slow: String
      }
    `,
    resolvers: {
      Query: {
        slow: async () => {
          await setTimeout$(5000)
          return 'I am slow.'
        }
      }
    }
  }),
  plugins: [
    useResponseCache({
      // 全局缓存
      session: () => null
    })
  ]
})

const server = createServer(yoga)
server.listen(4000, () => {
  console.info('Server is running on http://localhost:4000/graphql')
})

首次查询需要5秒完成,而第二次查询由于缓存命中,仅需几毫秒即可返回结果。

基于会话的缓存

对于需要根据用户会话返回不同数据的API,可以使用session选项实现基于会话的缓存。通常,会话信息可以从HTTP头部获取,例如包含在访问令牌中的用户ID。

会话配置示例

useResponseCache({
  // 基于认证头部进行缓存
  session: request => request.headers.get('authentication')
})

或者从GraphQL上下文中获取会话信息:

useResponseCache({
  // 基于上下文中JWT令牌进行缓存
  session: ({ context }) => context.jwt.token
})

强制会话缓存

某些情况下,类型或字段应该仅在存在会话时才被缓存。可以通过scope选项实现:

useResponseCache({
  session: request => request.headers.get('authentication'),
  scopePerSchemaCoordinate: {
    'Query.me': 'PRIVATE', // 字段级别
    User: 'PRIVATE',       // 类型级别
  }
})

也可以使用@cacheControl指令实现相同功能:

type Query {
  me: User @cacheControl(scope: PRIVATE)
}

type User @cacheControl(scope: PRIVATE) {
  #...
}

缓存生存时间(TTL)

可以为缓存操作设置生存时间,可以全局设置,也可以基于模式坐标或对象类型设置。

通过插件配置

useResponseCache({
  session: () => null,
  ttl: 2_000, // 默认缓存2秒
  ttlPerType: {
    User: 500 // User类型缓存500毫秒
  },
  ttlPerSchemaCoordinate: {
    'Query.lazy': 10_000 // Query.lazy字段缓存10秒
  }
})

使用@cacheControl指令

type Query {
  lazy: Something @cacheControl(maxAge: 10000)
}

type User @cacheControl(maxAge: 500) {
  #...
}

通过变更操作自动失效

当执行变更操作时,包含变更结果中类型实体的所有缓存查询结果将自动失效。例如:

mutation UpdateUser {
  updateUser(id: 1, newName: "John") {
    __typename
    id
    name
  }
}

执行此变更后,所有包含ID为1的User类型的缓存查询结果都将失效。

可以通过设置invalidateViaMutation: false禁用此行为。

手动缓存失效

可以手动使类型或特定类型实例的缓存失效:

import { createInMemoryCache, useResponseCache } from '@graphql-yoga/plugin-response-cache'

const cache = createInMemoryCache()

useResponseCache({
  session: () => null,
  cache
})

// 使特定类型的所有查询结果失效
cache.invalidate([{ typename: 'User' }])

// 使特定实体的查询结果失效
cache.invalidate([{ typename: 'User', id: '1' }])

外部缓存实现

默认情况下,响应缓存将所有缓存结果存储在内存中。如果需要多个服务器实例共享缓存,可以使用Redis实现:

import { useResponseCache } from '@graphql-yoga/plugin-response-cache'
import { createRedisCache } from '@envelop/response-cache-redis'
import { Redis } from 'ioredis'

const redis = new Redis('redis://:1234567890@my-redis-db.example.com:30652')

const cache = createRedisCache({ redis })

useResponseCache({
  session: () => null,
  cache
})

HTTP缓存机制

响应缓存插件支持通过ETagIf-None-Match头部实现HTTP缓存。当客户端发送带有If-None-Match头部的请求时,如果ETag值匹配,服务器将返回304 Not Modified状态码而不返回内容,从而减少服务器负载。

示例

首次请求:

curl -H 'Content-Type: application/json' \
  "http://localhost:4000/graphql?query={me{id name}}" -v

后续请求使用缓存:

curl -H "Accept: application/json" \
  -H "If-None-Match: [ETag值]" \
  -H "If-Modified-Since: [时间戳]" \
  "http://localhost:4000/graphql?query=\{me\{id,name\}\}" -v

总结

GraphQL-Yoga的响应缓存功能提供了灵活而强大的缓存策略,从简单的全局缓存到复杂的基于会话的缓存,再到细粒度的TTL控制和失效机制。合理利用这些功能可以显著提升GraphQL API的性能和用户体验。