问题现象

某功能改版只在部分节点先上线。上线后前一小时正常,一小时后接口开始间歇性报错、部分请求走了降级,取决于请求落到新节点还是老节点。

背景

这个接口的返回对象会被缓存组件序列化后写进共享缓存,缓存过期时间较短;灰度期间新旧两个版本的节点同时在跑,共用同一份缓存。

根本原因

缓存里存的是序列化后的对象,它的结构就是一份隐性契约。这次改了返回对象的字段结构,灰度期新旧结构必然共存于同一缓存空间:旧节点按旧结构写缓存,新节点读到后按新结构反序列化,字段对不上又开了遇到未知字段就报错,于是解析失败、接口间歇报错。没有版本隔离、又对未知字段零容错,新旧节点就互相读崩。

解决方案

给缓存 key 带上结构版本号,让新旧结构互不干扰;或把反序列化配成遇到未知字段忽略而不是报错;必要时发布前预失效相关缓存。

// 缓存 key 带结构版本,新旧互不命中
String key = "user:profile:v2:" + userId;
// 或:反序列化放开未知字段
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

测试建议

改动带缓存方法的返回结构时,专门覆盖灰度期新旧节点读到对方写入的缓存这一组合,而不只测单版本命中。构造缓存里同时存在新旧两种结构的场景,验证反序列化不报错、接口不间歇失败。

经验总结

被缓存或序列化的对象,其结构是一份隐性契约;改字段前先想清楚新旧版本在同一缓存空间共存时能否互相读,用版本隔离或未知字段容错兜底。