Apache Thrift和ProtoBuf一样,都是为优化序列化而生,Thrift是一个通信框架,最初有FaceBook开发,后交由Apache管理,目前Facebook也在使用。Thrift与ProtoBuf同样是跨平台多语言的,不过Thrift几乎支持现下的所有流行的语言,而ProtoBuf.net是.net的移植,相比而来Thrift支持更广。我这里做的是对Thrift序列化数据在效率做实验
1.定义接口文件
相当于数据契约。Thrift的契约定义用了自己的一套语法(IDL),这样做的目的是让他可以跨语言,同一套契约可以在任何语言下使用,关于IDL方法可以去参考Thrift的白皮书。
和上一篇文章相同,有以下这样两个类
public class Person { public int Id { get; set; }] public string Name { get; set; } public Address Address { get; set; } } public class Address { public string Line1 { get; set; } public string Line2 { get; set; } }
用Thrift 的IDL语言定义后如下,文件保存为test.idl
struct Address { 1: string Line1, 2: string Line2 } struct Person { 1: i32 Id, 2: string Name, 3: Address Address }
2. 使用Thrift提供的windows编译器生成文件
使用thrift-0.9.1.exe生成文件,将test.idl与thrift-0.9.1.exe放在同级目录下,在控制台是执行命令
thrift-0.9.1 --gen csharp test.idl
文件生成在目录下的gen-csharp文件夹中
/** * Autogenerated by Thrift Compiler (0.9.1) * * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING * @generated */ using System; using System.Collections; using System.Collections.Generic; using System.Text; using System.IO; using Thrift; using Thrift.Collections; using System.Runtime.Serialization; using Thrift.Protocol; using Thrift.Transport; #if !SILVERLIGHT [Serializable] #endif public partial class Address : TBase { private string _Line1; private string _Line2; public string Line1 { get { return _Line1; } set { __isset.Line1 = true; this._Line1 = value; } } public string Line2 { get { return _Line2; } set { __isset.Line2 = true; this._Line2 = value; } } public Isset __isset; #if !SILVERLIGHT [Serializable] #endif public struct Isset { public bool Line1; public bool Line2; } public Address() { } public void Read (TProtocol iprot) { TField field; iprot.ReadStructBegin(); while (true) { field = iprot.ReadFieldBegin(); if (field.Type == TType.Stop) { break; } switch (field.ID) { case 1: if (field.Type == TType.String) { Line1 = iprot.ReadString(); } else { TProtocolUtil.Skip(iprot, field.Type); } break; case 2: if (field.Type == TType.String) { Line2 = iprot.ReadString(); } else { TProtocolUtil.Skip(iprot, field.Type); } break; default: TProtocolUtil.Skip(iprot, field.Type); break; } iprot.ReadFieldEnd(); } iprot.ReadStructEnd(); } public void Write(TProtocol oprot) { TStruct struc = new TStruct("Address"); oprot.WriteStructBegin(struc); TField field = new TField(); if (Line1 != null && __isset.Line1) { field.Name = "Line1"; field.Type = TType.String; field.ID = 1; oprot.WriteFieldBegin(field); oprot.WriteString(Line1); oprot.WriteFieldEnd(); } if (Line2 != null && __isset.Line2) { field.Name = "Line2"; field.Type = TType.String; field.ID = 2; oprot.WriteFieldBegin(field); oprot.WriteString(Line2); oprot.WriteFieldEnd(); } oprot.WriteFieldStop(); oprot.WriteStructEnd(); } public override string ToString() { StringBuilder sb = new StringBuilder("Address("); sb.Append("Line1: "); sb.Append(Line1); sb.Append(",Line2: "); sb.Append(Line2); sb.Append(")"); return sb.ToString(); } }
/** * Autogenerated by Thrift Compiler (0.9.1) * * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING * @generated */ using System; using System.Collections; using System.Collections.Generic; using System.Text; using System.IO; using Thrift; using Thrift.Collections; using System.Runtime.Serialization; using Thrift.Protocol; using Thrift.Transport; #if !SILVERLIGHT [Serializable] #endif public partial class Person : TBase { private int _Id; private string _Name; private Address _Address; public int Id { get { return _Id; } set { __isset.Id = true; this._Id = value; } } public string Name { get { return _Name; } set { __isset.Name = true; this._Name = value; } } public Address Address { get { return _Address; } set { __isset.Address = true; this._Address = value; } } public Isset __isset; #if !SILVERLIGHT [Serializable] #endif public struct Isset { public bool Id; public bool Name; public bool Address; } public Person() { } public void Read (TProtocol iprot) { TField field; iprot.ReadStructBegin(); while (true) { field = iprot.ReadFieldBegin(); if (field.Type == TType.Stop) { break; } switch (field.ID) { case 1: if (field.Type == TType.I32) { Id = iprot.ReadI32(); } else { TProtocolUtil.Skip(iprot, field.Type); } break; case 2: if (field.Type == TType.String) { Name = iprot.ReadString(); } else { TProtocolUtil.Skip(iprot, field.Type); } break; case 3: if (field.Type == TType.Struct) { Address = new Address(); Address.Read(iprot); } else { TProtocolUtil.Skip(iprot, field.Type); } break; default: TProtocolUtil.Skip(iprot, field.Type); break; } iprot.ReadFieldEnd(); } iprot.ReadStructEnd(); } public void Write(TProtocol oprot) { TStruct struc = new TStruct("Person"); oprot.WriteStructBegin(struc); TField field = new TField(); if (__isset.Id) { field.Name = "Id"; field.Type = TType.I32; field.ID = 1; oprot.WriteFieldBegin(field); oprot.WriteI32(Id); oprot.WriteFieldEnd(); } if (Name != null && __isset.Name) { field.Name = "Name"; field.Type = TType.String; field.ID = 2; oprot.WriteFieldBegin(field); oprot.WriteString(Name); oprot.WriteFieldEnd(); } if (Address != null && __isset.Address) { field.Name = "Address"; field.Type = TType.Struct; field.ID = 3; oprot.WriteFieldBegin(field); Address.Write(oprot); oprot.WriteFieldEnd(); } oprot.WriteFieldStop(); oprot.WriteStructEnd(); } public override string ToString() { StringBuilder sb = new StringBuilder("Person("); sb.Append("Id: "); sb.Append(Id); sb.Append(",Name: "); sb.Append(Name); sb.Append(",Address: "); sb.Append(Address== null ? "<null>" : Address.ToString()); sb.Append(")"); return sb.ToString(); } }
3. 添加Thrift.dll
下载thrift-0.9.1.tar.gz解压后找到对应的C#代码编译后就得到Thrift.dll了
4. 序列化数据
创建一个C#控制台应用程序,将生成的两个代码文件添加到项目中,再添加Thrift.dll引用,和上次一样序列化同样的1000条数据,得到的数据大小为48.7 KB (49,890 字节)
class Program { static void Main(string[] args) { Person person = new Person { Address = new Address { Line1 = "Line1", Line2 = "Line2" }, Id = 1, Name = "Name" }; using (System.IO.Stream stream = System.IO.File.Create("Person.dat")) { Thrift.Transport.TTransport transport = new Thrift.Transport.TStreamTransport(null,stream); Thrift.Protocol.TProtocol protocol = new Thrift.Protocol.TBinaryProtocol(transport); List<Person> list = new List<Person>(); for (int i = 0; i < 1000; i++) { Person item = new Person { Address = new Address { Line1 = "Line1", Line2 = "Line2" }, Id = i, Name = "Name" + i }; item.Write(protocol); } } } }
对比上次的ProtoBuf大了不少,和C#的原始二进制序列化只差5kb。当然我这次对比几个数据也不能说明什么问题,只能做个大概了解。网络通信是个复杂的过程。序列化数据大小直接影响网络带宽,序列化与反序列化的效率也直接影响着服务器资源。当数据量大小上升到不同数量级时ProtoBuf和Thrift到底谁更占上风我就法验证了。
Thrift是个网络通信框架,提供了多种数据序列化方式,并且支持多种语言,通过Thrift的IDL语言定义过的契约可以在所有支持的语言上使用,且与平台无关,这就是他NB的地方。从Thrift的Tutorial看来对现将通信协议迁移也不是件事。