首页 > 代码库 > Apache Thrift 跨语言服务开发框架
Apache Thrift 跨语言服务开发框架
Apache Thrift 是一种支持多种编程语言的远程服务调用框架,由 Facebook 于 2007 年开发,并于 2008 年进入 Apache 开源项目管理。Apache Thrift 通过 IDL 来定义 RPC 的接口和数据类型,然后通过代码生成工具来生成针对不同编程语言的代码,目前支持 C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, OCaml, Delphi 等。
本文将从 C# 开发人员的角度介绍基于 Apache Thrift 的服务开发过程。
在文章《开源跨平台数据格式化框架概览》中主要介绍了各开源框架的数据格式化处理部分,但并没有描述消息的传输和 RPC 服务的定义。而实际上,Apache Thrift 与 Google Protocol Buffers 的一大不同点就是,Google Protocol Buffers 仅支持定义 RPC 服务接口,而 Apache Thrift 不仅支持定义 RPC 服务接口,还提供了支持 RPC 服务实现的完整的堆栈结构,并为 RPC 服务的 Server 端和 Client 端直接生成了可用代码。如下图描绘了 Thrift 的堆栈架构。
传输层(Transport)
传输层提供对网络 I/O 的抽象,通过 Transport 对客户端进行抽象,ServerTransport 对服务端进行抽象。
- TTransport
- TBufferedTransport
- TFramedTransport
- TStreamTransport
- TSocket
- TTLSSocket
- THttpClient
- TMemoryBuffer
- TNamedPipeClientTransport
- TServerTransport
- TServerSocket
- TTLSServerSocket
- TNamedPipeServerTransport
其实,看一眼 TSocket 的源代码就可以了解事情的真相了。
1 public TSocket(string host, int port, int timeout) 2 { 3 this.host = host; 4 this.port = port; 5 this.timeout = timeout; 6 7 InitSocket(); 8 } 9 10 private void InitSocket()11 {12 client = new TcpClient();13 client.ReceiveTimeout = client.SendTimeout = timeout;14 client.Client.NoDelay = true;15 }
协议层(Protocol)
协议层抽象了数据结构的定义,描述了如何组织数据以进行传输,包括 Encode 和 Decode 数据处理。所以,协议层负责实现数据的序列化和反序列化机制,例如序列化 Json, XML, Plain Text, Binary, Compact Binary 等。
协议层抽象了大量的读写操作接口,以供扩展。
1 public abstract void WriteMessageBegin(TMessage message); 2 public abstract void WriteMessageEnd(); 3 public abstract void WriteStructBegin(TStruct struc); 4 public abstract void WriteStructEnd(); 5 public abstract void WriteFieldBegin(TField field); 6 public abstract void WriteFieldEnd(); 7 public abstract void WriteFieldStop(); 8 public abstract void WriteMapBegin(TMap map); 9 public abstract void WriteMapEnd();10 public abstract void WriteListBegin(TList list);11 public abstract void WriteListEnd();12 public abstract void WriteSetBegin(TSet set);13 public abstract void WriteSetEnd();14 public abstract void WriteBool(bool b);15 public abstract void WriteByte(sbyte b);16 public abstract void WriteI16(short i16);17 public abstract void WriteI32(int i32);18 public abstract void WriteI64(long i64);19 public abstract void WriteDouble(double d);20 public virtual void WriteString(string s);21 public abstract void WriteBinary(byte[] b);22 23 public abstract TMessage ReadMessageBegin();24 public abstract void ReadMessageEnd();25 public abstract TStruct ReadStructBegin();26 public abstract void ReadStructEnd();27 public abstract TField ReadFieldBegin();28 public abstract void ReadFieldEnd();29 public abstract TMap ReadMapBegin();30 public abstract void ReadMapEnd();31 public abstract TList ReadListBegin();32 public abstract void ReadListEnd();33 public abstract TSet ReadSetBegin();34 public abstract void ReadSetEnd();35 public abstract bool ReadBool();36 public abstract sbyte ReadByte();37 public abstract short ReadI16();38 public abstract int ReadI32();39 public abstract long ReadI64();40 public abstract double ReadDouble();41 public virtual string ReadString();42 public abstract byte[] ReadBinary();
处理层(Processor)
Processor 封装了对输入输出流的读写操作,输入输出流也就代表着协议层处理的对象。Processor 接口定义的极其简单。
public interface TProcessor { bool Process(TProtocol iprot, TProtocol oprot); }
服务层(Server)
Server 将所有功能整合到一起:
- 创建一个 Transport;
- 创建 Transport 使用的 I/O Protocol;
- 为 I/O Protocol 创建 Processor;
- 启动服务,等待客户端的连接;
通过抽象 TServer 类来提供上述整合。
- TServer
- TSimpleServer -- Simple single-threaded server for testing.
- TThreadedServer -- Server that uses C# threads (as opposed to the ThreadPool) when handling requests.
- TThreadPoolServer -- Server that uses C# built-in ThreadPool to spawn threads when handling requests.
1 public TServer(TProcessor processor, 2 TServerTransport serverTransport, 3 TTransportFactory inputTransportFactory, 4 TTransportFactory outputTransportFactory, 5 TProtocolFactory inputProtocolFactory, 6 TProtocolFactory outputProtocolFactory, 7 LogDelegate logDelegate) 8 { 9 }10 11 public abstract void Serve();12 public abstract void Stop();
Thrift 实例
使用 Thrift 的过程:
- 编写类似于结构体的消息格式定义,使用类似于 IDL 的语言定义。
- 使用代码生成工具,生成目标语言代码。
- 在程序中直接使用这些代码。
这里我们从一个简单的 Thrift 实例开始,对 Thrift 服务的构建进行直观的展示。创建一个简单的 CalculatorService,通过 Calculate 接口来支持 "+ - x /" 简单的计算。Thrift 文件名为 calculator.thrift。
namespace cpp com.contracts.calculatornamespace java com.contracts.calculatornamespace csharp Contractsenum Operation { Add = 1, Subtract = 2, Multiply = 3, Divide = 4}exception DivideByZeroException { 1: string Message;}service CalculatorService { i32 Calculate(1:i32 x, 2:i32 y, 3:Operation op) throws (1:DivideByZeroException divisionByZero);}
上面的 calculator.thrift 实例中,
- namespace 定义针对不同编程语言的名空间或者包;
- enum 定义了 Calculate 需要支持的枚举类型;
- exception 定义了 Calculate 中可能发生的异常类型;
- service 定义了 CalculatorService 服务接口;
Apache Thrift 与 Google Protocol Buffers 的另一个不同点就是,Apache Thrift 支持对 Exception 的定义,使得在定义服务和实现服务接口时可以方便的处理服务端异常。
在命令行使用 Thrift 代码生成工具为 C# 编程语言生成代码:
thrift --gen csharp calculator.thrift
代码生成工具根据 calculator.thrift 中的定义会生成 3 个 C# 代码文件:
- CalculatorService.cs
- DivideByZeroException.cs
- Operation.cs
有了这些生成的代码文件,就可以设计服务端和客户端代码了。这里,创建 3 个 solution 文件:
- Contracts:存放生成的代码文件,共享给 Server 和 Client;
- Server:实现服务端代码,为客户端提供服务;
- Client:实现客户端代码,调用服务端;
Contracts 代码
由于在 calculator.thrift 文件中定义了 C# 的名空间:
namespace csharp Contracts
所以生成的代码的 namespace 即为 Contracts。
namespace Contracts{ public enum Operation { Add = 1, Subtract = 2, Multiply = 3, Divide = 4, }}
相应的,在 CalculatorService 文件中也生成了名为 Iface 的接口定义,也就是 Server 端需要为 Client 端实现的接口。
public partial class CalculatorService { public interface Iface { int Calculate(int x, int y, Operation op); } }
Server 端实现
为了实现 CalculatorService,需要实现一个 CalculatorServiceHandler 类来实现生成的 Contracts 中的 CalculatorService.Iface 接口。
1 public class CalculatorServiceHandler : CalculatorService.Iface 2 { 3 public int Calculate(int x, int y, Operation op) 4 { 5 switch (op) 6 { 7 case Operation.Add: 8 return x + y; 9 case Operation.Subtract:10 return x - y;11 case Operation.Multiply:12 return x * y;13 case Operation.Divide:14 if (y == 0)15 throw new Contracts.DivideByZeroException()16 {17 Message = "Cannot divide by zero."18 };19 return x / y;20 }21 22 throw new NotImplementedException();23 }24 }
上面代码中的 Operation.Divide 段,判断了当除数为 0 时将抛出 Contracts.DivideByZeroException 异常。
然后,需要启动 Server 来提供 CalculatorService 服务。将 CalculatorServiceHandler 类的实例传递给 CalculatorService.Processor 的构造函数,指定 Socket 绑定端口 8888,然后启动服务。
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 var handler = new CalculatorServiceHandler(); 6 var processor = new CalculatorService.Processor(handler); 7 8 TServerTransport transport = new TServerSocket(8888); 9 TServer server = new TThreadPoolServer(processor, transport);10 11 server.Serve();12 13 Console.ReadKey();14 }15 }
Client 端实现
Client 端消费 Server 端的代码更加简单,基本上 Thrift 都已提供了默认的实现,需要做的就是指定地址、端口和协议。
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 var transport = new TSocket("localhost", 8888); 6 var protocol = new TBinaryProtocol(transport); 7 var client = new CalculatorService.Client(protocol); 8 9 transport.Open();10 11 var test1 = client.Calculate(100, 2, Operation.Add);12 Console.WriteLine(test1);13 14 var test2 = client.Calculate(100, 2, Operation.Subtract);15 Console.WriteLine(test2);16 17 var test3 = client.Calculate(100, 2, Operation.Multiply);18 Console.WriteLine(test3);19 20 var test4 = client.Calculate(100, 2, Operation.Divide);21 Console.WriteLine(test4);22 23 try24 {25 var test5 = client.Calculate(100, 0, Operation.Divide);26 Console.WriteLine(test5);27 }28 catch (Contracts.DivideByZeroException ex)29 {30 Console.WriteLine(ex.Message);31 }32 33 Console.ReadKey();34 }35 }
然后,就可以启动 Server 端和 Client 端程序,实现简单的服务调用了。
本篇文章《Apache Thrift 跨语言服务开发框架》由 Dennis Gao 发表自博客园,未经作者本人同意禁止任何形式的转载,任何自动或人为的爬虫转载行为均为耍流氓。
Apache Thrift 跨语言服务开发框架