本地缓存不一致-记Jackson的MapSerializer序列化
踩坑-现象
现象: 线上出现运费险查询接口返回结果不符合预期 且短时间内同样的请求参数返回结果不一致
同样的参数 短时间内返回结果不一致 对应api的业务逻辑没有改变
根本原因:
seller-center应用使用的redisson用的jackson序列化 jackson序列化map时有坑
当map中的key是Integer类型对应的序列化执行器StdKeySerializers序列化后以string存储
value是integer类型对应的序列化执行器NumberSerializers序列化后还是integer
jackson的map序列化核心逻辑
com.fasterxml.jackson.databind.ser.std.MapSerializer#serializeTypedFields
public void serializeTypedFields(Map<?,?> value, JsonGenerator gen, SerializerProvider provider,
Object suppressableValue) // since 2.5
throws IOException
{
final Set<String> ignored = _ignoredEntries;
final boolean checkEmpty = (MARKER_FOR_EMPTY == suppressableValue);
//循环map 并序列化key 和value
for (Map.Entry<?,?> entry : value.entrySet()) {
Object keyElem = entry.getKey();
JsonSerializer<Object> keySerializer;
if (keyElem == null) {
keySerializer = provider.findNullKeySerializer(_keyType, _property);
} else {
if (ignored != null && ignored.contains(keyElem)) continue;
//key是integer的1 !=null 取的默认StdKeySerializers序列化执行器
keySerializer = _keySerializer;
}
final Object valueElem = entry.getValue();
JsonSerializer<Object> valueSer;
if (valueElem == null) {
if (_suppressNulls) { // all suppression include null suppression
continue;
}
valueSer = provider.getDefaultNullValueSerializer();
} else {
valueSer = _valueSerializer;
if (valueSer == null) {
//map的第一个entry进来 根据entry的vaule类型匹配序列化执行器
//这里value类型integer --> NumberSerializers
valueSer = _findSerializer(provider, valueElem);
}
if (checkEmpty) {
if (valueSer.isEmpty(provider, valueElem)) {
continue;
}
} else if (suppressableValue != null) {
if (suppressableValue.equals(valueElem)) {
continue;
}
}
}
keySerializer.serialize(keyElem, gen, provider);
try {
valueSer.serializeWithType(valueElem, gen, provider, _valueTypeSerializer);
} catch (Exception e) {
wrapAndThrow(provider, e, value, String.valueOf(keyElem));
}
}
}
map的 key 和value 分别匹配了不同的序列化执行器
map的key !=null 匹配到的是是 StdKeySerializers来序列化
map的value 根据value的类型来选择序列化 interger 匹配到了NumberSerializers序列化
重点来了 获取了map key 和 value的序列化执行器 下面开始序列化
两种序列化的具体实现
StdKeySerializers integer -> number -> string
NumberSerializers integer 还是integer
---------------------------------- 分隔线 -------------------------------------
艰难出坑-问题排查:
查询运费险接口实现加了本地缓存
缓存结构 本地缓存 -> redis -> DB 一层一层从上往下捞数据, 捞到数据再从下往上设置数据
该接口的返回值有 布尔值 且变量名is开头
/**
* 是否支持运费险
*/
private Boolean isSupport;
怀疑是lombok的 @Data注解自动生成的get set 有问题
故而加上手动生成的get set 重发应用 结果依旧是返回结果不一致
查看对应的类的class文件 发现isSupport这个变量的get set 方法和手动生成的是一致的 不是因为该变量命名Date问题引起,但是此类命名应该要避免
进一步推断 是代码问题引起 加上日志重发
发现是获取商家运费险的开通状态isOpen 导致的问题 获取商家开通状态代码
加上日志重发
问题就是这个map的containKey 导致的, 修改代码发布解决问题
出坑记录
从redis获取的map参数类型变了 put 到redis的map 和redis去除的map数据结构不一致
查询redis对应的key的值
具体redis存储map 序列化的过程
这里发现是redisson用的是Jackson序列化
重点: 遍历map的key vlue 获取对应class类型的序列化执行器 然后执行序列化
map的 key 和value 分别匹配了不同的序列化执行器
map的key !=null 匹配到的是是 StdKeySerializers来序列化
map的value 根据value的类型来选择序列化 interger 匹配到了NumberSerializers序列化
重点来了 获取了map key 和 value的序列化执行器 下面开始序列化
两种序列化的具体实现
StdKeySerializers integer -> number -> string
NumberSerializers integer 还是integer
map<integer, integer> 被jackson序列化后成了 {"@class":"java.util.HashMap","1":0,"2":0}
规范
如非必要不要把map用与缓存
如非必要不要把map用与dubbo接口的返回中
DTO中的布尔值 不要用is开头来命名 避免不同序列化导致的问题
action
梳理上述规范涉及存量代码 &评估改造风险
问题?
json 和 gson 对于map的序列化是怎么实现的呢?