Apollo Server v4 Redis缓存异常:含Duration字段时缓存失效求助
核心原因分析
出现这个问题的大概率是以下几个原因之一:
1. Duration字段的解析器缓存策略冲突
如果Duration类型或其下的hours/minutes等字段有单独的解析器,且该解析器未设置缓存策略或设置了no-cache,会导致整个查询的缓存被禁用。Apollo Server的缓存控制是组合式的,只要查询链中有一个字段的缓存策略为no-cache,整个查询结果就不会被缓存。
2. Float类型的序列化/精度问题
Duration中的hours/minutes等字段是Float类型,如果这些值是通过时间差计算生成的,可能会出现浮点精度误差(比如2.5和2.5000000001)。Apollo生成缓存键或序列化结果时,会把这些值纳入计算,微小的精度差异会导致每次生成的缓存键不同,无法命中缓存;或者Redis存储时序列化结果不一致,导致缓存失效。
3. 重复字段导致的Schema解析异常
你的Data类型中重复定义了u_clientpriority: String两次,虽然这不会直接导致缓存问题,但可能引发Schema解析的潜在异常,间接影响缓存逻辑的执行。
排查与修复步骤
步骤1:检查Duration解析器的缓存设置
如果Duration有单独的解析器,确保在该解析器中也设置一致的缓存策略:
// Duration类型的解析器示例 const resolvers = { Duration: { days: (parent) => parent.days, hours: (parent, args, context, info) => { // 显式设置缓存提示,与顶层保持一致 info.cacheControl.setCacheHint({ maxAge: 300, scope: 'PUBLIC' }); return parent.hours; }, // 其他Duration字段同理 }, Query: { getMajorIncidents: (parent, args, context, info) => { info.cacheControl.setCacheHint({ maxAge: 300, scope: 'PUBLIC' }); // 你的查询逻辑 }, }, };
步骤2:修复Float精度问题
将Duration中的Float值转为固定精度的格式,避免精度误差:
// 在生成Duration数据时,对Float值做精度处理 const calculateDuration = (start, end) => { const diff = new Date(end) - new Date(start); return { days: Math.floor(diff / (1000 * 60 * 60 * 24)), hours: parseFloat((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)).toFixed(2), minutes: parseFloat((diff % (1000 * 60 * 60)) / (1000 * 60)).toFixed(2), // 其他字段同理,或者转为整数(比如乘以60转成秒数) }; };
或者将Schema中的Float类型改为String,直接返回格式化后的字符串,彻底避免精度问题。
步骤3:清理Schema中的重复字段
删除Data类型中重复的u_clientpriority定义,修正后的Schema:
type Data { category: String subcategory: String dv_cmdb_ci: String dv_state: String major_incident_state: String number: String opened_at: String short_description: String u_clientpriority: String u_global_priority: String u_incident_end: String u_incident_start: String u_incident_commander: String dv_u_business_segments: String dv_u_communication_category: String duration: Duration } type Duration { days: Int hours: Float minutes: Float seconds: Float milliseconds: Float }
步骤4:验证Redis缓存状态
用Redis CLI查看是否生成了缓存键:
# 列出所有缓存键(生产环境不建议用KEYS,可用SCAN) KEYS *
如果没有生成缓存键,说明Apollo未触发缓存,重点检查缓存策略的组合;如果有键但每次查询生成新键,说明缓存键计算依赖了变化的Float值,回到步骤2处理精度问题。
内容的提问来源于stack exchange,提问作者Abhinav

