GraphQL-Yoga 响应缓存机制深度解析
概述
在现代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缓存机制
响应缓存插件支持通过ETag
和If-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的性能和用户体验。