# 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
1. 官網:[http://thrift.apache.org/](http://thrift.apache.org/)
1. golang的Thrift包:
~~~
go get git.apache.org/thrift.git/lib/go/thrift
~~~
### 如何安裝Thrift
mac下安裝Thrift,[參考上一篇介紹](http://blog.csdn.net/liuxinmingcode/article/details/45567241)
其他平臺安裝自行挖掘,呵呵。
安裝后通過
~~~
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](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://www.cnblogs.com/tianhuilove/archive/2011/09/05/2167669.html)
[http://my.oschina.net/helight/blog/195015](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 實現
1.先按照golang的Thrift包
> go get git.apache.org/thrift.git/lib/go/thrift
2.將Thrift生成的開發庫復制到GOPATH中
> cp -r /Users/liuxinming/wwwroot/testphp/gen-go/batu $GOPATH/src
3.開發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 實現
1. 首先去下載Thrift,git庫地址為:[https://github.com/apache/thrift](https://github.com/apache/thrift)
1. 新建項目目錄testphp,然后把thrift/lib/php/lib復制到testphp目錄下面
1. 復制生成的gen-php到testphp目錄下面
1. 客戶端代碼
~~~
<?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客戶端完成!
- 前言
- golang學習(一)之安裝
- Go語言學習二:Go基礎(變量、常量、數值類型、字符串、錯誤類型)
- Go語言學習三:Go基礎(iota,array,slice,map,make,new)
- Go語言學習四:struct類型
- Ubuntu 14.04/CentOS 6.5中安裝GO LANG(GO語言)
- Mac OS 安裝golang
- Mac install Thrift
- Thrift RPC 使用指南實戰(附golang&amp;PHP代碼)
- golang net/http包使用
- 冒泡排序Bubble sort-golang
- 快速排序Quick sort - golang
- Go語言學習:Channel是什么?
- Golang的select/非緩沖的Channel實例詳解
- Golang time包的定時器/斷續器
- Golang同步:鎖的使用案例詳解
- Golang同步:條件變量和鎖組合使用
- Golang同步:原子操作使用
- Golang之bytes.buffer
- Golang之字符串格式化
- Golang之反射reflect包
- Go語言配置文件解析器,類似于Windows下的INI文件.