Memcached 类库 EnyimMemcached 下的JSON序列化和二进制序列化兼容 - Jusfr - 博客园

来源: Memcached 类库 EnyimMemcached 下的JSON序列化和二进制序列化兼容 – Jusfr – 博客园

usfr 原创,转载请注明来自博客园,文章所用代码见于我的github 。

本人在分布式的项目使用中 EnyimMemcached ,由于业务需求使用了其序列化扩展,这里作下记录。

EnyimCaching 通过配置文件提供了扩展点,见github,序列化由 ITranscoder 接口定义,提供了 DefaultTranscoder 和 DataContractTranscoder 两个实现类,前者作为默认,后面以XML序列化的方式重写了前者的对象序列化方法。

EnyimCaching 是很典型的 DoNet 应用,内部逻辑使用 protected virtual 修饰,用户继承基类重写对应方法即可。

DefaultTranscoder 使用了一组方法对对象进行序列化,我们关注的引用类型使用了原生的 BinaryFormatter 进行序列化,它的效率高,带来的显著的问题是字节流带有 dll 强类型签名。对于分布式应用,到处引用 dll 并不是什么好主意;而非 Donet 语言更直接没辙了,处理好它们是我的需求

二进制序列化的效果

扩展过程并不麻烦,JSON 是首先方案,引用 Newtonsoft.Json 创建继承自 DefaultTranscoder 的 NewtonsoftJsonTranscoder,序列化方法重写 SerializeObject(object value) 方法即可,代码实在不需要贴出来


protected override ArraySegment<byte> SerializeObject(object value) {
JsonSerializer serializer = JsonSerializer.CreateDefault();
using (MemoryStream memoryStream = new MemoryStream())
using (TextWriter textWriter = new StreamWriter(memoryStream))
using (JsonWriter jsonWriter = new JsonTextWriter(textWriter)) {
serializer.Serialize(jsonWriter, value);
jsonWriter.Flush();
memoryStream.Seek(0L, SeekOrigin.Begin);
return new ArraySegment<byte>(memoryStream.ToArray());
}
}

Json 序列化的效果

但是反序列化就不是再丢上几句就完了,因为要对 Memcached 中已有的二进制序列化数据兼容,先说 EnyimCaching 对象序列化与反序列化逻辑:

1. CacheItem 是真正的缓存项,序列化时空对象时,使用长度为0的 byte 数组,结合 TypeCode.DBNull 枚举生成 CacheItem 实例;非空对象 调用 SerializeObject() 方法(内部使用 BinaryFormatter) 得到 byte 数组,组合 TypeCode.Object 枚举生成 CacheItem 实例;
2. 反序列化时检查 TypeCode 枚举,对枚举为 TypeCode.DBNull 的 CacheItem 直接返回 null;对枚举为 TypeCode.Object 的 CacheItem 调用 DeserializeObject() 方法并传入 Byte 数组;

 

下边是我的思路与解决方案

对于非空引用类型,Newtonsoft.Json 会序列化成形如 “{…}”的字符串,那么思路就来了:字符串“{}”的UTF8字节为[123, 125],那么我们是不是可以读出部分字节进行对比?像这样:读取 buffer[0],如果为123,则读取 buffer[buffer.Length – 1],如果为 [125],那么该 Byte 数组为 JSON 对象,可以使用 Newtonsoft.Json 反序列化;否则进行二进制序列化;

对于非引用,Newtonsoft.Json 会序列化 “null” 字符串,虽然反序列化时有开销,但是按照 EnyimCaching 的逻辑,空引用判断在前,不会进入 SerializeObject() 和 DeserializeObject() 方法;如果不放心,可以进行自己的 byte 数组非空与长度判断;


protected override object DeserializeObject(ArraySegment<byte> value) {
if (value.Array[0] != 123 || value.Array[value.Array.Length - 1] != 125) {
return base.DeserializeObject(value);
}

JsonSerializer serializer = JsonSerializer.CreateDefault();
serializer.NullValueHandling = NullValueHandling.Ignore;
using (MemoryStream memoryStream = new MemoryStream(value.Array, value.Offset, value.Count))
using (TextReader textReader = new StreamReader(memoryStream))
using (JsonReader jsonReader = new JsonTextReader(textReader)) {
return serializer.Deserialize(jsonReader);
}
}

很快就被打脸了:本机测试用例通过,开发中也用了不短时间,然后同事和我说遇到反序列化异常,马上就知道问题在这里,我用的 Newtonsoft.Json 版本是 6.0.8,他的是 6.0.4,虽然版本不一致,但是不应该序列化出来的东西不一样;接着调试,发现他进行反序列化时,得到的 byte 数组前面多了4个为0的字节,大概长这样 [0, 0, 0, 0, 123, …],接着调试,发现序列化时他写入的数组确实又是以 123 起头的,我香蕉你个把那!

接着想办法,由于 BinaryFormatter 有自己规律,打印几个看看。可以看到前8个 byte 都是在 0, 1, 255 之间,立马又心生一记,直接贴代码:


Byte[] _donetBytes = new[] { (Byte)0, (Byte)1, (Byte)255 };

protected override object DeserializeObject(ArraySegment<byte> value) {
if (value.Array.Length >= _donetBytes.Length) {
var isOrignalObjectByte = value.Array.Take(10).Distinct().All(b => _donetBytes.Contains(b));
if (isOrignalObjectByte) {
return base.DeserializeObject(value);
}
}

JsonSerializer serializer = JsonSerializer.CreateDefault();
serializer.NullValueHandling = NullValueHandling.Ignore;
using (MemoryStream memoryStream = new MemoryStream(value.Array, value.Offset, value.Count))
using (TextReader textReader = new StreamReader(memoryStream))
using (JsonReader jsonReader = new JsonTextReader(textReader)) {
return serializer.Deserialize(jsonReader);
}
}

取 byte 数组的前10个,如果全部落入 [0, 1, 255] 中则使用 BinaryFormatter 反序列化,否则按 Newtonsoft.Json 反序列化;

本以为没事了,过几天又有反序列化异常,实在是没功夫调试了,最后写成这样了:


1         private Byte[] _donetBytes = new[] { (Byte)0, (Byte)1, (Byte)255 };
2
3         private static Object JsonDeserialize(Byte[] buffer) {
4             JsonSerializer serializer = JsonSerializer.CreateDefault();
5             serializer.NullValueHandling = NullValueHandling.Ignore;
6             using (MemoryStream memoryStream = new MemoryStream(buffer))
7             using (TextReader textReader = new StreamReader(memoryStream))
8             using (JsonReader jsonReader = new JsonTextReader(textReader)) {
9                 return serializer.Deserialize(jsonReader);
10             }
11         }
12
13         protected override object DeserializeObject(ArraySegment<byte> value) {
14             Boolean isJson = false;
15             if (value.Array[0] == 123 && value.Array[value.Array.Length - 1] == 125) {
16                 isJson = true;
17             }
18             if (!isJson) {
19                 var isOrignalObjectByte = value.Array.Take(10).Distinct().All(_donetBytes.Contains);
20                 isJson = !isOrignalObjectByte;
21             }
22
23             if (isJson) {
24                 return JsonDeserialize(value.Array);
25             }
26             else {
27                 try {
28                     return base.DeserializeObject(value);
29                 }
30                 catch (SerializationException) {
31                     // Log or something
32                     return JsonDeserialize(value.Array);
33                 }
34             }
35         }

首先用理想的首尾字节判断是否为 JSON,如果不是则判断前10个 byte 是否落入 [0, 1, 255],最后还有一道补救,catch 二进制序列化失败下的异常,重新使用 JSON 序列化;

至此世界太平了,时间有限,要对 byte 数组进行更准确更有效率的推断实在是没有精力,如果您有其他实践或更好的方案,还请指教。

Jusfr 原创,转载请注明来自博客园,文章所用代码见于我的github 。

赞(0) 打赏
分享到: 更多 (0)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏