thrift nginx php,关于php使用thrift做服务端开发的那些事

蓝夕
2023-12-01

php使用thrift做服务端开发

thrift采用接口描述语言定义和创建服务,用二进制格式传输数据,体积更小、效率更高,对于高并发、数据量大和多语言的环境有更好的支持。

Apache Thrift是啥?

Apache Thrift是FaceBook开发的一套可扩展的、跨语言的服务调用框架。简单的说就是先定义一个配置文件,不同的语言可以利用thrift基于这个配置文件生成各自语言的服务端,不管客户端用什么语言,都可以调用,也就是说基于thrift协议用java可以调用php的服务。目前支持C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, OCaml and Delphi等语言之间相互调用。

相对于传统的xml和json等数据传输方式来说,thrift采用接口描述语言定义和创建服务,用二进制格式传输数据,体积更小、效率更高,对于高并发、数据量大和多语言的环境有更好的支持。

thrift安装环境要求

g++ 4.2

boost 1.53.0

lex and yacc(基于flex和bison)

如果没安装lex和yacc的话要先安装,否则会make失败,提示lex和yacc command not found错误(一般的机器貌似都没安,Ubuntu用apt-get install flex bision即可)。

安装thrift

下载最新版thrift:

wget http://www.apache.org/dyn/closer.cgi?path=/thrift/0.9.3/thrift-0.9.3.tar.gz

tar xvf thrift-0.9.3.tar.gz

cd thrift-0.9.3

2.创建configure文件

// 创建./configure文件

./bootstrap.sh

// 配置并安装

./configure

make

// 检测是否有问题,如果机子没有安装python和java等可能会报错,不过本文主要讲php,安了php环境就行

make check

make install

编译选项

使用./configure –help可以查看选项

如果想禁用某个语言,可以用./configure –without-java

thrift for php安装环境要求

php版本>5.0,因为TBinaryProtocol协议用到了pack()和unpack()函数来序列化数据

需要安装APC扩展,因为TSocketPool这个类用到了apc_fetch()和apc_store()函数进行apc缓存操作。

php使用thrift的时候,除了要将thrift/lib/php/lib里的基础文件copy到项目目录下,还需要将根据配置文件生成的php文件也copy到packages文件夹下,并引入到项目中,这个后续会详细讲。

类库说明

数据传输格式(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支持),自动剔除无效的服务器

开发流程

1、定义IDL(Interface description language)接口描述文件,后缀.thrift

IDL规范:http://thrift.apache.org/docs/idl

thrift types:http://thrift.apache.org/docs/types

2、服务端代码开发

3、客户端编写接入代码

IDL:

1.tutorial.thrift

include "shared.thrift"

namespace php tutorial

typedef i32 MyInteger

const i32 INT32CONSTANT = 9853

const map MAPCONSTANT = {'hello':'world', 'goodnight':'moon'}

enum Operation {

ADD = 1,

SUBTRACT = 2,

MULTIPLY = 3,

DIVIDE = 4

}

struct Work {

1: i32 num1 = 0,

2: i32 num2,

3: Operation op,

4: optional string comment,

}

exception InvalidOperation {

1: i32 whatOp,

2: string why

}

service Calculator extends shared.SharedService {

void ping(),

i32 add(1:i32 num1, 2:i32 num2),

i32 calculate(1:i32 logid, 2:Work w) throws (1:InvalidOperation ouch),

oneway void zip()

}

2.shared.thrift

namespace php shared

struct SharedStruct {

1: i32 key

2: string value

}

service SharedService {

SharedStruct getStruct(1: i32 key)

}

php服务端

namespace tutorial/php;

ini_set('display_errors',1);

error_reporting(E_ALL);

// 引入类自动加载文件

require_once __DIR__.'/../../lib/php/lib/Thrift/ClassLoader/ThriftClassLoader.php';

// 载入自动加载类

use Thrift/ClassLoader/ThriftClassLoader;

// 定义根据.thrift文件生成的php文件

$GEN_DIR = realpath(dirname(__FILE__).'/..').'/gen-php';

// 注册thrift服务

$loader = new ThriftClassLoader();

$loader->registerNamespace('Thrift', __DIR__ . '/../../lib/php/lib');

$loader->registerDefinition('shared', $GEN_DIR);

$loader->registerDefinition('tutorial', $GEN_DIR);

$loader->register();

if (php_sapi_name() == 'cli') {

ini_set("display_errors", "stderr");

}

use Thrift/Protocol/TBinaryProtocol; // 二进制格式打包解包

use Thrift/Transport/TPhpStream; // php流输入输出

use Thrift/Transport/TBufferedTransport; // 使用缓存

// 开始服务端逻辑

class CalculatorHandler implements /tutorial/CalculatorIf {

protected $log = array();

public function ping() {

error_log("ping()");

}

// 相加

public function add($num1, $num2) {

error_log("add({$num1}, {$num2})");

return $num1 + $num2;

}

// 枚举计算类型

public function calculate($logid, /tutorial/Work $w) {

error_log("calculate({$logid}, {{$w->op}, {$w->num1}, {$w->num2}})");

switch ($w->op) {

case /tutorial/Operation::ADD:

$val = $w->num1 + $w->num2;

break;

case /tutorial/Operation::SUBTRACT:

$val = $w->num1 - $w->num2;

break;

case /tutorial/Operation::MULTIPLY:

$val = $w->num1 * $w->num2;

break;

case /tutorial/Operation::DIVIDE:

if ($w->num2 == 0) {

$io = new /tutorial/InvalidOperation();

$io->whatOp = $w->op;

$io->why = "Cannot divide by 0";

throw $io;

}

$val = $w->num1 / $w->num2;

break;

default:

$io = new /tutorial/InvalidOperation();

$io->whatOp = $w->op;

$io->why = "Invalid Operation";

throw $io;

}

$log = new /shared/SharedStruct();

$log->key = $logid;

$log->value = (string)$val;

$this->log[$logid] = $log;

return $val;

}

public function getStruct($key) {

error_log("getStruct({$key})");

// This actually doesn't work because the PHP interpreter is

// restarted for every request.

//return $this->log[$key];

return new /shared/SharedStruct(array("key" => $key, "value" => "PHP is stateless!"));

}

public function zip() {

error_log("zip()");

}

};

header('Content-Type', 'application/x-thrift');

if (php_sapi_name() == 'cli') {

echo "/r/n";

}

$handler = new CalculatorHandler();

$processor = new /tutorial/CalculatorProcessor($handler);

// 客户端和服务端在同一个输入输出流上

//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();

//作为cli方式运行,非阻塞方式监听,基于libevent实现,非官方实现

//$transportFactory = new TBufferedTransportFactory();

//$protocolFactory = new TBinaryProtocolFactory(true, true);

//$transport = new TNonblockingServerSocket('localhost', 9090);

//$server = new TNonblockingServer($processor, $transport, $transportFactory, $transportFactory, $protocolFactory, $protocolFactory);

//$server->serve();

//作为cli方式运行,监听端口,官方实现

//$transportFactory = new TBufferedTransportFactory();

//$protocolFactory = new TBinaryProtocolFactory(true, true);

//$transport = new TServerSocket('localhost', 9090);

//$server = new TSimpleServer($processor, $transport, $transportFactory, $transportFactory, $protocolFactory, $protocolFactory);

//$server->serve();

php客户端

namespace tutorial/php;

error_reporting(E_ALL);

require_once __DIR__.'/../../lib/php/lib/Thrift/ClassLoader/ThriftClassLoader.php';

use Thrift/ClassLoader/ThriftClassLoader;

$GEN_DIR = realpath(dirname(__FILE__).'/..').'/gen-php';

$loader = new ThriftClassLoader();

$loader->registerNamespace('Thrift', __DIR__ . '/../../lib/php/lib');

$loader->registerDefinition('shared', $GEN_DIR);

$loader->registerDefinition('tutorial', $GEN_DIR);

$loader->register();

use Thrift/Protocol/TBinaryProtocol;

use Thrift/Transport/TSocket;

use Thrift/Transport/THttpClient;

use Thrift/Transport/TBufferedTransport;

use Thrift/Exception/TException;

// 以上配置跟服务端类似

try {

if (array_search('--http', $argv)) {

// 使用http方式连接

$socket = new THttpClient('localhost', 8080, '/php/PhpServer.php');

} else {

// 使用socket连接

$socket = new TSocket('localhost', 9090);

}

$transport = new TBufferedTransport($socket, 1024, 1024);

$protocol = new TBinaryProtocol($transport);

$client = new /tutorial/CalculatorClient($protocol);

$transport->open();

$client->ping();

print "ping()/n";

$sum = $client->add(1,1);

print "1+1=$sum/n";

// 调试异常情况

$work = new /tutorial/Work();

$work->op = /tutorial/Operation::DIVIDE;

$work->num1 = 1;

$work->num2 = 0;

try {

$client->calculate(1, $work);

print "Whoa! We can divide by zero?/n";

} catch (/tutorial/InvalidOperation $io) {

print "InvalidOperation: $io->why/n";

}

$work->op = /tutorial/Operation::SUBTRACT;

$work->num1 = 15;

$work->num2 = 10;

$diff = $client->calculate(1, $work);

print "15-10=$diff/n";

$log = $client->getStruct(1);

print "Log: $log->value/n";

$transport->close();

} catch (TException $tx) {

print 'TException: '.$tx->getMessage()."/n";

}

输出:

// php client.php --http

ping()

1+1=2

InvalidOperation: Cannot divide by 0

15-10=5

Log: PHP is stateless!

 类似资料: