配色: 字号:
Apache Thrift 跨语言服务开发框架
2016-09-14 | 阅:  转:  |  分享 
  
ApacheThrift跨语言服务开发框架
ApacheThrift是一种支持多种编程语言的远程服务调用框架,由Facebook于2007年开发,并于2008年进入Apache开源项目管理。ApacheThrift通过IDL来定义RPC的接口和数据类型,然后通过代码生成工具来生成针对不同编程语言的代码,目前支持C++,Java,Python,PHP,Ruby,Erlang,Perl,Haskell,C#,Cocoa,JavaScript,Node.js,Smalltalk,OCaml,Delphi等。

本文将从C#开发人员的角度介绍基于ApacheThrift的服务开发过程。

在文章《开源跨平台数据格式化框架概览》中主要介绍了各开源框架的数据格式化处理部分,但并没有描述消息的传输和RPC服务的定义。而实际上,ApacheThrift与GoogleProtocolBuffers的一大不同点就是,GoogleProtocolBuffers仅支持定义RPC服务接口,而ApacheThrift不仅支持定义RPC服务接口,还提供了支持RPC服务实现的完整的堆栈结构,并为RPC服务的Server端和Client端直接生成了可用代码。
传输层(Transport)
传输层提供对网络I/O的抽象,通过Transport对客户端进行抽象,ServerTransport对服务端进行抽象。

TTransport
TBufferedTransport
TFramedTransport
TStreamTransport
TSocket
TTLSSocket
THttpClient
TMemoryBuffer
TNamedPipeClientTransport
TServerTransport
TServerSocket
TTLSServerSocket
TNamedPipeServerTransport
其实,看一眼TSocket的源代码就可以了解事情的真相了。

复制代码
1publicTSocket(stringhost,intport,inttimeout)
2{
3this.host=host;
4this.port=port;
5this.timeout=timeout;
6
7InitSocket();
8}
9
10privatevoidInitSocket()
11{
12client=newTcpClient();
13client.ReceiveTimeout=client.SendTimeout=timeout;
14client.Client.NoDelay=true;
15}
复制代码
协议层(Protocol)
协议层抽象了数据结构的定义,描述了如何组织数据以进行传输,包括Encode和Decode数据处理。所以,协议层负责实现数据的序列化和反序列化机制,例如序列化Json,XML,PlainText,Binary,CompactBinary等。

协议层抽象了大量的读写操作接口,以供扩展。

复制代码
1publicabstractvoidWriteMessageBegin(TMessagemessage);
2publicabstractvoidWriteMessageEnd();
3publicabstractvoidWriteStructBegin(TStructstruc);
4publicabstractvoidWriteStructEnd();
5publicabstractvoidWriteFieldBegin(TFieldfield);
6publicabstractvoidWriteFieldEnd();
7publicabstractvoidWriteFieldStop();
8publicabstractvoidWriteMapBegin(TMapmap);
9publicabstractvoidWriteMapEnd();
10publicabstractvoidWriteListBegin(TListlist);
11publicabstractvoidWriteListEnd();
12publicabstractvoidWriteSetBegin(TSetset);
13publicabstractvoidWriteSetEnd();
14publicabstractvoidWriteBool(boolb);
15publicabstractvoidWriteByte(sbyteb);
16publicabstractvoidWriteI16(shorti16);
17publicabstractvoidWriteI32(inti32);
18publicabstractvoidWriteI64(longi64);
19publicabstractvoidWriteDouble(doubled);
20publicvirtualvoidWriteString(strings);
21publicabstractvoidWriteBinary(byte[]b);
22
23publicabstractTMessageReadMessageBegin();
24publicabstractvoidReadMessageEnd();
25publicabstractTStructReadStructBegin();
26publicabstractvoidReadStructEnd();
27publicabstractTFieldReadFieldBegin();
28publicabstractvoidReadFieldEnd();
29publicabstractTMapReadMapBegin();
30publicabstractvoidReadMapEnd();
31publicabstractTListReadListBegin();
32publicabstractvoidReadListEnd();
33publicabstractTSetReadSetBegin();
34publicabstractvoidReadSetEnd();
35publicabstractboolReadBool();
36publicabstractsbyteReadByte();
37publicabstractshortReadI16();
38publicabstractintReadI32();
39publicabstractlongReadI64();
40publicabstractdoubleReadDouble();
41publicvirtualstringReadString();
42publicabstractbyte[]ReadBinary();
复制代码
处理层(Processor)
Processor封装了对输入输出流的读写操作,输入输出流也就代表着协议层处理的对象。Processor接口定义的极其简单。

publicinterfaceTProcessor
{
boolProcess(TProtocoliprot,TProtocoloprot);
}
服务层(Server)
Server将所有功能整合到一起:

创建一个Transport;
创建Transport使用的I/OProtocol;
为I/OProtocol创建Processor;
启动服务,等待客户端的连接;
通过抽象TServer类来提供上述整合。

TServer
TSimpleServer--Simplesingle-threadedserverfortesting.
TThreadedServer--Serverwww.wang027.comthatusesC#threads
(asopposedtotheThreadPool)whenhandlingrequests.
TThreadPoolServer--ServerthatusesC#built-inThreadPooltospawnthreadswhenhandlingrequests.
复制代码
1publicTServer(TProcessorprocessor,
2TServerTransportserverTransport,
3TTransportFactoryinputTransportFactory,
4TTransportFactoryoutputTransportFactory,
5TProtocolFactoryinputProtocolFactory,
6TProtocolFactoryoutputProtocolFactory,
7LogDelegatelogDelegate)
8{
9}
10
11publicabstractvoidServe();
12publicabstractvoidStop();
复制代码
Thrift实例
使用Thrift的过程:

编写类似于结构体的消息格式定义,使用类似于IDL的语言定义。
使用代码生成工具,生成目标语言代码。
在程序中直接使用这些代码。


这里我们从一个简单的Thrift实例开始,对Thrift服务的构建进行直观的展示。创建一个简单的CalculatorService,通过Calculate接口来支持"+-x/"简单的计算。Thrift文件名为calculator.thrift。

复制代码
namespacecppcom.contracts.calculator
namespacejavacom.contracts.calculator
namespacecsharpContracts

enumOperation{
Add=1,
Subtract=2,
Multiply=3,
Divide=4
}

exceptionDivideByZeroException{
1:stringMessage;
}

serviceCalculatorService{
i32Calculate(1:i32x,2:i32y,3:Operationop)throws(1:DivideByZeroExceptiondivisionByZero);
}
复制代码
上面的calculator.thrift实例中,

namespace定义针对不同编程语言的名空间或者包;
enum定义了Calculate需要支持的枚举类型;
exception定义了Calculate中可能发生的异常类型;
service定义了CalculatorService服务接口;
ApacheThrift与GoogleProtocolBuffers的另一个不同点就是,ApacheThrift支持对Exception的定义,使得在定义服务和实现服务接口时可以方便的处理服务端异常。

在命令行使用Thrift代码生成工具为C#编程语言生成代码:

thrift--gencsharpcalculator.thrift
代码生成工具根据calculator.thrift中的定义会生成3个C#代码文件:

CalculatorService.cs
DivideByZeroException.cs
Operation.cs
有了这些生成的代码文件,就可以设计服务端和客户端代码了。这里,创建3个solution文件:

Contracts:存放生成的代码文件,共享给Server和Client;
Server:实现服务端代码,为客户端提供服务;
Client:实现客户端代码,调用服务端;


Contracts代码
由于在calculator.thrift文件中定义了C#的名空间:

namespacecsharpContracts
所以生成的代码的namespace即为Contracts。

复制代码
namespaceContracts
{
publicenumOperation
{
Add=1,
Subtract=2,
Multiply=3,
Divide=4,
}
}
复制代码
相应的,在CalculatorService文件中也生成了名为Iface的接口定义,也就是Server端需要为Client端实现的接口。

publicpartialclassCalculatorService{
publicinterfaceIface{
intCalculate(intx,inty,Operationop);
}
}
Server端实现
为了实现CalculatorService,需要实现一个CalculatorServiceHandler类来实现生成的Contracts中的CalculatorService.Iface接口。

复制代码
1publicclassCalculatorServiceHandler:CalculatorService.Iface
2{
3publicintCalculate(intx,inty,Operationop)
4{
5switch(op)
6{
7caseOperation.Add:
8returnx+y;
9caseOperation.Subtract:
10returnx-y;
11caseOperation.Multiply:
12returnxy;
13caseOperation.Divide:
14if(y==0)
15thrownewContracts.DivideByZeroException()
16{
17Message="Cannotdividebyzero."
18};
19returnx/y;
20}
21
22thrownewNotImplementedException();
23}
24}
复制代码
上面代码中的Operation.Divide段,判断了当除数为0时将抛出Contracts.DivideByZeroException异常。

然后,需要启动Server来提供CalculatorService服务。将CalculatorServiceHandler类的实例传递给CalculatorService.Processor的构造函数,指定Socket绑定端口8888,然后启动服务。

复制代码
1classProgram
2{
3staticvoidMain(string[]args)
4{
5varhandler=newCalculatorServiceHandler();
6varprocessor=newCalculatorService.Processor(handler);
7
8TServerTransporttransport=newTServerSocket(8888);
9TServerserver=newTThreadPoolServer(processor,transport);
10
11server.Serve();
12
13Console.ReadKey();
14}
15}
复制代码
Client端实现
Client端消费Server端的代码更加简单,基本上Thrift都已提供了默认的实现,需要做的就是指定地址、端口和协议。

复制代码
1classProgram
2{
3staticvoidMain(string[]args)
4{
5vartransport=newTSocket("localhost",8888);
6varprotocol=newTBinaryProtocol(transport);
7varclient=newCalculatorService.Client(protocol);
8
9transport.Open();
10
11vartest1=client.Calculate(100,2,Operation.Add);
12Console.WriteLine(test1);
13
14vartest2=client.Calculate(100,2,Operation.Subtract);
15Console.WriteLine(test2);
16
17vartest3=client.Calculate(100,2,Operation.Multiply);
18Console.WriteLine(test3);
19
20vartest4=client.Calculate(100,2,Operation.Divide);
21Console.WriteLine(test4);
22
23try
24{
25vartest5=client.Calculate(100,0,Operation.Divide);
26Console.WriteLine(test5);
27}
28catch(Contracts.DivideByZeroExceptionex)
29{
30Console.WriteLine(ex.Message);
31}
32
33Console.ReadKey();
34}
35}
复制代码
然后,就可以启动Server端和Client端程序,实现简单的服务调用了。
献花(0)
+1
(本文系thedust79首藏)