Thrift RPC 框架指南
認識Thrift框架
thrift是一個軟件框架,用來進行可擴展且跨語言的服務的開發。它結合了功能強大的軟件堆棧和代碼生成引擎,以構建在 C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, and OCaml 這些編程語言間無縫結合的、高效的服務。
- thrift最初由facebook開發,07年四月開放源碼,08年5月進入apache孵化器。
- thrift允許定義一個簡單的定義文件中的數據類型和服務接口,以作爲輸入文件,編譯器生成代碼用來方便地生成RPC客戶端和服務器通信的無縫跨編程語言。
- 類似Thrift的工具,還有Avro、protocol buffer,但相對於Thrift來講,都沒有Thrift支持全面和使用廣泛。
Thrift自下到上可以分爲4層
- Server(single-threaded, event-driven etc)
- 服務器進程調度
- Processor(compiler generated)
- RPC接口處理函數分發,IDL定義接口的實現將掛接到這裏面
- Protocol (JSON, compact etc)
- 協議
- Transport(raw TCP, HTTP etc)
- 網絡傳輸
Thrift實際上是實現了C/S模式,通過代碼生成工具將接口定義文件生成服務器端和客戶端代碼(可以爲不同語言),從而實現服務端和客戶端跨語言的支持。用戶在Thirft描述文件中聲明自己的服務,這些服務經過編譯後會生成相應語言的代碼文件,然後用戶實現服務(客戶端調用服務,服務器端提服務)便可以了。其中protocol(協議層, 定義數據傳輸格式,可以爲二進制或者XML等)和transport(傳輸層,定義數據傳輸方式,可以爲TCP/IP傳輸,內存共享或者文件共享等)被用作運行時庫。
Thrift支持的傳輸及服務模型
支持的傳輸格式:
參數 | 描述 |
---|---|
TBinaryProtocol | 二進制格式 |
TCompactProtocol | 壓縮格式 |
TJSONProtocol | JSON格式 |
TSimpleJSONProtocol | 提供JSON只寫協議, 生成的文件很容易通過腳本語言解析。 |
TDebugProtocol | 使用易懂的可讀的文本格式,以便於debug |
支持的數據傳輸方式:
參數 | 描述 |
---|---|
TSocket | 阻塞式socker |
TFramedTransport | 以frame爲單位進行傳輸,非阻塞式服務中使用。 |
TFileTransport | 以文件形式進行傳輸。 |
TMemoryTransport | 將內存用於I/O. java實現時內部實際使用了簡單的ByteArrayOutputStream。 |
TZlibTransport | 使用zlib進行壓縮, 與其他傳輸方式聯合使用。當前無java實現。 |
支持的服務模型:
參數 | 描述 |
---|---|
TSimpleServer | 簡單的單線程服務模型,常用於測試 |
TThreadPoolServer | 多線程服務模型,使用標準的阻塞式IO。 |
TNonblockingServer | 多線程服務模型,使用非阻塞式IO(需使用TFramedTransport數據傳輸方式) |
Thrift 下載及安裝
如何獲取Thrift
- 官網:http://thrift.apache.org/
- golang的Thrift包:
go get git.apache.org/thrift.git/lib/go/thrift
如何安裝Thrift
mac下安裝Thrift,參考上一篇介紹
其他平臺安裝自行挖掘,呵呵。
安裝後通過
liuxinmingMacBook-Rro#:thrift -version
Thrift version 0.9.2 #看到這一行表示安裝成功
Golang、PHP通過Thrift調用
先發個官方各種語言DEMO地址 https://git1-us-west.apache.org/repos/asf?p=thrift.git;a=tree;f=tutorial;h=d69498f9f249afaefd9e6257b338515c0ea06390;hb=HEAD
Thrift的協議庫IDL文件
語法參考
- 參考資料
-
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: 有符號字節
- i16: 16位有符號整型
- i32: 32位有符號整型
- i64: 64位有符號整型
- double: 64位浮點型
- string: Encoding agnostic text or binary string
基本類型中基本都是有符號數,因爲有些語言沒有無符號數,所以Thrift不支持無符號整型。
特殊類型
- binary: Blob (byte array) a sequence of unencoded bytes
這是string類型的一種變形,主要是爲java使用
struct結構體
thrift中struct是定義爲一種對象,和面嚮對象語言的class差不多.,但是struct有以下一些約束:
struct不能繼承,但是可以嵌套,不能嵌套自己。
1. 其成員都是有明確類型
2. 成員是被正整數編號過的,其中的編號使不能重複的,這個是爲了在傳輸過程中編碼使用。
3. 成員分割符可以是逗號(,)或是分號(;),而且可以混用,但是爲了清晰期間,建議在定義中只使用一種,比如C++學習者可以就使用分號(;)。
4. 字段會有optional和required之分和protobuf一樣,但是如果不指定則爲無類型–可以不填充該值,但是在序列化傳輸的時候也會序列化進去,
optional是不填充則部序列化。
required是必須填充也必須序列化。
5. 每個字段可以設置默認值
6. 同一文件可以定義多個struct,也可以定義在不同的文件,進行include引入。
struct Work {
1: i32 num1 = 0,
2: i32 num2,
3: Operation op,
4: optional string comment,
}
容器(Containers)
Thrift3種可用容器類型:
- list(t): 元素類型爲t的有序表,容許元素重複。
- set(t):元素類型爲t的無序表,不容許元素重複。對應c++中的set,java中的HashSet,python中的set,php中沒有set,則轉換爲list類型。
- map(t,t): 鍵類型爲t,值類型爲t的kv對,鍵不容許重複。對用c++中的map, Java的HashMap, PHP 對應 array, Python/Ruby 的dictionary。
容器中元素類型可以是除了service外的任何合法Thrift類型(包括結構體和異常)。爲了最大的兼容性,map的key最好是thrift的基本類型,有些語言不支持複雜類型的key,JSON協議只支持那些基本類型的key。
容器都是同構容器,不失異構容器。
實現Thrift TDL文件
batu.thrift文件:
/**
* BatuThrift TDL
* @author liuxinming
* @time 2015.5.13
*/
namespace go batu.demo
namespace php batu.demo
/**
* 結構體定義
*/
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文件,生成相關代碼
thrift -r --gen go batu.thrift
thrift -r --gen php batu.thrift
thrift -r --gen php:server batu.thrift #生成PHP服務端接口代碼有所不一樣
Golang Service 實現
先按照golang的Thrift包
go get git.apache.org/thrift.git/lib/go/thrift
將Thrift生成的開發庫複製到GOPATH中
cp -r /Users/liuxinming/wwwroot/testphp/gen-go/batu $GOPATH/src
開發Go server端代碼(後面的代碼,目錄我們放在$GOPATH/src/thrift 中運行和演示)
test.go文件:
package main
import (
"batu/demo" #注意導入Thrift生成的接口包
"fmt"
"git.apache.org/thrift.git/lib/go/thrift"
"os"
"time"
)
const (
NetworkAddr = "127.0.0.1:9090" #監聽地址&端口
)
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.運行go服務端(監聽9090端口)
liuxinmingdeMacBook-Pro:thrift liuxinming$ go run test.go
thrift server in 127.0.0.1:9090
至此Go的Thrift服務端OK.
Golang Client 實現
goClient.go文件:
package main
import (
"batu/demo"
"fmt"
"git.apache.org/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("本次調用用時:%d-%d=%d毫秒\n", endTime, startTime, (endTime - startTime))
}
func currentTimeMillis() int64 {
return time.Now().UnixNano() / 1000000
}
goClient運行後結果:
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]
本次調用用時:1431583140857-1431583140855=2毫秒
PHP Client 實現
- 首先去下載Thrift,git庫地址爲:https://github.com/apache/thrift
- 新建項目目錄testphp,然後把thrift/lib/php/lib複製到testphp目錄下面
- 複製生成的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();//記錄開始時間
$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接口服務器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); #傳輸方式:這個要和服務器使用的一致 [go提供後端服務,迭代10000次2.6 ~ 3s完成]
$transport = new TFramedTransport($socket); #傳輸方式:這個要和服務器使用的一致[go提供後端服務,迭代10000次1.9 ~ 2.1s完成,比TBuffer快了點]
$protocol = new TBinaryProtocol($transport); #傳輸格式:二進制格式
$client = new \batu\demo\batuThriftClient($protocol);# 構造客戶端
$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); # 對服務器發起rpc調用
echo "PHPClient Call->".implode('',$result)."<br>";
}
$s = new \batu\demo\Article();
$s->id = 1;
$s->title = '插入一篇測試文章';
$s->content = '我就是這篇文章內容';
$s->author = 'liuxinming';
$client->put($s);
$s->id = 2;
$s->title = '插入二篇測試文章';
$s->content = '我就是這篇文章內容';
$s->author = 'liuxinming';
$client->put($s);
$endTime = getMillisecond();
echo "本次調用用時: :".$endTime."-".$startTime."=".($endTime-$startTime)."毫秒<br>";
function getMillisecond() {
list($t1, $t2) = explode(' ', microtime());
return (float)sprintf('%.0f', (floatval($t1) + floatval($t2)) * 1000);
}
$transport->close();
PHP運行後結果:
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
本次調用用時: :1431582183296-1431582183290=6毫秒
Go服務端看到打印數據:
–>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:插入一篇測試文章 Content:我就是這篇文章內容 Author:liuxinming
Article—>id: 2 Title:插入二篇測試文章 Content:我就是這篇文章內容 Author:liuxinming
完結,至此一個Golang的Thrift服務端 和 PHP的Thrift客戶端完成!