文章出處

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)

thrift-0.9.2.tar.gz

下載這二個文件即可,第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>
View Code

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 }
View Code

這里監聽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 }
View Code 

在我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(對應多個端口)

 

附文中源碼下載:

http://code.taobao.org/svn/thrift-java/


文章列表




Avast logo

Avast 防毒軟體已檢查此封電子郵件的病毒。
www.avast.com


arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()