1.前言:
Thrift作為Facebook開源的RPC框架, 通過IDL中間語言, 並借助代碼生成引擎生成各種主流語言的rpc框架服務端/客戶端代碼,主要特點:
開發速度快:
通過編寫RPC接口IDL文件,利用編譯生成器自動生成Server端骨架(Skeletons)和客戶端Stubs,省去開發者自定義和維護接口編解碼、消息傳輸、服務器多線程模型等基礎工作;Server端開發者只需按照服務骨架,寫好自己的業務處理程序(Handlers)即可,Client端程序只需創建IDL中定義的服務對象,然后就像調用本地對象的方法一樣調用遠端服務。
接口維護簡單高效:
通過維護Thrift格式的IDL(Interface Description Language)文件(注意寫好注釋),即可作為給Clients使用的接口文檔使用,也自動生成接口代碼,始終保持代碼和文檔的一致性。且Thrift協議可靈活支持接口的可擴展性。
學習成本低:
因為其來自Google Protocol Buffers開發團隊,所以其IDL文件風格類似Google Protocol Buffers,且更加易讀易懂;特別是RPC服務接口的風格就像寫一個一般的面向對象的Class一樣簡單。
初學者只需參照http://thrift.apache.org/幾個小時即可理解和使用Thrift。
多語言/跨語言支持:
Thrift支持C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, OCaml and Delphi等多種語言,即可生成上述語言的服務器端和客戶端程序。
對於我們經常使用的Java、PHP、Python、C++支持良好,雖然對iOS環境的Objective-C(Cocoa)支持稍遜,但也完全滿足我們的使用要求。
已驗證成熟穩定:
Thrift在很多開源項目中已經被驗證是穩定和高效的,例如Cassandra、Evernode等;在Facebook、Baidu等后台產品中也有使用。
2.基礎知識
2.1 Thrift 軟件棧
Thrift對軟件棧的定義非常的清晰, 使得各個組件能夠松散的耦合, 針對不同的應用場景, 選擇不同是方式去搭建服務.
評注:
Transport: 傳輸層,定義數據傳輸方式,可以為TCP/IP傳輸,內存共享或者文件共享等
protocol: 協議層, 定義數據傳輸格式,可以為二進制或者XML等
Processor: 處理層, 這部分由定義的idl來生成, 封裝了協議輸入輸出流, 並委托給用戶實現的handler進行處理.
Server: 服務層, 整合上述組件, 提供網絡模型(單線程/多線程/事件驅動), 最終形成真正的服務.
Thrift 對語言的支持
Thrift和Google Protobuf相比, 都提供了可擴展序列化機制, 不但兼容性好而且壓縮率高. 兩者在這塊各有長短, 性能上PB稍有優勢. 但在語言的支持度上, Protobuf只支持c++/java/python這三種主流的語言, Thrift則幾乎覆蓋了大部分的語言, 從這點上來說, Thrift的優勢非常的明顯.
Thrift 支持的數據類型
基本類型:
bool: 布爾值
byte: 8位有符號整數
i16: 16位有符號整數
i32: 32位有符號整數
i64: 64位有符號整數
double: 64位浮點數
string: UTF-8編碼的字符串
binary: 二進制串
結構體類型:
struct: 定義的結構體對象
容器類型:
list: 有序元素列表
set: 無序無重復元素集合
map: 有序的key/value集合
異常類型:
exception: 異常類型
服務類型:
service: 具體對應服務的類
3.小試牛刀
3.1定義thrift文件
namespace java com.yangyang.thrift.api
service HelloService {
string hello(1: string name);
}
3.2使用thrift提供的代碼生成工具生成代碼
thrift -gen java -out ../java/ hello.thrift
3.3新建thrift-demo工程
在pom文件中加入thrift以及log4j的jar
<dependency>
<groupId>org.apache.thrift</groupId>
<artifactId>libthrift</artifactId>
<version>0.9.3</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.5</version>
</dependency>
編寫服務端代碼:
package com.yangyang.thrift.server;
import com.yangyang.thrift.api.HelloService;
import com.yangyang.thrift.com.yangyang.thrift.service.HelloServiceImpl;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TThreadPoolServer;
import org.apache.thrift.transport.TServerSocket;
import org.apache.thrift.transport.TTransportException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Created by chenshunyang on 2016/10/30.
*/
public class ServerDemo {
public static void main(String[] args) throws TTransportException {
Logger logger = LoggerFactory.getLogger(ServerDemo.class);
int port= 9000 ;
// *) 傳輸層(Transport), 設置監聽端口為9000
TServerSocket serverTransport = new TServerSocket(port);
// *) 協議層
TBinaryProtocol.Factory protocolFactory = new TBinaryProtocol.Factory(true, true);
// *) 處理層(Processor)
HelloServiceImpl handler = new HelloServiceImpl();
HelloService.Processor<HelloServiceImpl> processor = new HelloService.Processor<HelloServiceImpl>(handler);
// *) 服務層(Server)
TServer server = new TThreadPoolServer(
new TThreadPoolServer.Args(serverTransport)
.protocolFactory(protocolFactory)
.processor(processor));
// *) 啟動監聽服務
server.serve();
logger.info("服務已經啟動,端口為:"+port);
}
}
編寫客戶端代碼:
package com.yangyang.thrift;
import com.yangyang.thrift.api.HelloService;
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 chenshunyang on 2016/10/30.
*/
public class ClientDemo {
public static void main(String[] args) throws TException {
// *) 傳輸層
TTransport transport = new TSocket("localhost", 9000);
transport.open();
// *) 協議層, 與服務端對應
TProtocol protocol = new TBinaryProtocol(transport);
// *) 創建RPC客戶端
HelloService.Client client = new HelloService.Client(protocol);
// *) 調用服務
System.out.println(client.hello("chenshunyang"));
// *) 關閉句柄
transport.close();
}
}
先運行服務端,后運行客戶端,觀察結果,可以看到客戶端輸出結果:
hello chenshunyang
