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

Jusfr 原创,转载请注明来自博客园,文章所用代码见于我的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());
            }
        }
View Code

技术分享

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);
            }
        }
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 之间,立马又心生一记,直接贴代码:

技术分享

 

技术分享
        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 。

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。