设为首页 收藏本站
查看: 1022|回复: 0

[经验分享] Memcached 类库 EnyimMemcached 下的JSON序列化和二进制序列化兼容

[复制链接]

尚未签到

发表于 2015-8-31 11:56:53 | 显示全部楼层 |阅读模式
  Jusfr 原创,转载请注明来自博客园,文章所用代码见于我的github 。
  反序列化失败的问题已经处理,代码更新于 github。
  本人在分布式的项目使用中 EnyimMemcached ,由于业务需求使用了其序列化扩展,这里作下记录。
  EnyimCaching 通过配置文件提供了扩展点,见github,序列化由 ITranscoder 接口定义,提供了 DefaultTranscoder 和 DataContractTranscoder 两个实现类,前者作为默认,后面以XML序列化的方式重写了前者的对象序列化方法。
  EnyimCaching 是很典型的 DoNet 应用,内部逻辑使用 protected virtual 修饰,用户继承基类重写对应方法即可。
  DefaultTranscoder 使用了一组方法对对象进行序列化,我们关注的引用类型使用了原生的 BinaryFormatter 进行序列化,它的效率高,带来的显著的问题是字节流带有 dll 强类型签名。对于分布式应用,到处引用 dll 并不是什么好主意;而非 Donet 语言更直接没辙了,处理好它们是我的需求
DSC0000.jpg
  二进制序列化的效果
  扩展过程并不麻烦,JSON 是首先方案,引用 Newtonsoft.Json 创建继承自 DefaultTranscoder 的 NewtonsoftJsonTranscoder,序列化方法重写 SerializeObject(object value) 方法即可,代码实在不需要贴出来


DSC0001.gif DSC0002.gif


        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   DSC0003.jpg
  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 之间,立马又心生一记,直接贴代码:
   DSC0004.jpg
  





        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 数组进行更准确更有效率的推断实在是没有精力,如果您有其他实践或更好的方案,还请指教。
  反序列化失败的问题已经处理,代码更新于 github。
  Jusfr 原创,转载请注明来自博客园,文章所用代码见于我的github 。

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-106780-1-1.html 上篇帖子: memcached 的配置及 spymemcached 客户端简单使用 下篇帖子: memcached单点故障与负载均衡
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表