thrift是一個facebook開源的高效RPC框架,其主要特點是跨語言及二進制高效傳輸(當然,除了二進制,也支持json等常用序列化機制),官網地址:http://thrift.apache.org
跨語言通常有二種做法,
一是將其它語言轉換成某種主流的通用語言,比如:delphi.net以前就是先將delphi轉換成c#,然后再編譯成IL,從而實現delphi在.net上的運行(好久沒關注delphi了,不知道現在還是不是這種機制)
二是先定義一種規范文件(可以簡單的理解為『母版』),然后由特定的編譯器,將『母版』直接編譯成目標語言的源代碼。
thrift走的是第二條路,使用thrift框架時,先定義名為.thrift后綴的文件,然后由thrift編譯器編譯成指定語言的源文件,然后借助thrift提供的各種語言的實現lib庫,完成rpc的調用。
一、thrift編譯器的安裝
1.1、windows 安裝
http://thrift.apache.org/docs/install/windows 這是官網的windows安裝指導說明,windows的安裝其實最簡單
Thrift compiler for Windows (thrift-0.9.2.exe)
下載這二個文件即可,第1個是編譯器,第2個壓縮包里包括了種示例代碼。把thrift-0.9.2.exe保存到某個目錄,比如:c:\thrift下,然后將thrift-0.9.2.exe改個簡單的名字,比如:thrift.exe(這一步非必須),最后在環境變量的path路徑里,把c:\thrift加上,保證Command窗口下,鍵入thrift能找到該文件即可。
1.2、centos 安裝
http://thrift.apache.org/docs/install/centos 參考這個安裝,上面的詳細的命令,按上面的命令一步步來就行了,主要過程是先安裝一堆依賴的工具,然后
git clone https://git-wip-us.apache.org/repos/asf/thrift.git
將thrift源代碼拉到本地,再build,生成thrift編譯器
1.3、mac osx 安裝
http://thrift.apache.org/docs/install/os_x 參考這里,大概步驟跟centos差不多,相信大家都能搞定,唯一要注意的是,mac os上沒有yum之類的工具,建議使用brew 工具安裝
二、thrift文件的定義
從git拉回來的源代碼tutorial目錄下,有二個示例文件:shared.thrift及tutorial.thrift,大家可以打開看看,演示了主要用法,但我覺得還是有些小復雜,初學者一眼看上去有些亂,我按服務開發的常規場景,自己弄了二個小demo:
首先定義要傳輸的dto對象
dto.thrift
namespace java yjmyzz.thrift.study.dto namespace csharp yjmyzz.thrift.study.dto //Person類 struct Person { 1: i16 age = 0, 2: string name, 3: bool sex, 4: double salary, 5: byte childrenCount } //查詢參數 struct QueryParameter{ 1: i16 ageStart, 2: i16 ageEnd }
這里定義了二個類,一個是Person類,一個是查詢的參數對象類,最上面的namespace即為最終java, c#里的package及namespace
再定義服務接口service.thrift
namespace java yjmyzz.thrift.study.service namespace csharp yjmyzz.thrift.study.service include "dto.thrift" //服務 service DemoService { //用于檢測client-server之間通訊是否正常 string ping(), list<dto.Person> getPersonList(1:dto.QueryParameter parameter) }
實際開發中,建議大家使用intellij idea,其thrift插件可以支持高亮語法和代碼智能提示,如下圖:
三、client及server端項目開發
如果大家使用過hessian、dubbo之類的框架,相信對于服務開發這一類套路都很熟悉,通常會拆分成3部分,接口定義(也稱服務&數據契約 contract)、服務生產方(即:server端)、服務消費方(即:client端),按這一思路,創建三個module,項目結構見下圖:
其中thrift-contract即為公用的接口部分,thrift-client為客戶端,thrift-server為服務端,注意:dto.thrift及service.thrift這二個文件,我放在了thrift-contract\src\thrift這個目錄下。
3.1 生成目標語言源文件
架勢拉好了,開始干活,命令行下先進入thrift-contract\src\thrift所在目錄,
thrift -gen java dto.thrift
thrift -gen java service.thrift
這樣就把二個thrift【母版】文件生成了對應的java代碼,生成的源文件會存放在當前工作目錄的gen-java下,如果把-gen java換成-gen csharp就會生成c#代碼。
上圖是生成后的源代碼結構,由于src\thrift目錄并不是maven工程約定的源代碼目錄,手動把gen-java下生成的整個目錄,復制到src/main/java下即可。
3.2 maven中pom.xml依賴薦的添加
1 <dependencies> 2 <dependency> 3 <groupId>org.apache.thrift</groupId> 4 <artifactId>libthrift</artifactId> 5 <version>0.9.2</version> 6 </dependency> 7 8 <dependency> 9 <groupId>org.slf4j</groupId> 10 <artifactId>slf4j-log4j12</artifactId> 11 <version>1.5.8</version> 12 </dependency> 13 </dependencies>
libthrift這一項是必須要添加的,slf4j-log4j12對于接口定義來講,可以不用(但最終在server\client端運行時,這個包不可少)。然后就可以用 mvn clean install 來生成jar包并安裝到本機maven倉庫中了,注意這里有一個小問題:
thrift生成的java源代碼中,@Override這個注解有些地方添加得不對(比如:實現接口時,實現類中是不需要添加這一注解的),編譯時如果出現錯誤,直接去掉即可,建議:將生成的java源文件,全局替換,把@Override全干掉。
3.3 server端的接口實現
thrift-contract只是生成了服務的接口定義,并未提供實現,下面是DemoService的實現
package yjmyzz.thrift.study; import org.apache.thrift.TException; import yjmyzz.thrift.study.dto.Person; import yjmyzz.thrift.study.dto.QueryParameter; import yjmyzz.thrift.study.service.DemoService; import java.util.ArrayList; import java.util.List; public class DemoServiceImpl implements DemoService.Iface { public String ping() throws TException { System.out.println("ping()"); return "pong"; } public List<Person> getPersonList(QueryParameter parameter) throws TException { //System.out.println(parameter.getAgeStart() + " - " + parameter.getAgeEnd()); List<Person> list = new ArrayList<Person>(10); for (short i = 0; i < 10; i++) { Person p = new Person(); p.setAge(i); p.setChildrenCount(Byte.valueOf(i + "")); p.setName("test" + i); p.setSalary(10000D); p.setSex(true); list.add(p); } return list; } }
這里隨便返回一堆數據,意思一下而已,注:server端的pom.xml中,記得添加接口層的依賴項引用

1 <dependencies> 2 3 <dependency> 4 <groupId>yjmyzz</groupId> 5 <artifactId>thrift-contract</artifactId> 6 <version>1.0</version> 7 </dependency> 8 9 <dependency> 10 <groupId>org.apache.thrift</groupId> 11 <artifactId>libthrift</artifactId> 12 <version>0.9.2</version> 13 </dependency> 14 15 <dependency> 16 <groupId>org.slf4j</groupId> 17 <artifactId>slf4j-log4j12</artifactId> 18 <version>1.5.8</version> 19 </dependency> 20 </dependencies>
3.4 server的啟動

1 package yjmyzz.thrift.study; 2 3 4 import org.apache.thrift.server.TServer; 5 import org.apache.thrift.server.TServer.Args; 6 import org.apache.thrift.server.TSimpleServer; 7 import org.apache.thrift.transport.TServerSocket; 8 import org.apache.thrift.transport.TServerTransport; 9 import yjmyzz.thrift.study.service.DemoService; 10 11 12 public class ThriftServer { 13 14 public static DemoService.Iface service; 15 16 public static DemoService.Processor processor; 17 18 public static void main(String[] args) { 19 try { 20 service = new DemoServiceImpl(); 21 processor = new DemoService.Processor(service); 22 23 Runnable simple = new Runnable() { 24 public void run() { 25 simple(processor); 26 } 27 }; 28 new Thread(simple).start(); 29 30 } catch (Exception x) { 31 x.printStackTrace(); 32 } 33 } 34 35 public static void simple(DemoService.Processor processor) { 36 try { 37 TServerTransport serverTransport = new TServerSocket(9090); 38 TServer server = new TSimpleServer(new Args(serverTransport).processor(processor)); 39 System.out.println("Starting the simple server..."); 40 server.serve(); 41 } catch (Exception e) { 42 e.printStackTrace(); 43 } 44 } 45 46 47 }
這里監聽9090端口,響應client的調用請求。
3.5 client調用示例

1 package yjmyzz.thrift.study; 2 3 import org.apache.thrift.TException; 4 import org.apache.thrift.protocol.TBinaryProtocol; 5 import org.apache.thrift.protocol.TProtocol; 6 import org.apache.thrift.transport.TSocket; 7 import org.apache.thrift.transport.TTransport; 8 import yjmyzz.thrift.study.dto.QueryParameter; 9 import yjmyzz.thrift.study.service.DemoService; 10 11 public class ThriftClient { 12 13 public static void main(String[] args) { 14 15 try { 16 TTransport transport; 17 transport = new TSocket("localhost", 9090); 18 transport.open(); 19 20 TProtocol protocol = new TBinaryProtocol(transport); 21 DemoService.Client client = new DemoService.Client(protocol); 22 23 System.out.println(client.ping()); 24 25 int max = 100000; 26 27 Long start = System.currentTimeMillis(); 28 29 for (int i = 0; i < max; i++) { 30 call(client); 31 } 32 33 Long end = System.currentTimeMillis(); 34 35 Long elapse = end - start; 36 37 int perform = Double.valueOf(max / (elapse / 1000d)).intValue(); 38 39 System.out.print("thrift " + max + " 次RPC調用,耗時:" + elapse + "毫秒,平均" + perform + "次/秒"); 40 41 transport.close(); 42 43 } catch (TException x) { 44 x.printStackTrace(); 45 } 46 } 47 48 private static void call(DemoService.Client client) throws TException { 49 50 //client.ping(); 51 //System.out.println("ping()=>" + client.ping()); 52 53 QueryParameter parameter = new QueryParameter(); 54 parameter.setAgeStart(Short.valueOf("5")); 55 parameter.setAgeEnd(Short.valueOf("50")); 56 57 client.getPersonList(parameter); 58 //System.out.println(client.getPersonList(parameter)); 59 } 60 }
在我mac老爺本(2011年買的)上測試出來的結果,而且server使用的SimpleServer同步阻塞模式,如果采用多線程非阻塞模式,在服務器上性能相信至少還能翻N倍不成問題:
thrift 100000 次RPC調用,耗時:7774毫秒,平均12863次/秒
即使按這個結果來估算,1秒至少處理1w次,1小時3600秒就是3.6kw次,1天24小時,每天處理上億次調用毫無壓力,相信已經能滿足很多公司的要求了。而且thrift出來很多年了,相對比較成熟,如果項目是異構系統,要兼容多種語言之間的相互調用,thrift是不錯的選擇。
但最后也提醒一下,thrift雖然跨語言不假,但是在不同語言的實現上,性能相差甚遠,上面的示例client、server都是采用java開發,如果把server換成c#,結果就慘不忍睹了(相差幾個數量級),大家可以自行測試,建議實際項目中,至少server端使用java語言(或c++)開發。
另外在使用上,有一些小限制:一個Server只能對應一個Service接口,也就是說,如果有多個服務,要么融合成一個大接口,要么啟多個server(對應多個端口)
附文中源碼下載:
文章列表