.NET性能优化-是时候换个序列化协议了

计算机单机性能一直受到摩尔定律的约束,随着移动互联网的兴趣,单机性能不足的瓶颈越来越明显,制约着整个行业的发展 。不过我们虽然不能无止境的纵向扩容系统,但是我们可以分布式、横向的扩容系统 , 这听起来非常的美好 , 不过也带来了今天要说明的问题 , 分布式的节点越多,通信产生的成本就越大 。

  • 网络传输带宽变得越来越紧缺,我们服务器的标配上了10Gbps的网卡
  • HTTPx.x 时代TCP/IP协议通讯低效,我们即将用上QUIC HTTP 3.0
  • 同机器走Socket协议栈太慢,我们用起了eBPF
  • ....
现在我们的应用程序花在网络通讯上的时间太多了 , 其中花在序列化上的时间也非常的多 。我们和大家一样,在内部微服务通讯序列化协议中 , 绝大的部分都是用JSON 。JSON的好处很多 , 首先就是它对人非常友好,我们能直接读懂它的含义,但是它也有着致命的缺点,那就是它序列化太慢、序列化以后的字符串太大了 。
之前笔者做一个项目时,就遇到了一个选型的问题,我们有数亿行数据需要缓存到Redis中,每行数据有数百个字段 , 如果用Json序列化存储的话它的内存消耗是数TB级别的(部署个集群再做个主从、多中心需要成倍的内存、太贵了,用不起) 。于是我们就在找有没有除了JSON其它更好的序列化方式?
看看都有哪些目前市面上序列化协议有很多比如XML、JSON、Thrift、Kryo等等,我们选取了在.NET平台上比较常用的序列化协议来做比较:
  • JSON:JSON是一种轻量级的数据交换格式 。采用完全独立于编程语言的文本格式来存储和表示数据 。简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言 。
  • Protobuf:Protocol Buffers 是一种语言无关、平台无关、可扩展的序列化结构数据的方法 , 它可用于(数据)通信协议、数据存储等,它类似XML , 但比它更小、更快、更简单 。
  • MessagePack:是一种高效的二进制序列化格式 。它可以让你像JSON一样在多种语言之间交换数据 。但它更快、更小 。小的整数被编码成一个字节,典型的短字符串除了字符串本身之外,只需要一个额外的字节 。
  • MemoryPack:是Yoshifumi Kawai大佬专为C#设计的一个高效的二进制序列化格式,它有着.NET平台很多新的特性,并且它是Code First开箱即用,非常简单;同时它还有着非常好的性能 。
我们选择的都是.NET平台上比较常用的 , 特别是后面的三种都宣称自己是非常?。?非常快的 , 那么我们就来看看到底是谁最快,谁序列化后的结果最小 。
准备工作我们准备了一个DemoClass类,里面简单的设置了几个不同类型的属性,然后依赖了一个子类数组 。暂时忽略上面的一些头标记 。
[MemoryPackable][MessagePackObject][ProtoContract]public partial class DemoClass{[Key(0)] [ProtoMember(1)] public int P1 { get; set; }[Key(1)] [ProtoMember(2)] public bool P2 { get; set; }[Key(2)] [ProtoMember(3)] public string P3 { get; set; } = null!;[Key(3)] [ProtoMember(4)] public double P4 { get; set; }[Key(4)] [ProtoMember(5)] public long P5 { get; set; }[Key(5)] [ProtoMember(6)] public DemoSubClass[] Subs { get; set; } = null!;}[MemoryPackable][MessagePackObject][ProtoContract]public partial class DemoSubClass{[Key(0)] [ProtoMember(1)] public int P1 { get; set; }[Key(1)] [ProtoMember(2)] public bool P2 { get; set; }[Key(2)] [ProtoMember(3)] public string P3 { get; set; } = null!;[Key(3)] [ProtoMember(4)] public double P4 { get; set; }[Key(4)] [ProtoMember(5)] public long P5 { get; set; }}System.Text.Json选用它的原因很简单,这应该是.NET目前最快的JSON序列化框架之一了 , 它的使用非常简单,已经内置在.NET BCL中,只需要引用System.Text.Json命名空间,访问它的静态方法即可完成序列化和反序列化 。
using System.Text.Json;var obj = ....;// Serializevar json = JsonSerializer.Serialize(obj);// Deserializevar newObj = JsonSerializer.Deserialize<T>(json)Google Protobuf.NET上最常用的一个Protobuf序列化框架,它其实是一个工具包,通过工具包+*.proto文件可以生成GRPC Service或者对应实体的序列化代码 , 不过它使用起来有点麻烦 。
使用它我们需要两个Nuget包 , 如下所示:
<!--Google.Protobuf 序列化和反序列化帮助类--><PackageReference Include="Google.Protobuf" Version="3.21.9" /><!--Grpc.Tools 用于生成protobuf的序列化反序列化类 和 GRPC服务--><PackageReference Include="Grpc.Tools" Version="2.50.0"><PrivateAssets>all</PrivateAssets><IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets></PackageReference>

推荐阅读