记录一次大版本上线后回滚的案例
一 问题
一个大版本的迭代,保存到Redis中的缓存数据实体类增加了一些属性,就用了一个子类继承旧实体类。获取缓存数据时可以通过Redis的class类型判断是新数据,如果不是,就用旧实体类序列化,代码如下:
- 旧的实体类
class OldEntity {
// 旧的属性
private Integer age;
}
- 新的实体类
class NewEntity extends OldEntity {
// 新增属性
private String name;
}
- 从Redis获取数据,适配旧数据
Map<byte[], byte[]> loadedHash = stringRedisTemplate.execute((RedisCallback<Map>) conn -> conn.hGetAll(key.getBytes()));
// private final static ObjectHashMapper SELECTIVE_MAPPER = new ObjectHashMapper();
Object cached = SELECTIVE_MAPPER.fromHash(loadedHash);
if (cached == null)
return null;
// 判断Redis保存的是不是新的数据模型
if (NewEntity.class.isInstance(cached)) {
NewEntity result = (NewEntity) cached;
// TODO 其他业务操作
return result;
}
// 旧的数据模型
OldEntity result = (OldEntity) cached;
// TODO 其他业务操作
return result;
- 业务流程:
事件 | 操作 | 结果 |
---|---|---|
初始化数据事件 | 初始化数据 | 保存到缓存 |
中间点更新事件1 | 获取缓存更新数据1 | 更新缓存 |
中间点更新事件2 | 获取缓存更新数据2 | 更新缓存 |
中间点更新事件3 | 获取缓存更新数据3 | 更新缓存 |
中间点更新事件n | 获取缓存更新数据n | 更新缓存 |
结束事件 | 业务操作 | 落库,删除缓存 |
以上流程如果只是 OldEntity 转 NewEntity 是没有问题的,但是我们线上服务器是多台,发布的时候是一台台发布的,这个时候就会存在一个没有想到的情况。旧数据 OldEntity 在新发布的应用上被转化为新数据模型 NewEntity 后被保存在Redis,下一个中间点更新事件的请求打到了还没来得及发布的服务器上,这时旧版本的应用获取缓存时将 NewEntity 转 OldEntity 模型,转化失败,返回 null。导致线上报错预警。
- 发布部分服务器后的业务流程:
事件 | 操作 | 结果 | Redis数据模型 |
---|---|---|---|
初始化数据事件 | 旧应用初始化数据 | 保存到缓存 | OldEntity |
中间点更新事件1 | 新版本应用更新数据 | 更新缓存 | NewEntity |
中间点更新事件2 | 旧版本应用更新数据 | 数据转化失败,报错 | NewEntity |
二 解决方案
不新建实体类,在旧的实体类模型上增加新属性:
class OldEntity {
// 旧的属性
private Integer age;
// 新增属性
private String name;
}
获取缓存后判断新增属性值是否为空,以此判断是否是旧应用保存的数据:
Map<byte[], byte[]> loadedHash = stringRedisTemplate.execute((RedisCallback<Map>) conn -> conn.hGetAll(key.getBytes()));
// private final static ObjectHashMapper SELECTIVE_MAPPER = new ObjectHashMapper();
Object cached = SELECTIVE_MAPPER.fromHash(loadedHash);
if (cached == null)
return null;
OldEntity result = (OldEntity) cached;
if (ObjectUtils.isEmpty(result.getName())) {
// 旧应用保存的数据
// TODO 业务操作
} else {
// 新应用保存的数据
// TODO 业务操作
}
return result;