Apache Thrift是什么?
The Apache Thrift software framework, for scalable cross-language services development, combines a software stack with a code generation engine to build services that work efficiently and seamlessly between C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, OCaml and Delphi and other languages.
Apache Thrift軟件框架用于可擴(kuò)展的跨語(yǔ)言服務(wù)開(kāi)發(fā)遵班,將軟件堆棧與代碼生成引擎相結(jié)合擒抛,構(gòu)建可在C ++,Java,Python,PHP,Ruby疾掰,Erlang,Perl徐紧,Haskell静檬,C#之間高效無(wú)縫工作的服務(wù), Cocoa并级,JavaScript拂檩,Node.js,Smalltalk嘲碧,OCaml和Delphi等語(yǔ)言稻励。
Thrift最初由facebook研發(fā),主要用于各個(gè)服務(wù)之間的RPC通信,支持跨語(yǔ)言望抽,支持的語(yǔ)言有C++加矛,Java,Python煤篙,PHP斟览,Ruby,Erlang辑奈,PErl苛茂,Haskell,C#鸠窗,Cocoa味悄,JavaScript,Node.js,
Smalltalk,and OCaml都支持塌鸯。
Thrift是一個(gè)典型的CS(客戶端/服務(wù)端)結(jié)構(gòu),客戶端和服務(wù)端可以使用不同的語(yǔ)言開(kāi)發(fā)唐片。既然客戶端和服務(wù)器端能使用不同的語(yǔ)言開(kāi)發(fā)丙猬,那么一定就要有一種中間語(yǔ)言來(lái)關(guān)聯(lián)客戶端和服務(wù)器端的語(yǔ)言。這種語(yǔ)言就是IDL(Interface Description Language)费韭。
Thrift不支持無(wú)符號(hào)類型茧球,因?yàn)楹芏嗑幊陶Z(yǔ)言不存在無(wú)符號(hào)類型,比如說(shuō)java星持。一個(gè)RPC框架如果支持多種語(yǔ)言抢埋,那么這個(gè)RPC框架所支持的數(shù)據(jù)類型一定是這個(gè)RPC框架多語(yǔ)言支持的數(shù)據(jù)類型的交集。
Apache Thrift的一些概念
Thrift支持的數(shù)據(jù)類型
bool: 布爾類型(true或者false)
byte: 有符號(hào)字節(jié)
i16: 16位有符號(hào)整數(shù)
i32: 32位有符號(hào)整數(shù)
i64: 64位有符號(hào)整數(shù)
double: 64位浮點(diǎn)數(shù)
string: 字符串
集合中的元素可以是除了service之外的任何類型督暂,包括exception揪垄。這邊的service和exception是Thrift支持的組件,Thrift支持三種組件逻翁,分別是Structs(結(jié)構(gòu)體)饥努,Service(客戶端和服務(wù)端通信的接口),exception(客戶端和服務(wù)端通信接口拋出的異常)
結(jié)構(gòu)體(struct)
就像C語(yǔ)言一樣八回,Thrift支持struct類型酷愧,目的就是將一些數(shù)據(jù)聚合在一起,方便傳輸管理缠诅,struct的定義形式如下:
struct People{
1:string name;
2:i32 age;
3:string gender;
}
枚舉(enum)
枚舉的定義形式和Java的Enum定義類似
enum Gender{
MALE,
FEMALE
}
異常(exception)
Thrift支持自定義exception溶浴,規(guī)則與struct一樣
exception RequestException{
1: i32 code;
2: string reason;
}
服務(wù)(service)
Thrift定義服務(wù)相當(dāng)于Java中創(chuàng)建Interface一樣,創(chuàng)建的service經(jīng)過(guò)代碼生成命令之后就會(huì)生成客戶端和服務(wù)器端的框架代碼管引。定義形式如下:
service HelloWorldService{
//service中定義的函數(shù)士败,相當(dāng)于Java Interface中定義的方法
string doAction(1:string name,2:i32 age);
}
類型定義
Thrift支持類似C++一樣的typedef定義,比如我們對(duì)i32不熟悉汉匙,我們就使用int類代替i32拱烁,比如我們對(duì)i64不熟悉生蚁,我們就使用long代替i64
typedef i32 int
typedef i64 long
常量(const)
Thrift也支持常量定義,使用const關(guān)鍵字
const i32 MIN_GATE=30
const string MY_WEBSITE="http://facebook.com"
命名空間
Thrift的命名空間相當(dāng)于java中的package的意思戏自,主要目的是組織代碼邦投。Thift使用關(guān)鍵字namespave定義命名空間:
namespace java com.test.thift.demo
格式是:namespace 語(yǔ)言名 路徑
文件包含
Thrift也支持文件包含,相當(dāng)于C/C++中的include擅笔,java中的import志衣。使用關(guān)鍵字include定義:
include "global.thift"
注釋
Thrift注釋方式支持shell風(fēng)格的注釋,支持C/C++風(fēng)格的注釋猛们,即#和開(kāi)頭的語(yǔ)句都當(dāng)作注釋念脯,/**/包裹的語(yǔ)句也是注釋。
可選與必選
Thrift提供兩個(gè)關(guān)鍵字required弯淘,optional绿店,分別用于表示對(duì)應(yīng)的字段是必填的還是可選的
struct People{
1:required string name;
2:optional i32 age;
}
Thrift傳輸格式(協(xié)議)
- TBinaryProtocal-二進(jìn)制格式
- TCompactProtocol-壓縮格式
- TJSONProtocol-JSON格式
- TSimpleJSONProtocol-提供JSON只寫(xiě)協(xié)議,生成的文件很容易通過(guò)腳本語(yǔ)言解析
- TDebugProtocol-使用易懂的可讀文本格式庐橙,以便于debug
Thrift數(shù)據(jù)傳輸方式
- TSocket-阻塞式socket
- TFramedTransport-以frame為單位進(jìn)行傳輸假勿,非阻塞式服務(wù)中使用
- TFileTransport-以文件形式進(jìn)行傳輸
- TMemoryInputTransport-將內(nèi)存用語(yǔ)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)浆竭。
Thrift支持的服務(wù)模型
- TThreadPoolServer - 簡(jiǎn)單的單線程服務(wù)模型浸须,常用于測(cè)試
- TSimpleServer - 多線程服務(wù)模型,使用標(biāo)準(zhǔn)的阻塞式IO
- TNonblockingServer - 多線程服務(wù)模型邦泄,使用非阻塞式IO(需要使用TFramedTransport數(shù)據(jù)傳輸方式)
- THsHaServer-THsHa引入了線程池去處理删窒,其模型把讀寫(xiě)任務(wù)放到線程池處理;Half-sync/Half-async的處理模式,Half-async是在處理IO事件上
(accept/read/write io),Half-sync用于handler對(duì)rpc的同步處理虎韵。
注意
一般在工作中使用TCompactProtocol傳輸協(xié)議易稠,使用TFramedTransport數(shù)據(jù)傳輸方式,使用THsHaServer服務(wù)模型包蓝。
Thrift支持的容器類型
list:一系列由T類型的數(shù)據(jù)組成的有序列表驶社,元素可以重復(fù)。
set:一系列由T類型的數(shù)據(jù)組成的無(wú)序集合测萎,元素不可重復(fù)亡电。
map:一個(gè)字典結(jié)構(gòu),key為k類型硅瞧,value為V類型份乒,相當(dāng)于java中的HashMap
以上集合容器都可以使用泛型的。
Thrift工作原理
如何實(shí)現(xiàn)多語(yǔ)言之間的通信?
數(shù)據(jù)傳輸實(shí)現(xiàn)socket(多種語(yǔ)言均支持)或辖,數(shù)據(jù)再以特定的格式(String等)發(fā)送瘾英,接收方語(yǔ)言進(jìn)行解析。
Apache Thrift定義的thrift的文件(IDL)颂暇,由thrift文件(IDL)生成雙方語(yǔ)言的接口缺谴,model,在生成的model以及接口中會(huì)有解碼編碼的代碼耳鸯。
Thrift 架構(gòu)
Thrift的安裝
官方網(wǎng)站提供的下載安裝地址湿蛔,根據(jù)不同的操作系統(tǒng)選擇自己的安裝方式
mac電腦推薦使用更簡(jiǎn)單的安裝方式Homebrew工具,
Homebrew官方網(wǎng)址
先安裝Homebrew:
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
安裝成功之后县爬,檢測(cè)Homebrew:
? ~ which brew
/usr/local/bin/brew
安裝Apache Thrift
? ~ brew install thrift
安裝完成之后:
查看具體安裝信息:
? ~ which thrift
/usr/local/bin/thrift
? ~ thrift --version
Thrift version 0.10.0
thrift --help
快速入門
定義idl文件
先定義一個(gè)idl文件(接口描述文件)阳啥,定義了結(jié)構(gòu)體(struct),異常(exception)和服務(wù)(service)
namespace java thrift.generated
typedef i16 short
typedef i32 int
typedef i64 long
typedef bool boolean
typedef string String
struct Person{
1: optional String username,
2: optional int age,
3: optional boolean married
}
exception DataException{
1: optional String message,
2: optional String callStack,
3: optional String date
}
service PersonService{
Person getPersonByUsername(1: required String username) throws (1: DataException dateException),
void savePerson(1: required Person person) throws (1: DataException dataException)
}
使用thrift編譯器生成編譯文件
thrift --gen java src/thrift/data.thrift
將生成的代碼復(fù)制到src/main目錄下财喳,發(fā)現(xiàn)報(bào)錯(cuò)察迟,加入java的依賴pom文件:
<dependency>
<groupId>org.apache.thrift</groupId>
<artifactId>libthrift</artifactId>
<version>0.10.0</version>
</dependency>
使用java編寫(xiě)客戶端與服務(wù)器端的代碼
編寫(xiě)接口實(shí)現(xiàn)類,實(shí)際開(kāi)發(fā)中放在服務(wù)端
import org.apache.thrift.TException;
import thrift.generated.DataException;
import thrift.generated.Person;
import thrift.generated.PersonService;
public class PersonServiceImpl implements PersonService.Iface{
@Override
public Person getPersonByUsername(String username) throws DataException, TException {
System.out.println("Got client Param:" + username);
Person person = new Person();
person.setUsername(username);
person.setAge(32);
person.setMarried(true);
return person;
}
@Override
public void savePerson(Person person) throws DataException, TException {
System.out.println("Got Client Param: ");
System.out.println(person.getUsername());
System.out.println(person.getAge());
System.out.println(person.isMarried());
}
}
服務(wù)器端代碼:
import org.apache.thrift.TProcessorFactory;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.server.THsHaServer;
import org.apache.thrift.server.TServer;
import org.apache.thrift.transport.TFramedTransport;
import org.apache.thrift.transport.TNonblockingServerSocket;
import thrift.generated.PersonService;
public class ThriftServer {
public static void main(String[] args) throws Exception{
TNonblockingServerSocket socket = new TNonblockingServerSocket(8899);
THsHaServer.Args arg = new THsHaServer.Args(socket).minWorkerThreads(2).maxWorkerThreads(4);
//范型就是實(shí)現(xiàn)的接收類
PersonService.Processor<PersonServiceImpl> processor = new PersonService.Processor<>(new PersonServiceImpl());
//表示協(xié)議層次(壓縮協(xié)議)
arg.protocolFactory(new TCompactProtocol.Factory());
//表示傳輸層次
arg.transportFactory(new TFramedTransport.Factory());
arg.processorFactory(new TProcessorFactory(processor));
//半同步半異步的server
TServer server = new THsHaServer(arg);
System.out.println("Thrift Server started!");
//死循環(huán)耳高,永遠(yuǎn)不會(huì)退出
server.serve();
}
}
客戶端代碼:
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TFastFramedTransport;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
import thrift.generated.Person;
import thrift.generated.PersonService;
//服務(wù)端的協(xié)議和客戶端的協(xié)議要一致
public class ThriftClient {
public static void main(String[] args) {
TTransport tTransport = new TFastFramedTransport(new TSocket("localhost",8899),600);
TProtocol tProtocol = new TCompactProtocol(tTransport);
PersonService.Client client = new PersonService.Client(tProtocol);
try{
tTransport.open();
Person person = client.getPersonByUsername("張三");
System.out.println(person.getUsername());
System.out.println(person.getAge());
System.out.println(person.isMarried());
System.out.println("............");
Person person2 = new Person();
person2.setUsername("李四");
person2.setAge(30);
person2.setMarried(true);
client.savePerson(person2);
}catch (Exception ex){
throw new RuntimeException(ex.getMessage(),ex);
}finally {
tTransport.close();
}
}
}
啟動(dòng)服務(wù)器卷拘,再啟動(dòng)客戶端,
客戶端打幼8摺:
Received 1
張三
32
true
............
Received 2
服務(wù)器端打印:
Thrift Server started!
Got client Param:張三
Got Client Param:
李四
30
true
跟我們之前的Google Protobuf相比污筷,Google Protobuf只是進(jìn)行編解碼(序列化與反序列)操作工闺,使用netty作為網(wǎng)絡(luò)載體,進(jìn)行遠(yuǎn)程方法調(diào)用瓣蛀。而Thrift不僅僅既可以進(jìn)行編解碼工作陆蟆,還提供傳輸對(duì)象功能,并且可以自己定義業(yè)務(wù)接口惋增。