簡介
Apache Thrift是Facebook開源的跨語言的RPC通訊框架,目前已經捐獻給Apache基金會管理,因爲其跨語言特性和出色的性能,在不少互聯網公司獲得應用,有能力的公司甚至會基於thrift研發一套分佈式服務框架,增長諸如服務註冊、服務發現等功能。java
RPC即Remote Procedure Call,翻譯爲遠程過程調用。任何RPC協議的實現終極目標都是讓使用者在調用遠程方法的時候就像是調用本地方法同樣簡單,從而提升使用遠程服務的效率。apache
現代互聯網架構多數基於SOA思想而搭建,即面向服務化的架構。服務提供方稱爲Provider,服務的使用方稱爲Consumer,有時也把服務提供方稱爲Server端,使用方稱爲Client端,即典型的CS模型。這裏的遠程調用,主要指跨進程的調用,Provider和Consumer多是同一機器的不一樣進程,也可能在不一樣的機器,經過網絡相互通訊,大部分狀況下二者會部署在不一樣的物理機器上,這種狀況下因爲網絡通訊的開銷就會對RPC框架的性能要求極高。編程
下面分別從服務端和客戶端的視角來介紹Thrift在RPC中的應用。服務器
服務端(Server)
服務端須要發佈一個服務給別人使用,首先要約定好服務的接口,包括如下幾個部分:網絡
- 服務的名稱
- 服務使用時的參數
- 返回結果
Thrift本身規定了一套接口定義語言(IDL)來描述服務,用後綴爲.thrift的文件來描述,好比咱們要提供一個打招呼的服務,傳入姓名,而後返回一段友好的語句,Thrift文件HelloService.thrift的內容以下:數據結構
1
2
3
4
|
namespace java com.yuanwhy.service
service HelloService{
string sayHello(
1
:string name)
}
|
Thrift文件定義好以後,就約定好了接口的使用方式,可是仍然還不能使用,須要咱們用thrift命令來生成對應的編程語言的文件,好比用一下命令來生成HelloService.class的Java文件。架構
1
|
thrift -r --gen java HelloService.thrift
|
命令行參數 -r 表明遞歸生成裏面引用的其餘文件, --gen 後面跟生成的目標語言,最後跟上thrift文件。框架
生成的Java文件裏會有一個接口HelloService.Iface,這就是一個普通的Java Interface,服務端要提供該接口的實現,實現以前須要引用libthrift的jar包,好比咱們這麼實現:編程語言
1
2
3
4
5
6
7
8
9
10
|
package
com.yuanwhy.service;
import
org.apache.thrift.TException;
public
class
HelloServiceImpl
implements
HelloService.Iface {
@Override
public
String sayHello(String name)
throws
TException {
return
"Hello, "
+ name +
"!"
;
}
}
|
這樣,服務端就實現了服務的邏輯部分,可是要讓別人在網絡上真正可用,咱們還得把這個服務發佈出去,發佈的方式就是藉助Socket編程,監聽一個對外服務的端口,這也是網絡通信的基本套路。利用Thrift提供的API,在HelloServiceProvider類中啓動Thrift服務:分佈式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
package
com.yuanwhy.demo;
import
com.yuanwhy.service.HelloService;
import
com.yuanwhy.service.HelloServiceImpl;
import
org.apache.thrift.TProcessor;
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
HelloServiceProvider {
/**
* 啓動 Thrift 服務器
*
* @param args
*/
public
static
void
main(String[] args) {
try
{
// 設置服務端口爲 7911
TServerSocket serverTransport =
new
TServerSocket(
7911
);
TProcessor processor =
new
HelloService.Processor(
new
HelloServiceImpl());
TServer server =
new
TSimpleServer(
new
TServer.Args(serverTransport).processor(processor));
System.out.println(
"Start server on port 7911..."
);
server.serve();
}
catch
(TTransportException e) {
e.printStackTrace();
}
}
}
|
運行main函數以後,服務端就會監聽7911端口,開始對外提供sayHello的服務了。
客戶端(Client)
客戶端想要使用服務端經過thrift發佈的服務,只須要遵循面向接口編程的基本思想,引用Java接口,用thrift的API鏈接服務端便可,好比HelloServiceConsumer類中這麼使用sayHello服務:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
package
com.yuanwhy.demo;
import
com.yuanwhy.service.HelloService;
import
org.apache.thrift.TApplicationException;
import
org.apache.thrift.TEnum;
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;
/**
* Created by zeze on 2018/03/23.
*/
public
class
HelloServiceConsumer {
public
static
void
main(String[] args) {
TTransport transport =
new
TSocket(
"0.0.0.0"
,
7911
);
try
{
transport.open();
TProtocol protocol =
new
TBinaryProtocol(transport);
HelloService.Client client =
new
HelloService.Client(protocol);
System.out.println(client.sayHello(
"yuanwhy"
));
transport.close();
}
catch
(TApplicationException e) {
if
(e.getType() == TApplicationException.MISSING_RESULT) {
System.out.println(
"null"
);
}
}
catch
(TException e){
}
}
}
|
客戶端運行結果會打印Hello, yuanwhy!
,代表服務調用成功。仔細觀察一下客戶端的代碼會發現,基本和普通的Socket編程沒有太大的區別,只是又被thrift作了一層的封裝,讓咱們能夠按照約定的接口直接像client.sayHello("yuanwhy")
這樣調用遠程服務。
注意:客戶端若是是在不一樣的Java項目中調用服務,只須要服務端把thrift文件或者生成的Java接口文件以API的方式提供出來便可,客戶端絕對不須要引用HelloServiceImpl實現類,由於目的就是讓邏輯在服務端實現,對客戶端透明。
另外,當服務端返回null時,客戶端會拋一個Type爲TApplicationException.MISSING_RESULT
的異常出來,若是不處理就會影響客戶端正常的流程。這一點能夠在Thrift的生成代碼中看出來:
1
2
3
4
5
6
7
8
9
|
public
String recv_sayHello()
throws
org.apache.thrift.TException
{
sayHello_result result =
new
sayHello_result();
receiveBase(result,
"sayHello"
);
if
(result.isSetSuccess()) {
return
result.success;
}
throw
new
org.apache.thrift.TApplicationException(org.apache.thrift.TApplicationException.MISSING_RESULT,
"sayHello failed: unknown result"
);
}
|
總結
以上對Thrift的使用只作了個簡單的介紹,真正在項目中使用Thrift還會涉及不少,好比各類Thrift數據結構的使用,在對Thrift接口進行升級過程當中struct的字段最好保留原有字段順序以達到兼容目的,還好比客戶端應該創建鏈接池機制,而不是每次調用服務時都去新建一次TCP鏈接等等。