1. 缓存策略
- 缓存粒度控制:
- 字段级缓存:对于GraphQL查询中的每个字段,可以单独进行缓存。例如,如果一个查询返回用户信息,其中包括用户名、邮箱和地址,可以对每个字段分别设置缓存。这样,当某个字段更新时,只需要更新该字段的缓存。
- 查询级缓存:针对整个GraphQL查询进行缓存。如果多个客户端频繁发起相同的查询,直接返回缓存结果。但这种方式在数据更新时,需要更复杂的更新机制。
- 缓存更新机制:
- 写后失效:在数据发生变更(如创建、更新、删除)后,立即使相关的缓存失效。例如,当用户信息更新后,删除用户相关查询和字段的缓存。
- 写后更新:数据变更后,不仅使缓存失效,还同时更新缓存中的数据。这种方式可以减少下一次查询时的缓存重建开销,但可能导致数据不一致的短暂时间窗口。
2. 结合Redis实现
- 依赖引入:在Kotlin项目中,首先需要引入Redis相关依赖,如
lettuce-core
。
implementation("io.lettuce:lettuce-core:6.2.2.RELEASE")
import io.lettuce.core.RedisClient
import io.lettuce.core.RedisURI
import io.lettuce.core.api.StatefulRedisConnection
import io.lettuce.core.api.sync.RedisCommands
val redisURI = RedisURI.create("redis://localhost:6379")
val redisClient = RedisClient.create(redisURI)
val connection: StatefulRedisConnection<String, String> = redisClient.connect()
val commands: RedisCommands<String, String> = connection.sync()
fun getGraphQLField(fieldName: String, query: String): String? {
val cacheKey = "graphql:field:$fieldName:$query"
val cachedValue = commands.get(cacheKey)
if (cachedValue != null) {
return cachedValue
}
// 实际查询获取字段值
val actualValue = executeGraphQLFieldQuery(fieldName, query)
commands.setex(cacheKey, 3600, actualValue) // 设置缓存,有效期1小时
return actualValue
}
fun executeGraphQLFieldQuery(fieldName: String, query: String): String {
// 这里实现实际的GraphQL字段查询逻辑
return "..."
}
fun getGraphQLQueryResult(query: String): String? {
val cacheKey = "graphql:query:$query"
val cachedValue = commands.get(cacheKey)
if (cachedValue != null) {
return cachedValue
}
// 实际执行GraphQL查询
val actualResult = executeGraphQLQuery(query)
commands.setex(cacheKey, 3600, actualResult) // 设置缓存,有效期1小时
return actualResult
}
fun executeGraphQLQuery(query: String): String {
// 这里实现实际的GraphQL查询逻辑
return "..."
}
fun invalidateGraphQLFieldCache(fieldName: String, query: String) {
val cacheKey = "graphql:field:$fieldName:$query"
commands.del(cacheKey)
}
fun invalidateGraphQLQueryCache(query: String) {
val cacheKey = "graphql:query:$query"
commands.del(cacheKey)
}
3. 注意事项
- 缓存穿透:恶意查询不存在的数据,导致每次都穿透缓存到数据库查询。可以使用布隆过滤器(Bloom Filter)来提前判断数据是否存在,避免无效查询。
- 缓存雪崩:大量缓存同时过期,导致瞬间大量请求直接打到数据库。可以设置缓存过期时间时,加入一定的随机值,避免集中过期。
- 缓存击穿:高并发下,一个热点数据过期瞬间,大量请求同时访问,导致数据库压力剧增。可以使用互斥锁(如Redis的SETNX命令),只允许一个请求去查询数据库并更新缓存,其他请求等待。