使用紧凑的序列化器,数倍提升性能 —— ESFramework 4.0 快速上手(11)
在分布式通信系统中,网络传递的是二进制流,而内存中是我们基于对象模型构建的各种各样的对象,当我们需要将一个对象通过网络传递给另一个节点时,首先需要将其序列化为字节流,然后通过网络发送给目标节点,目标节点接收后,再反序列化为对象实例。在ESFramework体系中,也是遵循同样的规则。
ESFramework称这些需要经过网络传递的对象称之为协议类(Contract),协议类通常只是一个简单的数据结构封装,用于保存状态的一个哑类(不包含任何方法,从object继承的除外),有点类似于与数据库中表进行映射的贫血Entity。(关于Contract更详细的介绍可以参见ESFramework 4.0 进阶(01)-- 消息)。基于ESFramework的分布式系统使用这些协议类实例进行数据交换。如果是自己组装骨架流程并使用ESPlus提供的IContractHelper实现,那么ESFramework底层会自动帮我们完成协议对象的序列化和反序列化。
但是,如果我们使用的是Rapid引擎了,我们会经常使用ESPlus.Application.CustomizeInfo.Passive.ICustomizeInfoOutter接口的Send等方法发送二进制自定义信息(实际上,其底层转换为Message、再转换为字节流时,还是由ESFramework自动完成的),这个二进制信息通常是系统中的某个业务对象的序列化结果,而这个序列化过程我们必须自己完成。
一.序列化方式的两种选择
为了将业务对象转换为二进制流,大家通常有两种方案可以选择:使用.NET自带的二进制序列化器,或,先将业务对象转换为字符串(比如xml),再将结果用类似UTF8进行编码得到字节流。 这两种方案都有缺陷。
1..NET自带的二进制序列化器
使用.NET自带的二进制序列化器对我们来说是最方便的,因为只要直接调用API就好了,不需要我们自己动手做任何动作。但是,缺陷也很明显:
(1)首先,能够序列化的类必须加上Serializable标签,且.NET自带的二进制序列化器需要绑定被序列化的类的命名空间和程序集。比方说,将windows里序列化一个协议对象得到的结果传到Silverlight中,是无法完成反序列化的,即使Silverlight中也有完全相同的协议类定义。这也说明,如果通信的客户端是用Silverlight开发的,肯定是不能使用.NET自带的二进制序列化器的。
(2)序列化结果臃肿,其size巨大。在巨大并发高性能的分布式通信系统中,这将降低通信消息,并浪费大量的带宽,是不可忍受的。
(3)效率低下。.NET自带的二进制序列化器基于反射(Relection)的机制工作,比如读取类型的信息、读取/设置字段的值等都是通过反射来完成的。如果每秒仅仅序列化百千个对象,可能还可以应付;但是如果每秒需要序列化几万、甚至几十万个对象,就不堪重负了。
(4)加密困难。由于.NET自带的二进制序列化器在序列化协议对象时,会反射读取对象的内部成员(private),而如果定义协议的dll经过加密后,private成员的名称通常会被混淆成随机的名称,这样就要求通信的各方都使用同一个加密的dll,否则,协议对象的反序列化可能会失败。
2.通过string进行中转
为了解决类似Silverlight客户端与服务端通信的序列化统一的问题,我们可以使用string作为中转,而无论是否为Silverlight环境,对同一字符串进行相同格式(如UTF8)的编码,得到的字节流肯定是一样的。反过来,同样的字节流在不同的环境中使用相同的解码器进行解码得到的字符串也一样。这是一种可行的方案,但是缺点也是有的:
(1)我们需要自己打造协议对象与字符串之间的相互转换 -- 这可能是一个费时费神的又容易出错的工作。
(2)序列化之后的结果的Size取决于我们协议对象与字符串之间相互转换时所采用的规则,同样也取决于我们的耐心程度 -- 为了使结果的Size更小,我们也许要动更多的脑筋。
当然,现在也有现成的将对象与字符串想换转换的工具,比如JSON和XML。就一般而言,使用JSON转换协议对象得到的结果比使用xml要小很多。
(3)同.NET自带的二进制序列化器,对象属性值的读取/设置、对象的创建等通常也是基于反射的,所以,效率同样存在问题。
二.第三种方案
ESPlus提供了更紧凑、且不需绑定命名空间和程序集的二进制序列化器:ESPlus.Serialization.CompactPropertySerializer。
(1)CompactPropertySerializer 基于Emit技术和缓存技术构建,且避免了反射带来的开销,所以效率大大提升。
(2)CompactPropertySerializer 内部使用了简练、紧凑的序列化格式和规则,使得序列化的结果Size更短小。
(3)将被序列化的类不需要增加任何标签,且不依赖于命名空间和程序集,只要类的定义完全一致,CompactPropertySerializer 就可以正常工作。真正的弱侵入性。
(4)ESFramework.SL也提供了完全一样的CompactPropertySerializer ,所以基于第3点,服务端与Silverlight客户端就可以协同工作了。
(5)只要采用CompactPropertySerializer的序列化格式,.NET服务端可以与任何其它语言(如C++、JAVA等)构建的客户端协同工作。
CompactPropertySerializer解决了前面提出的几个问题,当然,它也不是全能的,使用它也有一些限制,下面我们即将讲到。
三.如何使用CompactPropertySerializer
ESPlus.Serialization.CompactPropertySerializer 和 ESFramework.SL.Serialization.CompactPropertySerializer 是ESFramework提供的分别用于桌面应用和Sivlerlight客户端的二进制序列化器。它们的实现几乎一模一样,所以,使用时要注意的方面也是相同的:
(1)CompactPropertySerializer 支持类和结构的序列化,但是被序列化的类或结构必须有默认的构造函数(Ctor)。
(2)CompactPropertySerializer 只序列化那些可读写的属性,如果一个属性仅仅是只读或只写的,那么该属性不会被序列化。这也是CompactPropertySerializer名称中Property的含义。
(3)CompactPropertySerializer 支持的类型:基础数据类型(如int、long、bool等)、string、byte[],以及由这些类型构成的class或struct。
(4)支持多层嵌套 -- 即被序列化的class中可以包含别的类型的对象,只要每一个被嵌入的对象最后都是由基础数据类型构成的。
(5)除byte[]/List<>/Dictionary<,>泛型外,不支持其它的集合类型。
(6)不支持循环引用。如果存在循环引用,序列化时将导致死循环。
正如本文开始提到的,在通信系统中用到的协议类都是一些最简单的仅仅包含数据的“哑类”,所以,上面的限制对我们在设计协议类时是没有什么约束的。尽可能地使用简单的数据类型,然后将需要序列化的字段通过可读写的属性暴露就OK了。
CompactPropertySerializer 包括了序列化和反序列化的两个基本方法:
byte[] Serialize<T>(T obj); T Deserialize<T>(byte[] buff, int startIndex) where T : new();
方法含义很明显,不解释了。另外,CompactPropertySerializer 采用了Singleton,我们可以在程序中直接使用这个Singleton ,通过CompactPropertySerializer.Default属性获得该Singleton实例的引用。
四.试试CompactPropertySerializer的性能和效率
我们以文件传送中要使用到的协议类BeginSendFileContract为例,BeginSendFileContract定义如下:
之所以加上[Serializable]标签,是因为下面测试.NET自带的二进制序列化器需要用到,正式的BeginSendFileContract定义是没有这个标签的。
正如大多数的协议类一样,这个类仅仅包含几个简单类型的属性,现在我们来对比一下.NET自带的二进制序列化器与CompactPropertySerializer的表现。
(1)比较序列化结果的大小:
BeginSendFileContract contract = new BeginSendFileContract();
byte[] result1 = CompactPropertySerializer.Default.Serialize<BeginSendFileContract>(contract);
byte[] result2 = ESBasic.Helpers.SerializeHelper.SerializeObject(contract);
ESBasic.Helpers.SerializeHelper就是对.NET自带的二进制序列化器的简化封装。执行的结果如下:
result1的长度为:32
result2的长度为:242
.Net自带序列化器的结果是CompactPropertySerializer结果的7-8倍,如果为contract的一些string类型的字段赋有意义的值,这个倍数会稍微降一点;如果这个contract的定义包含了更多的要序列化的属性,那么这个倍数还会继续提高。不管怎么样,这个比例都是很吓人的,所以在高频的通信系统中,相比于使用.Net自带序列化器,采用CompactPropertySerializer可以节省大量的带宽。
(2)测试性能:
我们分别运行两个序列化100万次,看所要的时间:
BeginSendFileContract contract = new BeginSendFileContract();
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
for (int i = 0; i < 1000000; i++)
{
byte[] result1 = CompactPropertySerializer.Default.Serialize<BeginSendFileContract>(contract);
}
stopwatch.Stop();
double span1 = stopwatch.ElapsedMilliseconds; stopwatch.Reset();
stopwatch.Start();
for (int i = 0; i < 1000000; i++)
{
byte[] result2 = ESBasic.Helpers.SerializeHelper.SerializeObject(contract);
}
stopwatch.Stop();
double span2 = stopwatch.ElapsedMilliseconds;
运行结果如下:
span1:5324ms
span2:17249ms
CompactPropertySerializer比.Net自带的序列化器快3倍以上,优势不言而喻。
大家可以参考上面的demo,写更多的测试程序,来测试更多的内容,包括它们在反序列化方面的表现的比较。
对于一般的通信应用,使用.Net自带的二进制序列化器也许就够用了,不会有太大的影响,但是如果在类似MMORPG、视频/音频会议等等需要高频、高性能的通信系统中,.Net自带的二进制序列化器就不是最好的选择了。如果使用ESFramework来构建你的分布式通信应用,那就可以从CompactPropertySerializer得到更多的帮助。
使用紧凑的序列化器,数倍提升性能 —— ESFramework 4.0 快速上手(11)的更多相关文章
- GooseFS助力大数据业务数倍提升计算能力
前言 GooseFS是由腾讯云推出的一款分布式缓存方案,主要针对包括需要缓存加速的数据湖业务场景,提供基于对象存储COS服务的近计算端数据加速层. GooseFS 基于开源大数据缓存方案 Alluxi ...
- SQL Server中使用Check约束提升性能
在SQL Server中,SQL语句的执行是依赖查询优化器生成的执行计划,而执行计划的好坏直接关乎执行性能. 在查询优化器生成执行计划过程中,需要参考元数据来尽可能生成高效的执行计划, ...
- 百万级高并发mongodb集群性能数十倍提升优化实践
背景 线上某集群峰值TPS超过100万/秒左右(主要为写流量,读流量很低),峰值tps几乎已经到达集群上限,同时平均时延也超过100ms,随着读写流量的进一步增加,时延抖动严重影响业务可用性.该集群采 ...
- 开源!一款功能强大的高性能二进制序列化器Bssom.Net
好久没更新博客了,我开源了一款高性能的二进制序列化器Bssom.Net和新颖的二进制协议Bssom,欢迎大家Star,欢迎参与项目贡献! Net开源技术交流群 976304396,禁止水,只能讨论技术 ...
- 一: DRF web应用框架基础,及序列化器的使用
---恢复内容开始--- 一: web 应用模式(有两种) 1: 前后端不分离(前端从后端直接获取数据) 2: 前后端分离 二: api 接口 原因一: 为了在团队内部形成共识.防止个人习惯差异引起的 ...
- drf之序列化器的使用
一.序列化器-Serializer 作用: 1. 序列化,序列化器会把模型对象转换成字典,经过response以后变成json字符串 2. 完成数据校验功能 3. 反序列化,把客户端发送过来的数据,经 ...
- 16-DRF工程搭建与序列化器
1.DRF工程搭建 环境安装与配置 DRF是以Django扩展应用的方式提供的,所以我们可以直接利用Django环境,而无需创建(先创建Django环境). 1.安装DRF pip3 install ...
- DRF序列化器的使用
序列化器的使用 序列化器的使用分两个阶段: 在客户端请求时,使用序列化器可以完成对数据的反序列化. 在服务器响应时,使用序列化器可以完成对数据的序列化. 序列化的基本使用 使用的还是上一篇博文中使用的 ...
- Django-DRF组件学习-环境安装与配置与序列化器学习
1.DRF环境安装与配置 DRF需要以下依赖: Python (2.7, 3.2, 3.3, 3.4, 3.5, 3.6) Django (1.10, 1.11, 2.0) DRF是以Django扩展 ...
随机推荐
- nvl isnull coalesce
oracle 的NVL(col,0)是判断如果col字段为空的时候赋值0. postgresql里也有类似的方法 SELECT coalesce(collect_result,0) as collec ...
- js两种生成对象模式(公有成员和成员私有)
假设有个需求,创建一个book类,有isbn码,书名,作者 :可以对isbn进行数据完整性校验:--js设计模式 <script type="text/javascript" ...
- java程序基础
- JTree单击事件
import javax.swing.*; import javax.swing.tree.*; import java.awt.FlowLayout; import java.awt.GridLay ...
- python中判断语句用两个or连接的奇葩
学python的时候犯的一个错误,放在这吧.就是在循环某个列表的时候不要去操作它,这是容易忽略的一个地方.所以如果要操作某个列表本身,那么先把该列表copy一份,然后再读取的时候读copy的那份.操作 ...
- SQL 默认数据库被误删
我今天也遇到了,自己解决了.这是我总结的:(不管这帖子沉到哪里了) 删除了SQL服务器默认数据库,无法连接服务器,用户[]登录失败. 1,使用管理员账户修改此用户的默认数据库. 企业管理器,展开 ...
- 7.MyBatis延时加载
1.创建javaWeb项目MyBatis_Lazy并在WebRoot下的WEB-INF下的lib下添加如下jar文件 cglib-nodep-2.1_3.jar log4j-1.2.17.jar my ...
- [编码解码] Base64 编码换行和+号遍空格的处理
Android自身带有Base64加密与解密的包,可以方便地加密密码方便传输. String base64Token = Base64.encodeToString(token.trim().getB ...
- concurrent实用类
improt java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.RejectedExecutionHandler; ...
- 移动端audio自动播放问题
中秋临近,心血来潮想做个手机端贺卡,以前接触的移动端较少,虽然是个简单的贺卡,其实也蛮多坑的,简略说一下在制作贺卡的过程遇到的坑: 一:移动端的屏幕大小不能算作body的大小,因为手机浏览器头部都有网 ...