zoukankan      html  css  js  c++  java
  • 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.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 发表自博客园,未经作者本人同意禁止任何形式的转载,任何自动或人为的爬虫转载行为均为耍流氓。

  • 相关阅读:
    1066 Bash 游戏
    1070 Bash 游戏 V4
    codevs 2928 你缺什么
    分块、线段树练习
    Father Christmas flymouse
    codevs 2494 Vani和Cl2捉迷藏
    codevs 2144 砝码称重2
    国王游戏
    codevs 1664 清凉冷水
    2015ACM/ICPC亚洲区沈阳站 Pagodas
  • 原文地址:https://www.cnblogs.com/gaochundong/p/apache_thrift.html
Copyright © 2011-2022 走看看