文章出處

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.calculator
namespace java com.contracts.calculator
namespace csharp Contracts

enum 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       try
24       {
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 發表自博客園,未經作者本人同意禁止任何形式的轉載,任何自動或人為的爬蟲轉載行為均為耍流氓。


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()