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了

序列化之Apache Thrift

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);
                }
            }

        }
    }

Demo下载

对比上次的ProtoBuf大了不少,和C#的原始二进制序列化只差5kb。当然我这次对比几个数据也不能说明什么问题,只能做个大概了解。网络通信是个复杂的过程。序列化数据大小直接影响网络带宽,序列化与反序列化的效率也直接影响着服务器资源。当数据量大小上升到不同数量级时ProtoBuf和Thrift到底谁更占上风我就法验证了。

Thrift是个网络通信框架,提供了多种数据序列化方式,并且支持多种语言,通过Thrift的IDL语言定义过的契约可以在所有支持的语言上使用,且与平台无关,这就是他NB的地方。从Thrift的Tutorial看来对现将通信协议迁移也不是件事。

相关文章: