公司跨項(xiàng)目協(xié)作带迟,一些部門服務(wù)框架底層封裝了thrift提供服務(wù)券盅,于是對(duì)thrift簡(jiǎn)單做了一些了解鞭执。
關(guān)于thrift
Facebook公布的一款開(kāi)源跨語(yǔ)言的RPC框架。
什么是RPC框架呢伴找?RPC全稱為Remote Procedure Call,意為遠(yuǎn)程過(guò)程調(diào)用盈蛮。
其實(shí)簡(jiǎn)單來(lái)說(shuō)有兩個(gè)系統(tǒng),一個(gè)系統(tǒng)想調(diào)用另一個(gè)系統(tǒng)技矮,但兩個(gè)系統(tǒng)不在同一個(gè)進(jìn)程抖誉,需要通過(guò)網(wǎng)絡(luò)來(lái)傳輸,而網(wǎng)絡(luò)傳輸需要涉及Socket,序列化反序列化,網(wǎng)絡(luò)I/O等一系列的事項(xiàng)衰倦,牛掰的程序員將這一過(guò)程封裝起來(lái)做成了一個(gè)框架袒炉,就是RPC框架,而thrift是其中一種樊零。
thrift通過(guò)一個(gè)中間語(yǔ)言IDL(接口定義語(yǔ)言)來(lái)定義RPC的數(shù)據(jù)類型和接口,這些內(nèi)容寫在以.thrift結(jié)尾的文件中,然后通過(guò)特殊的編譯器來(lái)生成不同語(yǔ)言的代碼,以滿足不同需要的開(kāi)發(fā)者,比如java開(kāi)發(fā)者,就可以生成java代碼,c++ 開(kāi)發(fā)者可以生成c++ 代碼,生成的代碼中不但包含目標(biāo)語(yǔ)言的接口定義,方法,數(shù)據(jù)類型,還包含有RPC協(xié)議層和傳輸層的實(shí)現(xiàn)代碼我磁。
安裝thrift
安裝前的小插曲
一開(kāi)始打算用Homebrew
裝的,想象Homebrew
好久沒(méi)更新了驻襟,首先更新了一把brew update
,結(jié)果mac居然報(bào)了下面的錯(cuò)誤:
invalid active developer path (/Library/Developer/CommandLineTools), missing xcrun at:
google了一下發(fā)現(xiàn)是因?yàn)閙acOS本身升級(jí)造成的夺艰,需要安裝下Command line tools,調(diào)用下命令即可沉衣,但是非常慢郁副,裝了幾十分鐘。
xcode-select --install
當(dāng)我解決了這個(gè)問(wèn)題之后豌习,發(fā)現(xiàn)用brew裝只能裝最新版本的存谎,但由于提供方目前使用的是0.9.3
的版本,為了保證一致斑鸦,所以打算跟他們一樣愕贡,所以放棄了使用brew使用草雕。
mac下正式安裝
首先到官網(wǎng)下載對(duì)應(yīng)版本的安裝包,選擇對(duì)應(yīng)的版本就可以了巷屿。
下載下來(lái)之后,解壓后進(jìn)入到目錄進(jìn)行如下操作:
#Step 1
./configure --prefix=/usr/local/ --with-boost=/usr/local --with-libevent=/usr/local --without-ruby --without-perl --without-php --without-nodejs
#Step 2
make
#Step 3
make install
在執(zhí)行第一步的時(shí)候發(fā)現(xiàn)報(bào)錯(cuò)了墩虹,錯(cuò)誤如下:
configure: error: Bison version 2.5 or higher must be installed on the system!
原因是mac上預(yù)安裝bison版本過(guò)低嘱巾,需要升級(jí)下bison,直接通過(guò)homebrew安裝即可:
brew install bison
安裝后還需要替換一下路徑诫钓,默認(rèn)安裝的路徑是在:
/usr/local/opt/bison/bin/bison
而系統(tǒng)自帶時(shí)的路徑是在:
/Library/Developer/CommandLineTools/usr/bin/
將原來(lái)的bison重命名下旬昭,然后將新的bison復(fù)制進(jìn)去:
mv bison bison_copy
cp /usr/local/opt/bison/bin/bison /Library/Developer/CommandLineTools/usr/bin/
這樣重復(fù)上面的步驟即可,安裝后可以查看下版本菌湃,如果正常展示就說(shuō)明安裝成功了:
thrift -version
數(shù)據(jù)類型及關(guān)鍵字
基本類型
thrift不支持無(wú)符號(hào)的類型,無(wú)符號(hào)類型可以簡(jiǎn)單理解為不能表示負(fù)數(shù),只能表示正數(shù)的類型,像java的基本數(shù)據(jù)類型都是有符號(hào)的類型问拘。
byte:有符號(hào)字節(jié)
i32:32位有符號(hào)整數(shù),此外還有i16,i64
double:64位浮點(diǎn)數(shù)
string:二進(jìn)制字符串
bool 布爾值 true或false
結(jié)構(gòu)體類型(struct)
類似于c語(yǔ)言的結(jié)構(gòu)體定義,在java中會(huì)被轉(zhuǎn)化為javabean類。
struct DemoModel {
1: i32 id;
2: string name;
3: double number;
4: bool flag;
}
服務(wù)類型(service)
service:對(duì)應(yīng)服務(wù)的接口,內(nèi)部可定義各種方法,相當(dāng)于java中創(chuàng)建interface一樣,創(chuàng)建的service經(jīng)過(guò)代碼生成命令會(huì)生成客戶端,服務(wù)端的框架代碼。
service DemoService{
string demoString(1:string s);
i32 demoInt(1:i32 i);
bool demoBoolean(1:bool b);
void demoVoid();
string demoNull();
}
異常類型(Exception)
exception RequestException {
1:i32 code;
2:string msg;
}
容器類型
集合中的元素可以是除了service之外的任意類型骤坐。
list<T>:有序列表,元素可重復(fù)
set<T>:無(wú)需集合,元素不可重復(fù)
map<K,V>:鍵值對(duì)集合
枚舉類型
enum StatusEnum{
Success,
Error
}
命名空間(namespace)
可以理解成java中的packet,用于避免一些代碼沖突,每種語(yǔ)言都有屬于自己的命名空間的方式,比如java語(yǔ)言,就可以使用java語(yǔ)言的格式绪杏。
namespace java com.demo.project
其他常用的
required string name1: 必選參數(shù)
optional string name2: 可選參數(shù)
const string strDemo = "demo": 定義常量
include "demo.thrift" 引入文件
Thrift支持的傳輸協(xié)議
Thrift支持多種傳輸協(xié)議,我們可以根據(jù)自己的需要來(lái)選擇合適的類型,總體上來(lái)說(shuō),分為文本傳輸和二進(jìn)制傳輸,由于二進(jìn)制傳輸在傳輸速率和節(jié)省帶寬上有優(yōu)勢(shì),所以大部分情況下使用二進(jìn)制傳輸是比較好的選擇。
TBinaryProtocol:使用二進(jìn)制編碼格式傳輸,是thrift的默認(rèn)傳輸協(xié)議
TCompactProtocol:使用壓縮格式傳輸
TJSONProtocol :使用JSON格式傳輸
TDebugProtocol : 使用易懂可讀的文本格式進(jìn)行傳輸纽绍,以便于debug
TSimpleJSONProtocol : 提供JSON只寫的協(xié)議蕾久,適用于通過(guò)腳本語(yǔ)言解析
Thrift支持的傳輸模式
Thrift封裝了一層傳輸層來(lái)支持底層的網(wǎng)絡(luò)通信,在Thrift中稱為Transport,不僅提供open,close,flush等方法,還有一些read/write方法。
TSocket:阻塞式IO的Transport實(shí)現(xiàn),用在客戶端.
TServerSocket:非阻塞式Socket,用于服務(wù)器端,用于監(jiān)聽(tīng)TSocket.
TNonblockingSocket:非阻塞式IO的實(shí)現(xiàn)
TMemoryInputTransport: 封裝了一個(gè)字節(jié)數(shù)組byte[]來(lái)做輸入流的封裝
TFramedTransport: 同樣使用非阻塞方式拌夏,按塊的大小進(jìn)行傳輸,輸入流封裝了TMemoryInputTransport
Thrift支持的服務(wù)模型
TSimpleServer
這種工作模式只有一個(gè)線程,循環(huán)監(jiān)聽(tīng)傳過(guò)來(lái)的請(qǐng)求并對(duì)其進(jìn)行處理,處理完才能接受下一個(gè)請(qǐng)求,是一種阻塞式IO的實(shí)現(xiàn),因?yàn)樾时容^低,實(shí)際線上環(huán)境一般用不到.一般用于開(kāi)發(fā)時(shí)候演示工作流程時(shí)使用僧著。
TNonblockingServer
這種模式與TsimpleServer最大的區(qū)別就是使用NIO,也就是非阻塞是IO的方式實(shí)現(xiàn)IO的多路復(fù)用,它可以同時(shí)監(jiān)聽(tīng)多個(gè)socket的變化,但因?yàn)闃I(yè)務(wù)處理上還是單線程模式,所以在一些業(yè)務(wù)處理比較復(fù)雜耗時(shí)的時(shí)候效率還是不高,因?yàn)槎鄠€(gè)請(qǐng)求任務(wù)依然需要排隊(duì)一個(gè)一個(gè)進(jìn)行處理。
TThreadPoolServer
這種模式引入了線程池,主線程只負(fù)責(zé)accept,即監(jiān)聽(tīng)Socket,當(dāng)有新的請(qǐng)求(客戶端Socket)來(lái)時(shí),就會(huì)在線程池里起一個(gè)線程來(lái)處理業(yè)務(wù)邏輯,這樣在并發(fā)量比較大的時(shí)候(但不超過(guò)線程池的數(shù)量)每個(gè)請(qǐng)求都能及時(shí)被處理,效率比較高,但一旦并發(fā)量很大的時(shí)候(超過(guò)線程池?cái)?shù)量),后面來(lái)的請(qǐng)求也只能排隊(duì)等待障簿。
TThreadedSelectorServer
這是一種多線程半同步半異步的服務(wù)模型,是Thrift提供的最復(fù)雜最高級(jí)的服務(wù)模型,內(nèi)部有一個(gè)專門負(fù)責(zé)處理監(jiān)聽(tīng)Socket的線程,有多個(gè)專門處理業(yè)務(wù)中網(wǎng)絡(luò)IO的線程,有一個(gè)專門負(fù)責(zé)決定將新Socket連接分配給哪一個(gè)線程處理的起負(fù)載均衡作用的線程,還有一個(gè)工作線程池.這種模型既可以響應(yīng)大量并發(fā)連接的請(qǐng)求又可以快速對(duì)網(wǎng)絡(luò)IO進(jìn)行讀寫,能適配很多場(chǎng)景,因此是一種使用比較高頻的服務(wù)模盹愚。
java實(shí)現(xiàn)
通過(guò)一個(gè)小demo來(lái)了解下thrift和具體的編碼實(shí)現(xiàn)。
首先我們創(chuàng)建個(gè)thrift文件,簡(jiǎn)單定義了一個(gè)方法:
namespace java service.demo
service Hello{
string helloString(1:string para)
}
創(chuàng)建好Hello.thrift
后通過(guò)終端生成java的代碼:
thrift -r -gen java Hello.thrift
發(fā)現(xiàn)在當(dāng)前目錄下多了一個(gè)gen-java
的目錄,里面的有一個(gè)Hello.java
的文件.這個(gè)java文件包含Hello服務(wù)的接口定義Hello.Iface
,以及服務(wù)調(diào)用的底層通信細(xì)節(jié),包括客戶端的調(diào)用邏輯Hello.Client
以及服務(wù)端的處理邏輯Hello.Processor
站故。
接著可以引入jar包杯拐,然后將生成的代碼復(fù)制到項(xiàng)目中:
<dependency>
<groupId>org.apache.thrift</groupId>
<artifactId>libthrift</artifactId>
<version>0.9.3</version>
</dependency>
然后創(chuàng)建HelloServiceImpl實(shí)現(xiàn)Hello.Iface接口:
package service.demo;
import org.apache.thrift.TException;
public class HelloServiceImpl implements Hello.Iface {
public String helloString(String para) throws TException {
return "result:"+para;
}
}
接著創(chuàng)建服務(wù)端實(shí)現(xiàn)代碼HelloServiceServer,把HelloServiceImpl作為一個(gè)具體的處理器傳遞給Thrift服務(wù)器:
package service.demo;
import org.apache.thrift.TProcessor;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TSimpleServer;
import org.apache.thrift.transport.TServerSocket;
import org.apache.thrift.transport.TTransportException;
public class HelloServiceServer {
public static void main(String[] args) {
try {
System.out.println("服務(wù)端開(kāi)啟....");
TProcessor tprocessor = new Hello.Processor<Hello.Iface>(new HelloServiceImpl());
// 簡(jiǎn)單的單線程服務(wù)模型
TServerSocket serverTransport = new TServerSocket(9898);
TServer.Args tArgs = new TServer.Args(serverTransport);
tArgs.processor(tprocessor);
tArgs.protocolFactory(new TBinaryProtocol.Factory());
TServer server = new TSimpleServer(tArgs);
server.serve();
}catch (TTransportException e) {
e.printStackTrace();
}
}
}
最后創(chuàng)建客戶端實(shí)現(xiàn)代碼HelloServiceClient,調(diào)用Hello.client訪問(wèn)服務(wù)端的邏輯實(shí)現(xiàn)
package service.demo;
import org.apache.thrift.TException;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
import org.apache.thrift.transport.TTransportException;
public class HelloServiceClient {
public static void main(String[] args) {
System.out.println("客戶端啟動(dòng)....");
TTransport transport = null;
try {
transport = new TSocket("localhost", 9898, 30000);
// 協(xié)議要和服務(wù)端一致
TProtocol protocol = new TBinaryProtocol(transport);
Hello.Client client = new Hello.Client(protocol);
transport.open();
String result = client.helloString("哈哈");
System.out.println(result);
} catch (TTransportException e) {
e.printStackTrace();
} catch (TException e) {
e.printStackTrace();
} finally {
if (null != transport) {
transport.close();
}
}
}
}
啟動(dòng)服務(wù)端和客戶端就能看到效果啦。
總結(jié)
簡(jiǎn)單了解了thrift之后就開(kāi)始代碼變現(xiàn)了世蔗,有空還是需要深入了解下的端逼。
另外看到一篇thrift與http性能對(duì)比的文章(小測(cè)thrift和http在node.js中的性能對(duì)比),發(fā)現(xiàn)thrift性能還是可以的污淋,畢竟現(xiàn)在大多情況下我們的服務(wù)還是使用http通過(guò)json傳輸?shù)亩ヌ病H绻麑?duì)于性能有高要求的業(yè)務(wù)場(chǎng)景,可以考慮thrift的寸爆。