Thrift開發使用筆記(1)--Thrift簡介及安裝使用

使用案例
1、 下載安裝(Maven中添加依賴)

<dependency>
    <groupId>org.apache.thrift</groupId>
    <artifactId>libthrift</artifactId>
    <version>0.9.3</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.6.6</version>
</dependency>

2、 下載編譯器
下載地址:http://thrift.apache.org/download
或者直接點擊:
http://www.apache.org/dyn/closer.cgi?path=/thrift/0.9.3/thrift-0.9.3.exe

3、  編寫測試用例

新建demoHello.thrift,內容如下

namespace java service.demo 
service Hello{ 
  string helloString(1:string para) 
  i32 helloInt(1:i32 para) 
  bool helloBoolean(1:bool para) 
  void helloVoid() 
  string helloNull() 
}

這裏寫圖片描述
打開windows控制檯進入Thrift文件夾下,輸入:thrift-0.9.3.exe -r -gen java ./demoHello.thrift

生成目錄如下:
service
└─demo
Hello.java
4、 將hello.java複製到項目中
5、 新建
HelloServiceImpl.java並實現Hello.Iface接口

package com.service.demo.impl;

import com.service.demo.Hello;
import org.apache.thrift.TException;

/**
 * Created by ssjk on 2016/10/20.
 */
public class HelloServiceImpl implements Hello.Iface{

    @Override
    public String helloString(String para) throws TException {
        return para;
    }

    @Override
    public int helloInt(int para) throws TException {
        try {
            Thread.sleep(20000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return para;
    }

    @Override
    public boolean helloBoolean(boolean para) throws TException {
        return para;
    }

    @Override
    public void helloVoid() throws TException {
        System.out.println("Hello word!");
    }

    @Override
    public String helloNull() throws TException {
        return null;
    }
}

6、 建立啓動服務
HelloServiceServer.java

package com.day06;

import com.service.demo.Hello;
import com.service.demo.impl.HelloServiceImpl;
import org.apache.thrift.TProcessor;
import org.apache.thrift.TProcessorFactory;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TBinaryProtocol.Factory;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TThreadPoolServer;
import org.apache.thrift.transport.TServerSocket;
import org.apache.thrift.transport.TTransportException;


public class HelloServiceServer {
    /**
     * 啓動 Thrift 服務器
     * @param args
     */
    public static void main(String[] args) {
        try {
            // 設置服務端口爲 7911
            TServerSocket serverTransport = new TServerSocket(7911);
            // 設置協議工廠爲 TBinaryProtocol.Factory

            // 關聯處理器與 Hello 服務的實現
            TProcessor processor = new Hello.Processor(new HelloServiceImpl());
            TProcessorFactory tProcessorFactory = new TProcessorFactory(processor);
            TThreadPoolServer.Args args1=new TThreadPoolServer.Args(serverTransport);
            args1.processor(processor);
            args1.processorFactory(tProcessorFactory);
            TServer server =new TThreadPoolServer(args1);
            System.out.println("Start server on port 7911...");
            server.serve();
        } catch (TTransportException e) {
            e.printStackTrace();
        }
    }
}

7、 建立客戶端HelloServiceClient.java

package com.day06;

import com.service.demo.Hello;
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 {
    /**
     * 調用 Hello 服務
     * @param args
     */
    public static void main(String[] args) {
        try {
            // 設置調用的服務地址爲本地,端口爲 7911
            TTransport transport = new TSocket("localhost", 7911);
            transport.open();
            // 設置傳輸協議爲 TBinaryProtocol
            TProtocol protocol = new TBinaryProtocol(transport);
            Hello.Client client = new Hello.Client(protocol);
            // 調用服務的 helloString 方法
            String str = client.helloString("Thrift測試");              
System.out.println(str);
            transport.close();
        } catch (TTransportException e) {
            e.printStackTrace();
        } catch (TException e) {
            e.printStackTrace();
        }
    }
}

8、 輸出結果
這裏寫圖片描述
成功!

Thrift 架構

Thrift 包含一個完整的堆棧結構用於構建客戶端和服務器端。下圖描繪了 Thrift 的整體架構。
圖 1. 架構圖
這裏寫圖片描述
如圖所示,圖中黃色部分是用戶實現的業務邏輯,褐色部分是根據 Thrift 定義的服務接口描述文件生成的客戶端和服務器端代碼框架,紅色部分是根據 Thrift 文件生成代碼實現數據的讀寫操作。紅色部分以下是 Thrift 的傳輸體系、協議以及底層 I/O 通信,使用 Thrift 可以很方便的定義一個服務並且選擇不同的傳輸協議和傳輸層而不用重新生成代碼。
Thrift 服務器包含用於綁定協議和傳輸層的基礎架構,它提供阻塞、非阻塞、單線程和多線程的模式運行在服務器上,可以配合服務器 / 容器一起運行,可以和現有的 J2EE 服務器 /Web 容器無縫的結合。
服務端和客戶端具體的調用流程如下:
圖 2. Server 端啓動、服務時序圖(查看大圖)
這裏寫圖片描述
該圖所示是 HelloServiceServer 啓動的過程以及服務被客戶端調用時,服務器的響應過程。從圖中我們可以看到,程序調用了 TThreadPoolServer 的 serve 方法後,server 進入阻塞監聽狀態,其阻塞在 TServerSocket 的 accept 方法上。當接收到來自客戶端的消息後,服務器發起一個新線程處理這個消息請求,原線程再次進入阻塞狀態。在新線程中,服務器通過 TBinaryProtocol 協議讀取消息內容,調用 HelloServiceImpl 的 helloVoid 方法,並將結果寫入 helloVoid_result 中傳回客戶端。
圖 3. Client 端調用服務時序圖(查看大圖)
這裏寫圖片描述
該圖所示是 HelloServiceClient 調用服務的過程以及接收到服務器端的返回值後處理結果的過程。從圖中我們可以看到,程序調用了 Hello.Client 的 helloVoid 方法,在 helloVoid 方法中,通過 send_helloVoid 方法發送對服務的調用請求,通過 recv_helloVoid 方法接收服務處理請求後返回的結果。
數據類型
Thrift 腳本可定義的數據類型包括以下幾種類型:
• 基本類型:
o bool:布爾值,true 或 false,對應 Java 的 boolean
o byte:8 位有符號整數,對應 Java 的 byte
o i16:16 位有符號整數,對應 Java 的 short
o i32:32 位有符號整數,對應 Java 的 int
o i64:64 位有符號整數,對應 Java 的 long
o double:64 位浮點數,對應 Java 的 double
o string:未知編碼文本或二進制字符串,對應 Java 的 String
• 結構體類型:
o struct:定義公共的對象,類似於 C 語言中的結構體定義,在 Java 中是一個 JavaBean
• 容器類型:
o list:對應 Java 的 ArrayList
o set:對應 Java 的 HashSet
o map:對應 Java 的 HashMap
• 異常類型:
o exception:對應 Java 的 Exception
• 服務類型:
o service:對應服務的類

協議

Thrift 可以讓用戶選擇客戶端與服務端之間傳輸通信協議的類別,在傳輸協議上總體劃分爲文本 (text) 和二進制 (binary) 傳輸協議,爲節約帶寬,提高傳輸效率,一般情況下使用二進制類型的傳輸協議爲多數,有時還會使用基於文本類型的協議,這需要根據項目 / 產品中的實際需求。常用協議有以下幾種:
• TBinaryProtocol —— 二進制編碼格式進行數據傳輸
使用方法如清單 3 和清單 4 所示。
• TCompactProtocol —— 高效率的、密集的二進制編碼格式進行數據傳輸
構建 TCompactProtocol 協議的服務器和客戶端只需替換清單 3 和清單 4 中 TBinaryProtocol 協議部分即可,替換成如下代碼:
清單 5. 使用 TCompactProtocol 協議構建的 HelloServiceServer.java
TCompactProtocol.Factory proFactory = new TCompactProtocol.Factory();
清單 6. 使用 TCompactProtocol 協議的 HelloServiceClient.java
TCompactProtocol protocol = new TCompactProtocol(transport);
• TJSONProtocol —— 使用 JSON 的數據編碼協議進行數據傳輸
構建 TJSONProtocol 協議的服務器和客戶端只需替換清單 3 和清單 4 中 TBinaryProtocol 協議部分即可,替換成如下代碼:
清單 7. 使用 TJSONProtocol 協議構建的 HelloServiceServer.java
TJSONProtocol.Factory proFactory = new TJSONProtocol.Factory();
清單 8. 使用 TJSONProtocol 協議的 HelloServiceClient.java
TJSONProtocol protocol = new TJSONProtocol(transport);
• TSimpleJSONProtocol —— 只提供 JSON 只寫的協議,適用於通過腳本語言解析

傳輸層

常用的傳輸層有以下幾種:
• TSocket —— 使用阻塞式 I/O 進行傳輸,是最常見的模式
使用方法如清單 4 所示。
• TFramedTransport —— 使用非阻塞方式,按塊的大小進行傳輸,類似於 Java 中的 NIO
若使用 TFramedTransport 傳輸層,其服務器必須修改爲非阻塞的服務類型,客戶端只需替換清單 4 中 TTransport 部分,代碼如下,清單 9 中 TNonblockingServerTransport 類是構建非阻塞 socket 的抽象類,TNonblockingServerSocket 類繼承 TNonblockingServerTransport
清單 9. 使用 TFramedTransport 傳輸層構建的 HelloServiceServer.java
TNonblockingServerTransport serverTransport;
serverTransport = new TNonblockingServerSocket(10005);
Hello.Processor processor = new Hello.Processor(new HelloServiceImpl());
TServer server = new TNonblockingServer(processor, serverTransport);
System.out.println(“Start server on port 10005 …”);
server.serve();
清單 10. 使用 TFramedTransport 傳輸層的 HelloServiceClient.java
TTransport transport = new TFramedTransport(new TSocket(“localhost”, 10005));
• TNonblockingTransport —— 使用非阻塞方式,用於構建異步客戶端
使用方法請參考 Thrift 異步客戶端構建
服務端類型
常見的服務端類型有以下幾種:
• TSimpleServer —— 單線程服務器端使用標準的阻塞式 I/O
代碼如下:
清單 11. 使用 TSimpleServer 服務端構建的 HelloServiceServer.java
TServerSocket serverTransport = new TServerSocket(7911);
TProcessor processor = new Hello.Processor(new HelloServiceImpl());
TServer server = new TSimpleServer(processor, serverTransport);
System.out.println(“Start server on port 7911…”);
server.serve();
客戶端的構建方式可參考清單 4。
• TThreadPoolServer —— 多線程服務器端使用標準的阻塞式 I/O
使用方法如清單 3 所示。
• TNonblockingServer —— 多線程服務器端使用非阻塞式 I/O
使用方法請參考 Thrift 異步客戶端構建
Thrift 異步客戶端構建
Thrift 提供非阻塞的調用方式,可構建異步客戶端。在這種方式中,Thrift 提供了新的類 TAsyncClientManager 用於管理客戶端的請求,在一個線程上追蹤請求和響應,同時通過接口 AsyncClient 傳遞標準的參數和 callback 對象,服務調用完成後,callback 提供了處理調用結果和異常的方法。
首先我們看 callback 的實現:
清單 12.CallBack 的實現:MethodCallback.java

 package service.callback; 
 import org.apache.thrift.async.AsyncMethodCallback; 

 public class MethodCallback implements AsyncMethodCallback { 
    Object response = null; 

    public Object getResult() { 
        // 返回結果值
        return this.response; 
    } 

    // 處理服務返回的結果值
    @Override 
    public void onComplete(Object response) { 
        this.response = response; 
    } 
    // 處理調用服務過程中出現的異常
    @Override 
    public void onError(Throwable throwable) { 

    } 
 }

如代碼所示,onComplete 方法接收服務處理後的結果,此處我們將結果 response 直接賦值給 callback 的私有屬性 response。onError 方法接收服務處理過程中拋出的異常,此處未對異常進行處理。
創建非阻塞服務器端實現代碼,將 HelloServiceImpl 作爲具體的處理器傳遞給異步 Thrift 服務器,代碼如下:
清單 13.HelloServiceAsyncServer.java

 package service.server; 
 import org.apache.thrift.server.TNonblockingServer; 
 import org.apache.thrift.server.TServer; 
 import org.apache.thrift.transport.TNonblockingServerSocket; 
 import org.apache.thrift.transport.TNonblockingServerTransport; 
 import org.apache.thrift.transport.TTransportException; 
 import service.demo.Hello; 
 import service.demo.HelloServiceImpl; 

 public class HelloServiceAsyncServer { 
    /** 
     * 啓動 Thrift 異步服務器
     * @param args 
     */ 
    public static void main(String[] args) { 
        TNonblockingServerTransport serverTransport; 
        try { 
            serverTransport = new TNonblockingServerSocket(10005); 
            Hello.Processor processor = new Hello.Processor( 
                    new HelloServiceImpl()); 
            TServer server = new TNonblockingServer(processor, serverTransport); 
            System.out.println("Start server on port 10005 ..."); 
            server.serve(); 
        } catch (TTransportException e) { 
            e.printStackTrace(); 
        } 
    } 
 }

HelloServiceAsyncServer 通過 java.nio.channels.ServerSocketChannel 創建非阻塞的服務器端等待客戶端的連接。
創建異步客戶端實現代碼,調用 Hello.AsyncClient 訪問服務端的邏輯實現,將 MethodCallback 對象作爲參數傳入調用方法中,代碼如下:
清單 14.HelloServiceAsyncClient.java

 package service.client; 
 import java.io.IOException; 
 import org.apache.thrift.async.AsyncMethodCallback; 
 import org.apache.thrift.async.TAsyncClientManager; 
 import org.apache.thrift.protocol.TBinaryProtocol; 
 import org.apache.thrift.protocol.TProtocolFactory; 
 import org.apache.thrift.transport.TNonblockingSocket; 
 import org.apache.thrift.transport.TNonblockingTransport; 
 import service.callback.MethodCallback; 
 import service.demo.Hello; 

 public class HelloServiceAsyncClient { 
    /** 
     * 調用 Hello 服務
     * @param args 
     */ 
    public static void main(String[] args) throws Exception { 
        try { 
            TAsyncClientManager clientManager = new TAsyncClientManager(); 
            TNonblockingTransport transport = new TNonblockingSocket( 
                    "localhost", 10005); 
            TProtocolFactory protocol = new TBinaryProtocol.Factory(); 
            Hello.AsyncClient asyncClient = new Hello.AsyncClient(protocol, 
                    clientManager, transport); 
            System.out.println("Client calls ....."); 
            MethodCallback callBack = new MethodCallback(); 
            asyncClient.helloString("Hello World", callBack); 
            Object res = callBack.getResult(); 
            while (res == null) { 
                res = callBack.getResult(); 
            } 
            System.out.println(((Hello.AsyncClient.helloString_call) res) 
                    .getResult()); 
        } catch (IOException e) { 
            e.printStackTrace(); 
        } 
  } 
 }

HelloServiceAsyncClient 通過 java.nio.channels.Socketchannel 創建異步客戶端與服務器建立連接。在本文中異步客戶端通過以下的循環代碼實現了同步效果,讀者可去除這部分代碼後再運行對比。
清單 15. 異步客戶端實現同步效果代碼段

Object res = callBack.getResult();

// 等待服務調用後的返回結果

while (res == null) {
   res = callBack.getResult();
}

通過與清單 9 和清單 10 的代碼比較,我們可以構建一個 TNonblockingServer 服務類型的服務端,在客戶端構建一個 TFramedTransport 傳輸層的同步客戶端和一個 TNonblockingTransport 傳輸層的異步客戶端,那麼一個服務就可以通過一個 socket 端口提供兩種不同的調用方式。有興趣的讀者可以嘗試一下。

常見問題
NULL 問題
我們在對服務的某個方法調用時,有時會出現該方法返回 null 值的情況,在 Thrift 中,直接調用一個返回 null 值的方法會拋出 TApplicationException 異常。在清單 2 中,HelloServiceImpl 裏實現了 helloNull 方法,返回 null 值,我們在 HelloServiceClient.java 中加入調用該方法的代碼,出現如下圖所示的異常:
圖 4. TApplicationException 異常

爲了處理返回 null 值情況,我們要捕獲該異常,並進行相應的處理,具體客戶端代碼實現如下:
清單 16. 處理服務返回值爲 null 的代碼

 package service.client; 
 import org.apache.thrift.TApplicationException; 
 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; 
 import service.demo.Hello; 

 public class HelloServiceClient { 
    /** 
     * 調用 Hello 服務,並處理 null 值問題
     * @param args 
     */ 
    public static void main(String[] args) { 
        try { 
            TTransport transport = new TSocket("localhost", 7911); 
            transport.open(); 
            TProtocol protocol = new TBinaryProtocol(transport); 
            Hello.Client client = new Hello.Client(protocol); 
            System.out.println(client.helloNull()); 
            transport.close(); 
        } catch (TTransportException e) { 
            e.printStackTrace(); 
        } catch (TException e) { 
            if (e instanceof TApplicationException 
                    && ((TApplicationException) e).getType() ==   
                                 TApplicationException.MISSING_RESULT) { 
                System.out.println("The result of helloNull function is NULL"); 
            } 
        } 
    } 
 }

調用 helloNull 方法後,會拋出 TApplicationException 異常,並且異常種類爲 MISSING_RESULT,本段代碼顯示,捕獲該異常後,直接在控制檯打印“The result of helloNull function is NULL”信息。
類型定義案例
第一包(命名空間)
namespace java com.winwill.thrift
定義枚舉
enum RequestType{
SAY_HELLO ,
QUERY_TIME ,
}
定義機構體
struct Request{
1:required RequestType type;
2:required string name;
3:optional i32 age

}
定義異常
exception RequestException{
1:required i32 code;
2:optional string reason;
}
定義service
service HelloService{
string doAction(1:Request request) throws (1:RequestException qe)
}

實現服務端
有阻塞,非阻塞,線程池,半同步半異步,Selector多種服務端實現模式。
TSimpleServer – 簡單的單線程服務模型,常用於測試
TThreadedServer – 多線程服務模型,使用阻塞式IO,每個請求創建一個線程。(java 不支持)
TThreadPoolServer – 多線程服務模型,使用標準的阻塞式IO,預先創建一組線程處理請求。
TThreadedSelectorServer 允許你用多個線程來處理網絡I/O。它維護了兩個線程池,一個用來處理網絡I/O,另一個用來進行請求的處理
TNonblockingServer – 多線程服務模型,使用非阻塞式IO(需使用TFramedTransport數據傳輸方式),只有一個線程來處理消息
THsHaServer - 半同步半異步的服務模型,一個單獨的線程用來處理網絡I/O,一個worker線程池用來進行消息的處理
create Socket
TSocket:採用TCP Socket進行數據傳輸,阻塞型socket,用於客戶端,採用系統函數read和write進行讀寫數據;(BIO)
TNonblockingSocket (NIO) 異步客戶端使用

create Transport
TSocket:採用TCP Socket進行數據傳輸,阻塞型socket,用於客戶端,採用系統函數read和write進行讀寫數據;(BIO)
TNonblockingSocket (NIO) 異步客戶端使用
TSSLSocket 繼承TSocket,阻塞型socket, 用於客戶端;採用openssl的接口進行讀寫數據。
THttpTransport:採用Http傳輸協議進行數據傳輸
TFileTransport – 以文件形式進行傳輸。
TMemoryTransport – 將內存用於I/O. java實現時內部實際使用了簡單的ByteArrayOutputStream。
TZlibTransport – 使用zlib進行壓縮, 與其他傳輸方式聯合使用。當前無java實現。
TFramedTransport – 以frame爲單位進行傳輸,非阻塞式服務中使用。類似於Java中的NIO。
TFastFramedTransport 與TFramedTransport相比,始終使用相同的Buffer,提高了內存的使用率。
TSaslClientTransport與TSaslServerTransport, 提供SSL校驗
create Protocol must same as service based on transport
TBinaryProtocol – 二進制格式.
TCompactProtocol – 壓縮格式
TDenseProtocol -繼承TCompactProtocol,不包含meta信息
TJSONProtocol – JSON格式
TSimpleJSONProtocol –提供JSON只寫協議, 生成的文件很容易通過腳本語言解析。
基本概念:http://elf8848.iteye.com/blog/1960131

服務端編寫的一般步驟:
1. 創建Handler
2. 基於Handler創建Processor
3. 創建Transport(通信方式)
4. 創建Protocol方式(設定傳輸格式)
5. 基於Processor, Transport和Protocol創建Server
6. 運行Server

客戶端編寫的一般步驟:
1. 創建Transport
2. 創建Protocol方式
3. 基於Transport和Protocol創建Client
4. 運行Client的方法

參考資源:
http://blog.csdn.net/zhu_tianwei/article/details/40948233
http://www.aboutyun.com/thread-7139-1-1.html
http://www.ibm.com/developerworks/cn/java/j-lo-apachethrift/
https://my.oschina.net/yybear/blog/101217
http://www.ibm.com/developerworks/cn/java/j-lo-apachethrift/
http://www.cnblogs.com/lori/p/3526657.html
https://my.oschina.net/penngo/blog/492253

發佈了36 篇原創文章 · 獲贊 7 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章