转载

Memcached 类库 EnyimMemcached 下的JSON序列化和二进制序列化兼容

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

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

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

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

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

Memcached 类库 EnyimMemcached 下的JSON序列化和二进制序列化兼容

二进制序列化的效果

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

Memcached 类库 EnyimMemcached 下的JSON序列化和二进制序列化兼容
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());  } } 
View Code

Memcached 类库 EnyimMemcached 下的JSON序列化和二进制序列化兼容

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 数组非空与长度判断;

Memcached 类库 EnyimMemcached 下的JSON序列化和二进制序列化兼容
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);  } } 
View Code

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

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

Memcached 类库 EnyimMemcached 下的JSON序列化和二进制序列化兼容

Memcached 类库 EnyimMemcached 下的JSON序列化和二进制序列化兼容
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);  } } 
View Code

取 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 。

正文到此结束
Loading...