Thrift RPC 框架指南
認(rèn)識(shí)Thrift框架
thrift是一個(gè)軟件框架,用來(lái)進(jìn)行可擴(kuò)展且跨語(yǔ)言的服務(wù)的開(kāi)發(fā)。它結(jié)合了功能強(qiáng)大的軟件堆棧和代碼生成引擎,以構(gòu)建在 C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, and OCaml 這些編程語(yǔ)言間無(wú)縫結(jié)合的、高效的服務(wù)。
- thrift最初由facebook開(kāi)發(fā),07年四月開(kāi)放源碼,08年5月進(jìn)入apache孵化器。
- thrift允許定義一個(gè)簡(jiǎn)單的定義文件中的數(shù)據(jù)類型和服務(wù)接口,以作為輸入文件,編譯器生成代碼用來(lái)方便地生成RPC客戶端和服務(wù)器通信的無(wú)縫跨編程語(yǔ)言。
- 類似Thrift的工具,還有Avro、protocol buffer,但相對(duì)于Thrift來(lái)講,都沒(méi)有Thrift支持全面和使用廣泛。
Thrift自下到上可以分為4層
-
Server(single-threaded, event-driven etc)
- 服務(wù)器進(jìn)程調(diào)度
-
Processor(compiler generated)
- RPC接口處理函數(shù)分發(fā),IDL定義接口的實(shí)現(xiàn)將掛接到這里面
-
Protocol (JSON, compact etc)
- 協(xié)議
-
Transport(raw TCP, HTTP etc)
- 網(wǎng)絡(luò)傳輸
Thrift實(shí)際上是實(shí)現(xiàn)了C/S模式,通過(guò)代碼生成工具將接口定義文件生成服務(wù)器端和客戶端代碼(可以為不同語(yǔ)言),從而實(shí)現(xiàn)服務(wù)端和客戶端跨語(yǔ)言的支持。用戶在Thirft描述文件中聲明自己的服務(wù),這些服務(wù)經(jīng)過(guò)編譯后會(huì)生成相應(yīng)語(yǔ)言的代碼文件,然后用戶實(shí)現(xiàn)服務(wù)(客戶端調(diào)用服務(wù),服務(wù)器端提服務(wù))便可以了。其中protocol(協(xié)議層, 定義數(shù)據(jù)傳輸格式,可以為二進(jìn)制或者XML等)和transport(傳輸層,定義數(shù)據(jù)傳輸方式,可以為T(mén)CP/IP傳輸,內(nèi)存共享或者文件共享等)被用作運(yùn)行時(shí)庫(kù)。
Thrift支持的傳輸及服務(wù)模型
支持的傳輸格式:
| 參數(shù) |
描述 |
| TBinaryProtocol |
二進(jìn)制格式 |
| TCompactProtocol |
壓縮格式 |
| TJSONProtocol |
JSON格式 |
| TSimpleJSONProtocol |
提供JSON只寫(xiě)協(xié)議, 生成的文件很容易通過(guò)腳本語(yǔ)言解析。 |
| TDebugProtocol |
使用易懂的可讀的文本格式,以便于debug |
支持的數(shù)據(jù)傳輸方式:
| 參數(shù) |
描述 |
| TSocket |
阻塞式socker |
| TFramedTransport |
以frame為單位進(jìn)行傳輸,非阻塞式服務(wù)中使用。 |
| TFileTransport |
以文件形式進(jìn)行傳輸。 |
| TMemoryTransport |
將內(nèi)存用于I/O. java實(shí)現(xiàn)時(shí)內(nèi)部實(shí)際使用了簡(jiǎn)單的ByteArrayOutputStream。 |
| TZlibTransport |
使用zlib進(jìn)行壓縮, 與其他傳輸方式聯(lián)合使用。當(dāng)前無(wú)java實(shí)現(xiàn)。 |
支持的服務(wù)模型:
| 參數(shù) |
描述 |
| TSimpleServer |
簡(jiǎn)單的單線程服務(wù)模型,常用于測(cè)試 |
| TThreadPoolServer |
多線程服務(wù)模型,使用標(biāo)準(zhǔn)的阻塞式IO。 |
| TNonblockingServer |
多線程服務(wù)模型,使用非阻塞式IO(需使用TFramedTransport數(shù)據(jù)傳輸方式) |
Thrift 下載及安裝
如何獲取Thrift
- 官網(wǎng):http://thrift./
- golang的Thrift包:
go get git./thrift.git/lib/go/thrift
如何安裝Thrift
mac下安裝Thrift,參考上一篇介紹
其他平臺(tái)安裝自行挖掘,呵呵。
安裝后通過(guò)
liuxinmingMacBook-Rro#:thrift -version
Thrift version 0.9.2 #看到這一行表示安裝成功
Golang、PHP通過(guò)Thrift調(diào)用
先發(fā)個(gè)官方各種語(yǔ)言DEMO地址 https://git1-us-west./repos/asf?p=thrift.git;a=tree;f=tutorial;h=d69498f9f249afaefd9e6257b338515c0ea06390;hb=HEAD
Thrift的協(xié)議庫(kù)IDL文件
語(yǔ)法參考
- 參考資料
-
http://www.cnblogs.com/tianhuilove/archive/2011/09/05/2167669.html
- http://my.oschina.net/helight/blog/195015
基本類型
- bool: 布爾值 (true or false), one byte
- byte: 有符號(hào)字節(jié)
- i16: 16位有符號(hào)整型
- i32: 32位有符號(hào)整型
- i64: 64位有符號(hào)整型
- double: 64位浮點(diǎn)型
- string: Encoding agnostic text or binary string
基本類型中基本都是有符號(hào)數(shù),因?yàn)橛行┱Z(yǔ)言沒(méi)有無(wú)符號(hào)數(shù),所以Thrift不支持無(wú)符號(hào)整型。
特殊類型
- binary: Blob (byte array) a sequence of unencoded bytes
這是string類型的一種變形,主要是為java使用
struct結(jié)構(gòu)體
thrift中struct是定義為一種對(duì)象,和面向?qū)ο笳Z(yǔ)言的class差不多.,但是struct有以下一些約束:
struct不能繼承,但是可以嵌套,不能嵌套自己。
1. 其成員都是有明確類型
2. 成員是被正整數(shù)編號(hào)過(guò)的,其中的編號(hào)使不能重復(fù)的,這個(gè)是為了在傳輸過(guò)程中編碼使用。
3. 成員分割符可以是逗號(hào)(,)或是分號(hào)(;),而且可以混用,但是為了清晰期間,建議在定義中只使用一種,比如C++學(xué)習(xí)者可以就使用分號(hào)(;)。
4. 字段會(huì)有optional和required之分和protobuf一樣,但是如果不指定則為無(wú)類型–可以不填充該值,但是在序列化傳輸?shù)臅r(shí)候也會(huì)序列化進(jìn)去,
optional是不填充則部序列化。
required是必須填充也必須序列化。
5. 每個(gè)字段可以設(shè)置默認(rèn)值
6. 同一文件可以定義多個(gè)struct,也可以定義在不同的文件,進(jìn)行include引入。
struct Work {
1: i32 num1 = 0,
2: i32 num2,
3: Operation op,
4: optional string comment,
}
容器(Containers)
Thrift3種可用容器類型:
- list(t): 元素類型為t的有序表,容許元素重復(fù)。
- set(t):元素類型為t的無(wú)序表,不容許元素重復(fù)。對(duì)應(yīng)c++中的set,java中的HashSet,python中的set,php中沒(méi)有set,則轉(zhuǎn)換為list類型。
- map(t,t): 鍵類型為t,值類型為t的kv對(duì),鍵不容許重復(fù)。對(duì)用c++中的map, Java的HashMap, PHP 對(duì)應(yīng) array, Python/Ruby 的dictionary。
容器中元素類型可以是除了service外的任何合法Thrift類型(包括結(jié)構(gòu)體和異常)。為了最大的兼容性,map的key最好是thrift的基本類型,有些語(yǔ)言不支持復(fù)雜類型的key,JSON協(xié)議只支持那些基本類型的key。
容器都是同構(gòu)容器,不失異構(gòu)容器。
實(shí)現(xiàn)Thrift TDL文件
batu.thrift文件:
/**
* BatuThrift TDL
* @author liuxinming
* @time 2015.5.13
*/
namespace go batu.demo
namespace php batu.demo
/**
* 結(jié)構(gòu)體定義
*/
struct Article{
1: i32 id,
2: string title,
3: string content,
4: string author,
}
const map<string,string> MAPCONSTANT = {'hello':'world', 'goodnight':'moon'}
service batuThrift {
list<string> CallBack(1:i64 callTime, 2:string name, 3:map<string, string> paramMap),
void put(1: Article newArticle),
}
編譯IDL文件,生成相關(guān)代碼
thrift -r --gen go batu.thrift
thrift -r --gen php batu.thrift
thrift -r --gen php:server batu.thrift #生成PHP服務(wù)端接口代碼有所不一樣
Golang Service 實(shí)現(xiàn)
先按照golang的Thrift包
go get git./thrift.git/lib/go/thrift
將Thrift生成的開(kāi)發(fā)庫(kù)復(fù)制到GOPATH中
cp -r /Users/liuxinming/wwwroot/testphp/gen-go/batu $GOPATH/src
開(kāi)發(fā)Go server端代碼(后面的代碼,目錄我們放在$GOPATH/src/thrift 中運(yùn)行和演示)
test.go文件:
package main
import (
"batu/demo" #注意導(dǎo)入Thrift生成的接口包
"fmt"
"git./thrift.git/lib/go/thrift"
"os"
"time"
)
const (
NetworkAddr = "127.0.0.1:9090" #監(jiān)聽(tīng)地址&端口
)
type batuThrift struct {
}
func (this *batuThrift) CallBack(callTime int64, name string, paramMap map[string]string) (r []string, err error) {
fmt.Println("-->from client Call:", time.Unix(callTime, 0).Format("2006-01-02 15:04:05"), name, paramMap)
r = append(r, "key:"+paramMap["a"]+" value:"+paramMap["b"])
return
}
func (this *batuThrift) Put(s *demo.Article) (err error) {
fmt.Printf("Article--->id: %d\tTitle:%s\tContent:%t\tAuthor:%d\n", s.Id, s.Title, s.Content, s.Author)
return nil
}
func main() {
transportFactory := thrift.NewTFramedTransportFactory(thrift.NewTTransportFactory())
protocolFactory := thrift.NewTBinaryProtocolFactoryDefault()
//protocolFactory := thrift.NewTCompactProtocolFactory()
serverTransport, err := thrift.NewTServerSocket(NetworkAddr)
if err != nil {
fmt.Println("Error!", err)
os.Exit(1)
}
handler := &batuThrift{}
processor := demo.NewBatuThriftProcessor(handler)
server := thrift.NewTSimpleServer4(processor, serverTransport, transportFactory, protocolFactory)
fmt.Println("thrift server in", NetworkAddr)
server.Serve()
}
4.運(yùn)行g(shù)o服務(wù)端(監(jiān)聽(tīng)9090端口)
liuxinmingdeMacBook-Pro:thrift liuxinming$ go run test.go
thrift server in 127.0.0.1:9090
至此Go的Thrift服務(wù)端OK.
Golang Client 實(shí)現(xiàn)
goClient.go文件:
package main
import (
"batu/demo"
"fmt"
"git./thrift.git/lib/go/thrift"
"net"
"os"
"strconv"
"time"
)
const (
HOST = "127.0.0.1"
PORT = "9090"
)
func main() {
startTime := currentTimeMillis()
transportFactory := thrift.NewTFramedTransportFactory(thrift.NewTTransportFactory())
protocolFactory := thrift.NewTBinaryProtocolFactoryDefault()
transport, err := thrift.NewTSocket(net.JoinHostPort(HOST, PORT))
if err != nil {
fmt.Fprintln(os.Stderr, "error resolving address:", err)
os.Exit(1)
}
useTransport := transportFactory.GetTransport(transport)
client := demo.NewBatuThriftClientFactory(useTransport, protocolFactory)
if err := transport.Open(); err != nil {
fmt.Fprintln(os.Stderr, "Error opening socket to "+HOST+":"+PORT, " ", err)
os.Exit(1)
}
defer transport.Close()
for i := 0; i < 10; i++ {
paramMap := make(map[string]string)
paramMap["a"] = "batu.demo"
paramMap["b"] = "test" + strconv.Itoa(i+1)
r1, _ := client.CallBack(time.Now().Unix(), "go client", paramMap)
fmt.Println("GOClient Call->", r1)
}
model := demo.Article{1, "Go第一篇文章", "我在這里", "liuxinming"}
client.Put(&model)
endTime := currentTimeMillis()
fmt.Printf("本次調(diào)用用時(shí):%d-%d=%d毫秒\n", endTime, startTime, (endTime - startTime))
}
func currentTimeMillis() int64 {
return time.Now().UnixNano() / 1000000
}
goClient運(yùn)行后結(jié)果:
liuxinmingdeMacBook-Pro:thrift liuxinming$ go run goClient.go
GOClient Call-> [key:batu.demo value:test1]
GOClient Call-> [key:batu.demo value:test2]
GOClient Call-> [key:batu.demo value:test3]
GOClient Call-> [key:batu.demo value:test4]
GOClient Call-> [key:batu.demo value:test5]
GOClient Call-> [key:batu.demo value:test6]
GOClient Call-> [key:batu.demo value:test7]
GOClient Call-> [key:batu.demo value:test8]
GOClient Call-> [key:batu.demo value:test9]
GOClient Call-> [key:batu.demo value:test10]
本次調(diào)用用時(shí):1431583140857-1431583140855=2毫秒
PHP Client 實(shí)現(xiàn)
- 首先去下載Thrift,git庫(kù)地址為:https://github.com/apache/thrift
- 新建項(xiàng)目目錄testphp,然后把thrift/lib/php/lib復(fù)制到testphp目錄下面
- 復(fù)制生成的gen-php到testphp目錄下面
- 客戶端代碼
<?php
/**
* Thrift RPC - PHPClient
* @author liuxinming
* @time 2015.5.13
*/
namespace batu\testDemo;
header("Content-type: text/html; charset=utf-8");
$startTime = getMillisecond();//記錄開(kāi)始時(shí)間
$ROOT_DIR = realpath(dirname(__FILE__).'/');
$GEN_DIR = realpath(dirname(__FILE__).'/').'/gen-php';
require_once $ROOT_DIR . '/Thrift/ClassLoader/ThriftClassLoader.php';
use Thrift\ClassLoader\ThriftClassLoader;
use Thrift\Protocol\TBinaryProtocol;
use Thrift\Transport\TSocket;
use Thrift\Transport\TSocketPool;
use Thrift\Transport\TFramedTransport;
use Thrift\Transport\TBufferedTransport;
$loader = new ThriftClassLoader();
$loader->registerNamespace('Thrift',$ROOT_DIR);
$loader->registerDefinition('batu\demo', $GEN_DIR);
$loader->register();
$thriftHost = '127.0.0.1'; //UserServer接口服務(wù)器IP
$thriftPort = 9090; //UserServer端口
$socket = new TSocket($thriftHost,$thriftPort);
$socket->setSendTimeout(10000);#Sets the send timeout.
$socket->setRecvTimeout(20000);#Sets the receive timeout.
//$transport = new TBufferedTransport($socket); #傳輸方式:這個(gè)要和服務(wù)器使用的一致 [go提供后端服務(wù),迭代10000次2.6 ~ 3s完成]
$transport = new TFramedTransport($socket); #傳輸方式:這個(gè)要和服務(wù)器使用的一致[go提供后端服務(wù),迭代10000次1.9 ~ 2.1s完成,比TBuffer快了點(diǎn)]
$protocol = new TBinaryProtocol($transport); #傳輸格式:二進(jìn)制格式
$client = new \batu\demo\batuThriftClient($protocol);# 構(gòu)造客戶端
$transport->open();
$socket->setDebug(TRUE);
for($i=1;$i<11;$i++){
$item = array();
$item["a"] = "batu.demo";
$item["b"] = "test"+$i;
$result = $client->CallBack(time(),"php client",$item); # 對(duì)服務(wù)器發(fā)起rpc調(diào)用
echo "PHPClient Call->".implode('',$result)."<br>";
}
$s = new \batu\demo\Article();
$s->id = 1;
$s->title = '插入一篇測(cè)試文章';
$s->content = '我就是這篇文章內(nèi)容';
$s->author = 'liuxinming';
$client->put($s);
$s->id = 2;
$s->title = '插入二篇測(cè)試文章';
$s->content = '我就是這篇文章內(nèi)容';
$s->author = 'liuxinming';
$client->put($s);
$endTime = getMillisecond();
echo "本次調(diào)用用時(shí): :".$endTime."-".$startTime."=".($endTime-$startTime)."毫秒<br>";
function getMillisecond() {
list($t1, $t2) = explode(' ', microtime());
return (float)sprintf('%.0f', (floatval($t1) + floatval($t2)) * 1000);
}
$transport->close();
PHP運(yùn)行后結(jié)果:
PHPClient Call->key:batu.demo value:1
PHPClient Call->key:batu.demo value:2
PHPClient Call->key:batu.demo value:3
PHPClient Call->key:batu.demo value:4
PHPClient Call->key:batu.demo value:5
PHPClient Call->key:batu.demo value:6
PHPClient Call->key:batu.demo value:7
PHPClient Call->key:batu.demo value:8
PHPClient Call->key:batu.demo value:9
PHPClient Call->key:batu.demo value:10
本次調(diào)用用時(shí): :1431582183296-1431582183290=6毫秒
Go服務(wù)端看到打印數(shù)據(jù):
–>from client Call: 2015-05-13 22:43:03 php client map[a:batu.demo b:1]
–>from client Call: 2015-05-13 22:43:03 php client map[a:batu.demo b:2]
–>from client Call: 2015-05-13 22:43:03 php client map[a:batu.demo b:3]
–>from client Call: 2015-05-13 22:43:03 php client map[a:batu.demo b:4]
–>from client Call: 2015-05-13 22:43:03 php client map[a:batu.demo b:5]
–>from client Call: 2015-05-13 22:43:03 php client map[a:batu.demo b:6]
–>from client Call: 2015-05-13 22:43:03 php client map[a:batu.demo b:7]
–>from client Call: 2015-05-13 22:43:03 php client map[b:8 a:batu.demo]
–>from client Call: 2015-05-13 22:43:03 php client map[a:batu.demo b:9]
–>from client Call: 2015-05-13 22:43:03 php client map[a:batu.demo b:10]
Article—>id: 1 Title:插入一篇測(cè)試文章 Content:我就是這篇文章內(nèi)容 Author:liuxinming
Article—>id: 2 Title:插入二篇測(cè)試文章 Content:我就是這篇文章內(nèi)容 Author:liuxinming
完結(jié),至此一個(gè)Golang的Thrift服務(wù)端 和 PHP的Thrift客戶端完成!