Apache Thrift是一个跨语言的服务部署框架,通过一个中间语言(IDL, 接口定义语言)来定义RPC的接口和数据类型,然后通过一个编译器生成不同语言的代码(支持C++,Java,Python,PHP, GO,Javascript,Ruby,Erlang,Perl, Haskell, C#等),并由生成的代码负责RPC协议层和传输层的实现。
在CentOS 6.5上安装Thrift
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | sudo yum -y update sudo yum -y groupinstall "Development Tools" #升级autoconf,必须2.65以上 wget http: //ftp .gnu.org /gnu/autoconf/autoconf-2 .69. tar .gz tar xvf autoconf-2.69. tar .gz cd autoconf-2.69 . /configure --prefix= /usr make sudo make install cd .. #升级automake必须1.14以上 wget http: //ftp .gnu.org /gnu/automake/automake-1 .14. tar .gz tar xvf automake-1.14. tar .gz cd automake-1.14 . /configure --prefix= /usr make sudo make install cd .. #升级bsion wget http: //ftp .gnu.org /gnu/bison/bison-2 .5.1. tar .gz tar xvf bison-2.5.1. tar .gz cd bison-2.5.1 . /configure --prefix= /usr make sudo make install cd .. #安装boost wget http: //sourceforge .net /projects/boost/files/boost/1 .55.0 /boost_1_55_0 . tar .gz tar xvf boost_1_55_0. tar .gz cd boost_1_55_0 . /bootstrap .sh sudo . /b2 install cd .. #安装thrift,编译会比较久,内存最好1024M以上 git clone https: //git-wip-us .apache.org /repos/asf/thrift .git cd thrift . /bootstrap .sh . /configure make sudo make install cd .. #查看版本 thrift -version #安装thrift_protocol扩展,仅支持二进制读写 cd thrift /lib/php/src/ext/thrift_protocol phpize . /configure sudo make sudo make install #这里不需要更改php.ini,已自动在/etc/php.d/thrift_protocol.ini里面添加 php -m | grep thrift |
Thrift的PHP类库位于thrift/lib/php/lib/Thrift目录下面,Thrift对于数据传输格式、数据传输方式,服务器模型均做了定义,方便自行扩展。
数据传输格式(protocol)是定义的了传输内容,对Thrift Type的打包解包,包括
- TBinaryProtocol,二进制格式,TBinaryProtocolAccelerated则是依赖于thrift_protocol扩展的快速打包解包。
- TCompactProtocol,压缩格式
- TJSONProtocol,JSON格式
- TMultiplexedProtocol,利用前三种数据格式与支持多路复用协议的服务端(同时提供多个服务,TMultiplexedProcessor)交互
数据传输方式(transport),定义了如何发送(write)和接收(read)数据,包括
- TBufferedTransport,缓存传输,写入数据并不立即开始传输,直到刷新缓存。
- TSocket,使用socket传输
- TFramedTransport,采用分块方式进行传输,具体传输实现依赖其他传输方式,比如TSocket
- TCurlClient,使用curl与服务端交互
- THttpClient,采用stream方式与HTTP服务端交互
- TMemoryBuffer,使用内存方式交换数据
- TPhpStream,使用PHP标准输入输出流进行传输
- TNullTransport,关闭数据传输
- TSocketPool在TSocket基础支持多个服务端管理(需要APC支持),自动剔除无效的服务器
- TNonblockingSocket,非官方实现非阻塞socket
服务模型,定义了当PHP作为服务端如何监听端口处理请求
- TForkingServer,采用子进程处理请求
- TSimpleServer,在TServerSocket基础上处理请求
- TNonblockingServer,基于libevent的非官方实现非阻塞服务端,与TNonblockingServerSocket,TNonblockingSocket配合使用
另外还定义了一些工厂,以便在Server模式下对数据传输格式和传输方式进行绑定
- TProtocolFactory,数据传输格式工厂类,对protocol的工厂化生产,包括TBinaryProtocolFactory,TCompactProtocolFactory,TJSONProtocolFactory
- TTransportFactory,数据传输方式工厂类,对transport的工厂化生产,作为server时,需要自行实现
- TStringFuncFactory,字符串处理工厂类
其他文件便是异常,字符串处理,自动加载器的定义等等。
现在开始编写一个简单接IDL文件HelloWorld.thrift
1 2 3 4 5 | namespace php Services.HelloWorld service HelloWorld { string sayHello(1:string name); } |
然后通过生成器生成PHP文件
1 2 | #不指明:server不生成processor。。 thrift --gen php:server HelloWorld.thrift |
生成文件在gen-php目录下面的Services/HelloWord/HelloWorld.php(目录与namesapce定义一致),这是个公共文件,服务端和客户端都需要包括它。其中客户端调用的代码(HelloWorldClient )已经生成好了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | //服务端需要继承该接口 interface HelloWorldIf { /** * @param string $name * @return string */ public function sayHello( $name ); } //提供给客户端调用的方法 class HelloWorldClient implements \Services\HelloWorld\HelloWorldIf { public function sayHello( $name ) { $this ->send_sayHello( $name ); return $this ->recv_sayHello(); } public function send_sayHello( $name ) { } public function recv_sayHello() { } } //HelloWord类sayHello方法参数读取 class HelloWorld_sayHello_args { } //HelloWord类sayHello方法结果写入 class HelloWorld_sayHello_result { } //作为服务端才会生成 class HelloWorldProcessor { } |
而服务端的服务实现代码则需要继承HelloWorldIf 实现代码HelloWorldHandler.php
1 2 3 4 5 6 7 8 9 | <?php namespace Services\HelloWorld; class HelloWorldHandler implements HelloWorldIf { public function sayHello( $name ) { return "Hello $name" ; } } |
编写服务端代码Server.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | <?php namespace Services\HelloWorld; error_reporting (E_ALL); define( 'THRIFT_ROOT' , __DIR__. '/../../../' ); require_once THRIFT_ROOT. 'Thrift/ClassLoader/ThriftClassLoader.php' ; use Thrift\ClassLoader\ThriftClassLoader; $loader = new ThriftClassLoader(); $loader ->registerNamespace( 'Thrift' , THRIFT_ROOT); $loader ->registerDefinition( 'Service' , THRIFT_ROOT. '/gen-php' ); $loader ->register(); use Thrift\Exception\TException; use Thrift\Factory\TBinaryProtocolFactory; use Thrift\Factory\TBufferedTransportFactory; use Thrift\Server\TServerSocket; use Thrift\Server\TSimpleServer; //use Thrift\Server\TNonblockingServerSocket; //use Thrift\Server\TNonblockingServer; //use Thrift\Protocol\TBinaryProtocol; //use Thrift\Transport\TPhpStream; //use Thrift\Transport\TBufferedTransport; try { require_once 'HelloWorldHandler.php' ; $handler = new \Services\HelloWorld\HelloWorldHandler(); $processor = new \Services\HelloWorld\HelloWorldProcessor( $handler ); $transportFactory = new TBufferedTransportFactory(); $protocolFactory = new TBinaryProtocolFactory(true, true); //作为cli方式运行,监听端口,官方实现 $transport = new TServerSocket( 'localhost' , 9090); $server = new TSimpleServer( $processor , $transport , $transportFactory , $transportFactory , $protocolFactory , $protocolFactory ); $server ->serve(); //作为cli方式运行,非阻塞方式监听,基于libevent实现,非官方实现 //$transport = new TNonblockingServerSocket('localhost', 9090); //$server = new TNonblockingServer($processor, $transport, $transportFactory, $transportFactory, $protocolFactory, $protocolFactory); //$server->serve(); //客户端和服务端在同一个输入输出流上 //使用方式 //1) cli 方式:php Client.php | php Server.php //2) cgi 方式:利用Apache或nginx监听http请求,调用php-fpm处理,将请求转换为PHP标准输入输出流 //$transport = new TBufferedTransport(new TPhpStream(TPhpStream::MODE_R | TPhpStream::MODE_W)); //$protocol = new TBinaryProtocol($transport, true, true); //$transport->open(); //$processor->process($protocol, $protocol); //$transport->close(); } catch (TException $tx ) { print 'TException: ' . $tx ->getMessage(). "\n" ; } |
服务端创建的步骤:
- 首先初始化服务提供者handler
- 然后利用该handler初始化自动生成的processor
- 初始化数据传输方式transport
- 利用该传输方式初始化数据传输格式protocol
- 开始服务
编写客户端代码Client.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | <?php namespace Services\HelloWorld; error_reporting (E_ALL); define( 'THRIFT_ROOT' , __DIR__. '/../../../' ); require_once THRIFT_ROOT. 'Thrift/ClassLoader/ThriftClassLoader.php' ; use Thrift\ClassLoader\ThriftClassLoader; $loader = new ThriftClassLoader(); $loader ->registerNamespace( 'Thrift' , THRIFT_ROOT); $loader ->registerDefinition( 'Service' , THRIFT_ROOT. '/gen-php' ); $loader ->register(); //use Thrift\Transport\TPhpStream; use Thrift\Protocol\TBinaryProtocol; use Thrift\Transport\TSocket; use Thrift\Transport\TBufferedTransport; use Thrift\Exception\TException; try { //仅在与服务端处于同一输出输出流有用 //使用方式:php Client.php | php Server.php //$transport = new TBufferedTransport(new TPhpStream(TPhpStream::MODE_R | TPhpStream::MODE_W)); //socket方式连接服务端 //数据传输格式和数据传输方式与服务端一一对应 //如果服务端以http方式提供服务,可以使用THttpClient/TCurlClient数据传输方式 $transport = new TBufferedTransport( new TSocket( 'localhost' , 9090)); $protocol = new TBinaryProtocol( $transport ); $client = new \Services\HelloWorld\HelloWorldClient( $protocol ); $transport ->open(); //同步方式进行交互 $recv = $client ->sayHello( 'Courages' ); echo "\n sayHello11dd:" . $recv . " \n" ; //异步方式进行交互 $client ->send_sayHello( 'Us' ); echo "\n send_sayHello \n" ; $recv = $client ->recv_sayHello(); echo "\n recv_sayHello:" . $recv . " \n" ; $transport ->close(); } catch (TException $tx ) { print 'TException: ' . $tx ->getMessage(). "\n" ; } |
客户端调用的步骤:
- 初始化数据传输方式transport,与服务端对应
- 利用该传输方式初始化数据传输格式protocol,与服务端对应
- 实例化自动生成的Client对象
- 开始调用
在终端上运行
1 2 3 4 5 6 7 8 9 | #以cli方式运行TPhpStream #php Client.php | php Server.php #先运行Server.php #要不然会报错:TException: TSocket: Could not connect to localhost:9090 (Connection refused [111]) php Server.php #在另外一个终端运行 php Client.ph |
官方给的例子,PHP作为服务端是以web方式进行提供的,在cli方式下并不能运行。
Thrift作为一个跨语言的服务框架,方便不同语言、模块之间互相调用,解耦服务逻辑代码,拓展了PHP的处理能力(如与Hbase交互),使得WEB架构更具弹性。与基于 SOAP 消息格式的 Web Service和基于 JSON 消息格式的 RESTful 服务不同,Thrif数据传输格式默认采用二进制传格式,对 XML 和 JSON 体积更小,但对于服务端的CPU占用比JSON、XML要高。PHP虽然有thrift_protocol扩展,但仅仅作为二进制数据传输格式化使用,其他文件的加载仍然为PHP,需要更多的开销。
如果由PHP来做为Thrift的服务端,仅仅这样子做仍然是不够的,Thrift仅仅实现的数据定义和传输,未实现RPC架构
- 需要避免重复加载各类文件,是否做成PHP扩展
- 数据传输格式和方式是否适需要自行扩展
- 客户端要能够自动连可使用的服务端,剔除失效的服务器
- 服务端需要处理客户端并发情况,是否多进程/异步处理
- 服务端需要监控服务是否正常
workerman-thrift-rpc对这些问题进行了解决,基于thrift提供了一个可靠性的RPC框架。对客户端和服务端的调用做了封装,提供统一入口,利用workerman做socket中转,当客户端发出请求时,将给socket转给服务端使用,提供服务。workerman-json-rpc与workerman-thrift-rpc类似,采用异步(分步)收发,但简单多了,更像是一种约定。数据格式,发送时仅发送class,function,parameters三个参数,接收时,仅code,msg,data三个返回值,在格式约束及跨语言上,需要自行处理;不需要thrift那样依赖于生成器所生成的文件,客户端完全独立于服务端。
注:以上示例使用修改过的代码,附上代码:thrift。
参考链接:
Apache Thrift – 可伸缩的跨语言服务开发框架
Thirft框架介绍
Apache Thrift
Building Apache Thrift on CentOS 6.5
PHP Tutorial
Creating a public API with Apache Thrift
hadoop + Hbase + thrift + php 安裝設定與程式設計
php实现的thrift socket server
Our own “Hello World!”