以前工作中也用过一些远程服务调用,比如 Webservice,PHP 中常用的 SOAP,XML-RPC,还有 jsonp 和 restful 之类的。最近公司工作中,用到了 thrift 这种远程服务调用框架,总结和记录下。

Thrift 是 Facebook 实现的一种高效的、支持多种编程语言的远程服务调用的框架。它通过一个中间语言 IDL(接口定义语言)来定义 RPC 的接口和数据类型,然后通过一个编译器生成不同语言的代码并由生成的代码负责 RPC 协议层和传输层的实现。

Thrift 的架构图

thrif

黄色是用户实现的业务逻辑,褐色部分是根据 Thrift 定义的服务接口描述文件生成的客户端和服务器端代码框架,红色部分是根据 Thrift 文件生成代码实现数据的读写操作。红色部分以下是 Thrift 的传输体系、协议以及底层 I/O 通信,使用 Thrift 可以很方便的定义一个服务并且选择不同的传输协议和传输层而不用重新生成代码。用户在 Thirft 描述文件中声明自己的服务,这些服务经过编译后会生成相应语言的代码文件,然后用户实现服务(客户端调用服务,服务器端提服务)。其中 protocol(协议层, 定义数据传输格式,可以为二进制或者 XML 等)和 transport(传输层,定义数据传输方式,可以为 TCP/IP 传输,内存共享或者文件共享等)被用作运行时库。

传输协议 在传输协议上总体划分为文本和二进制 ,为节约带宽,提高传输效率,一般情况下使用二进制类型的传输协议为多数.

  • TBinaryProtocol — 二进制编码格式进行数据传输
  • TCompactProtocol — 高效率的、密集的二进制编码格式进行数据传输
  • TJSONProtocol — 使用 JSON 的数据编码协议进行数据传输
  • TSimpleJSONProtocol — 只提供 JSON 只写的协议,适用于通过脚本语言解析
  • TDebugProtocol – 使用易懂的可读的文本格式,以便于 debug

数据传输

  • TSocket — 使用阻塞式 I/O 进行传输,是最常见的模式
  • TFramedTransport — 使用非阻塞方式,按块的大小进行传输
  • TNonblockingTransport — 使用非阻塞方式,用于构建异步客户端
  • TMemoryTransport – 将内存用于 I/O
  • TZlibTransport – 使用 zlib 进行压缩, 与其他传输方式联合使用
  • TFileTransport – 以文件形式进行传输

服务端类型

  • TSimpleServer — 单线程服务器端使用标准的阻塞式 I/O
  • TThreadPoolServer —— 多线程服务器端使用标准的阻塞式 I/O
  • TNonblockingServer —— 多线程服务器端使用非阻塞式 I/O

安装

Thrift 部署服务

一般的步骤为:编写服务说明,保存到.thrift 文件->编译.thrift 文件,生成相应的语言源代码->编写 client 端和 server 端代码。

语法 数据类型 Thrift 脚本可定义的数据类型包括以下几种类型: 基本类型: bool:布尔值,true 或 false byte:8 位有符号整数 i16:16 位有符号整数 i32:32 位有符号整数 i64:64 位有符号整数 double:64 位浮点数 string:未知编码文本或二进制字符串 结构体类型: struct:定义公共的对象,类似于 C 语言中的结构体定义 容器类型: List:一系列 t1 类型的元素组成的有序表,元素可以重复 Set:一系列 t1 类型的元素组成的无序表,元素唯一 Map:key/value 对(key 的类型是 t1 且 key 唯一,value 类型是 t2) 异常类型: exception 异常在语法和功能上类似于结构体,它在语义上不同于结构体—当定义一个 RPC 服务时,开发者可能需要声明一个远程方法抛出一个异常。 服务类型: service:对应服务的类

注释 Thrfit 支持 shell 注释风格,C/C++语言中单行或者多行注释风格

# This is a valid comment.

/*
* This is a multi-line comment.
*/

// C++/Java style single-line comments work just as well.

命名空间 Thrift 中的命名空间同 C++中的 namespace 和 java 中的 package 类似,它们均提供了一种组织(隔离)代码的方式。因为每种语言均有自己的命名空间定义方式(如 python 中有 module),thrift 允许开发者针对特定语言定义 namespace:

namespace cpp com.example.project
namespace java com.example.project
namespace php App.Api

文件包含 Thrift 允许 thrift 文件包含,用户需要使用 thrift 文件名作为前缀访问被包含的对象

include "tweet.thrift"

定义常量 Thrift 允许用户定义常量,复杂的类型和结构体可使用 JSON 形式表示

const i32 INT_CONST = 1234;
const map<string,string> MAP_CONST = {"hello": "world", "goodnight": "moon"}

定义结构体

struct Tweet {

1: required i32 userId; // 每个域有一个唯一的,正整数标识符

2: required string userName; // 每个域可以标识为required或者optional(也可以不注明)

3: required string text;

4: optional Location loc; // 结构体可以包含其他结构体

16: optional string language = "english" // 域可以有缺省值

}

struct Location { // 一个thrift中可定义多个结构体,并存在引用关系

1: required double latitude;

2: required double longitude;

}

定义服务 Thrift 编译器会根据选择的目标语言为 server 产生服务接口代码,为 client 产生桩代码

//“Twitter”与“{”之间需要有空格!!!
service Twitter {

// 方法定义方式类似于C语言中的方式,它有一个返回值,一系列参数和可选的异常

// 列表. 注意,参数列表和异常列表定义方式与结构体中域定义方式一致.

void ping(), // 函数定义可以使用逗号或者分号标识结束

bool postTweet(1:Tweet tweet); // 参数可以是基本类型或者结构体,参数是只读的(const),不可以作为返回值!!!

TweetSearchResult searchTweets(1:string query); // 返回值可以是基本类型或者结构体

// ”oneway”标识符表示client发出请求后不必等待回复(非阻塞)直接进行下面的操作,

// ”oneway”方法的返回值必须是void

oneway void zip() // 返回值可以是void

}

函数中参数列表的定义方式与 struct 完全一样,Service 支持继承,一个 service 可使用 extends 关键字继承另一个 service

实例 直接用官方的 tutorial,服务端用 python,客户端用 php shared.thrift tutorial.thrift 服务端

[code]
pip install thrift
thrift -r --gen py tutorial.thrift

[/code]

新建server.py

import sys, glob
sys.path.append('gen-py')

from tutorial import Calculator
from tutorial.ttypes import *

from shared.ttypes import SharedStruct

from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TBinaryProtocol
from thrift.server import TServer

class CalculatorHandler:
def __init__(self):
self.log = {}

def ping(self):
print 'ping()'

def add(self, n1, n2):
print 'add(%d,%d)' % (n1, n2)
return n1+n2

def calculate(self, logid, work):
print 'calculate(%d, %r)' % (logid, work)

if work.op == Operation.ADD:
val = work.num1 + work.num2
elif work.op == Operation.SUBTRACT:
val = work.num1 - work.num2
elif work.op == Operation.MULTIPLY:
val = work.num1 * work.num2
elif work.op == Operation.DIVIDE:
if work.num2 == 0:
x = InvalidOperation()
x.what = work.op
x.why = 'Cannot divide by 0'
raise x
val = work.num1 / work.num2
else:
x = InvalidOperation()
x.what = work.op
x.why = 'Invalid operation'
raise x

log = SharedStruct()
log.key = logid
log.value = '%d' % (val)
self.log[logid] = log

return val

def getStruct(self, key):
print 'getStruct(%d)' % (key)
return self.log[key]

def zip(self):
print 'zip()'

handler = CalculatorHandler()
processor = Calculator.Processor(handler)
transport = TSocket.TServerSocket(port=9090)
tfactory = TTransport.TBufferedTransportFactory()
pfactory = TBinaryProtocol.TBinaryProtocolFactory()

server = TServer.TSimpleServer(processor, transport, tfactory, pfactory)

# You could do one of these for a multithreaded server
#server = TServer.TThreadedServer(processor, transport, tfactory, pfactory)
#server = TServer.TThreadPoolServer(processor, transport, tfactory, pfactory)

print 'Starting the server...'
server.serve()
print 'done.'

客户端 拷贝 thrift 包中的 lib/php/lib 到当前目录下

thrift -r --gen php tutorial.thrift

新建 client.php

#!/usr/bin/env php
<?php
namespace tutorial\php;

error_reporting(E_ALL);

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

use Thrift\ClassLoader\ThriftClassLoader;

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

$loader = new ThriftClassLoader();
$loader->registerNamespace('Thrift', __DIR__ . '/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)) {
    $socket = new THttpClient('127.0.0.1', 8080, '/php/PhpServer.php');
} else {
    $socket = new TSocket('127.0.0.1', 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();

运行结果

ryan@localhost-6:~/WebRoot/test/thrift-blog$ python server.py
Starting the server...
ping()
add(1,1)
calculate(1, Work(comment=None, num1=1, num2=0, op=4))
calculate(1, Work(comment=None, num1=15, num2=10, op=2))
getStruct(1)

参考

Apache Thrift – 可伸缩的跨语言服务开发框架

Thirft 框架介绍

转载请注明: 转载自Ryan 是菜鸟 | LNMP 技术栈笔记

如果觉得本篇文章对您十分有益,何不 打赏一下

谢谢打赏

本文链接地址: Thrift 应用总结

知识共享许可协议 本作品采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可