1. 程式人生 > >跨平臺通訊中介軟體thrift學習【Java版本】(轉)

跨平臺通訊中介軟體thrift學習【Java版本】(轉)

1. What is thrift?

Thrift是一個跨語言的服務部署框架,最初由Facebook於2007年開發,2008年進入Apache開源專案。跨平臺通訊中thrift可以作為二進位制的高效能的通訊中介軟體,支援資料(物件)序列化和多種型別的RPC服務。

 

2. thrift為我們做了什麼?

首先我們需要先了解下任何RPC的解決方案都包含如下幾層實現:

· 服務層(service):RPC介面定義與實現

· 協議層(protocol):RPC報文格式和資料編碼格式

· 傳輸層(transport):實現底層的通訊(如 socket)以及系統相關的功能(如事件迴圈、多執行緒)

 

Thrift通過一箇中間語言(IDL, 介面定義語言)來定義RPC的介面和資料型別,然後通過一個編譯器生成不同語言的程式碼(目前支援C++,Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, Smalltalk等),並由生成的程式碼負責RPC協議層和傳輸層的實現。我們只需要去實現具體的介面實現就可以了。

 

Thrift實際上是實現了請求響應的C/S模式,通過程式碼生成工具將介面定義檔案生成伺服器端程式碼,從而實現服務端和客戶端跨語言的支援。使用者在Thirft描述檔案中宣告自己的服務,這些服務經過編譯後會生成相應語言的程式碼檔案,然後使用者實現服務便可以了。

 

3. thrift基本概念

資料型別

     * Base Types:基本型別

     * Struct:結構體型別

     * Container:容器型別,即List、Set、Map

     * Exception:異常型別

     * Service: 定義物件的介面,和一系列方法

 

協議層型別

Thrift可以讓你選擇客戶端與服務端之間傳輸通訊協議的類別,在傳輸協議上總體上劃分為文字(text)和二進位制(binary)傳輸協議, 為節約頻寬,提供傳輸效率,一般情況下

 

使用二進位制型別的傳輸協議為多數,但有時會還是會使用基於文字型別的協議,這需要根據專案/產品中的實際需求:

    * TBinaryProtocol – 二進位制編碼格式進行資料傳輸。

    * TCompactProtocol – 這種協議非常有效的,使用Variable-Length Quantity (VLQ) 編碼對資料進行壓縮。

    * TJSONProtocol – 使用JSON的資料編碼協議進行資料傳輸。

    * TSimpleJSONProtocol – 這種節約只提供JSON只寫的協議,適用於通過指令碼語言解析

    * TDebugProtocol – 在開發的過程中幫助開發人員除錯用的,以文字的形式展現方便閱讀。

 

傳輸層型別

    * TSocket – 使用堵塞式I/O進行傳輸,也是最常見的模式。

    * THttpTransport – 採用Http傳輸協議進行資料傳輸

    * TFileTransport – 顧名思義按照檔案的方式程序傳輸,雖然這種方式不提供Java的實現,但是實現起來非常簡單。

    * TZlibTransport – 使用執行zlib壓縮,不提供Java的實現。

 

    下面幾個類主要是對上面幾個類地裝飾(採用了裝飾模式),以提高傳輸效率。

    TBufferedTransport – 對某個Transport物件操作的資料進行buffer,即從buffer中讀取資料進行傳輸,或者將資料直接寫入buffer

    TFramedTransport – 以frame為單位進行傳輸,非阻塞式服務中使用。同TBufferedTransport類似,也會對相關資料進行buffer,同時,它支援定長資料傳送和接收。

    TMemoryBuffer – 從一個緩衝區中讀寫資料,使用記憶體I/O,就好比Java中的ByteArrayOutputStream實現。

 

服務端型別

    * TSimpleServer– 簡單的單執行緒服務模型,常用於測試

    * TThreadedServer – 多執行緒服務模型,使用阻塞式IO,每個請求建立一個執行緒。

    * TThreadPoolServer – 執行緒池服務模型,使用標準的阻塞式IO,預先建立一組執行緒處理請求。

    * TNonblockingServer – 多執行緒服務模型,使用非阻塞式IO(需使用TFramedTransport資料傳輸方式)

 

4. Thrift 架構

Thrift 包含一個完整的堆疊結構用於構建客戶端和伺服器端。下圖描繪了Thrift的整體架構。

 

 

如圖所示,圖中黃色部分是使用者實現的業務邏輯,褐色部分是根據 Thrift 定義的服務介面描述檔案生成的客戶端和伺服器端程式碼框架,紅色部分是根據 Thrift 檔案生成程式碼實現資料的讀寫操作。紅色部分以下是 Thrift 的傳輸體系、協議以及底層 I/O 通訊,使用 Thrift 可以很方便的定義一個服務並且選擇不同的傳輸協議和傳輸層而不用重新生成程式碼。

 

Thrift 伺服器包含用於繫結協議和傳輸層的基礎架構,它提供阻塞、非阻塞、單執行緒和多執行緒的模式執行在伺服器上,可以配合伺服器 / 容器一起執行,可以和現有的 J2EE 伺服器 /Web 容器無縫的結合。

 

5. 安裝

http://thrift.apache.org/ 官網下載,需要用ant編譯成lib,最終實際專案中只需要加入如下jar包:

* libthrift.jar

* slf4j-api-1.5.8.jar

* slf4j-log4j12-1.5.8.jar

* log4j-1.2.15.jar

 

6. 簡單例項

建立一個簡單的Helloworld。首先根據Thrift的語法規範編寫指令碼檔案Hello.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() 
 }

 

其中定義了服務 Hello 的五個方法,每個方法包含一個方法名,引數列表和返回型別。每個引數包括引數序號,引數型別以及引數名。

 

使用thrift.exe -gen java Hello.thrift生成兩個gen-java資料夾,裡面就有我們生成的伺服器端介面Hello.java了。

 

建立 HelloServiceImpl.java 檔案並實現 Hello.java 檔案中的 Hello.Iface 介面,程式碼如下:

 

 
 package service.demo; 
 import org.apache.thrift.TException; 
 public class HelloServiceImpl implements Hello.Iface { 
    @Override 
    public boolean helloBoolean(boolean 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 String helloNull() throws TException { 
        return null; 
    } 
    @Override 
    public String helloString(String para) throws TException { 
        return para; 
    } 
    @Override 
    public void helloVoid() throws TException { 
        System.out.println("Hello World"); 
    } 
 }

 

建立伺服器端實現程式碼,將 HelloServiceImpl 作為具體的處理器傳遞給 Thrift 伺服器,程式碼如下:

 

 

 
 package service.server; 
 import org.apache.thrift.TProcessor; 
 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; 
 import service.demo.Hello; 
 import service.demo.HelloServiceImpl; 
 
 public class HelloServiceServer { 
    /** 
     * 啟動 Thrift 伺服器
     * @param args 
     */ 
    public static void main(String[] args) { 
        try { 
            // 設定服務埠為 7911 
            TServerSocket serverTransport = new TServerSocket(7911); 
            // 設定協議工廠為 TBinaryProtocol.Factory 
            Factory proFactory = new TBinaryProtocol.Factory(); 
            // 關聯處理器與 Hello 服務的實現
            TProcessor processor = new Hello.Processor(new HelloServiceImpl()); 
            TServer server = new TThreadPoolServer(processor, serverTransport, 
                    proFactory); 
            System.out.println("Start server on port 7911..."); 
            server.serve(); 
        } catch (TTransportException e) { 
            e.printStackTrace(); 
        } 
    } 
 }

建立客戶端實現程式碼,呼叫 Hello.client 訪問服務端的邏輯實現,程式碼如下:

 

package service.client; 
 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 服務
     * @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); 
            // 呼叫服務的 helloVoid 方法
            client.helloVoid(); 
            transport.close(); 
        } catch (TTransportException e) { 
            e.printStackTrace(); 
        } catch (TException e) { 
            e.printStackTrace(); 
        } 
    } 
 }

 

程式碼編寫完後執行伺服器,再啟動客戶端呼叫服務 Hello 的方法 helloVoid,在伺服器端的控制檯視窗輸出“Hello World”。

 

首先環境介紹一下:

1.IntelliJ IDEA 2017.1

2.thrift-0.9.3

相信大家在看我這篇文章的時候已經對thrift通訊框架已有所調研,這裡就不再贅述了,直接進入正題:

<1>建立HelloWorld.thrift

namespace java com.thrift.demo

service HelloWorldService{
string sayHello(1:string username)
}

 <2>利用thrift生成HelloWorld.java檔案,cmd指令下進入thrift當前目錄下輸入命令

thrift.exe -gen java HelloWorld.thrift

java為要生成檔案的型別,HelloWorld.thrift為前面的檔案。

<3>建立IDEA 下的maven專案,其中的好處就不一一說明了,最重要的一條就是可以在pom.xml檔案中新增dependency,能夠在專案中自行下載庫檔案,方便協同開發中出現的開發包不對應的情況。

<dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
        </dependency>
        <dependency>
            <groupId>jstl</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
        <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.7.5</version>
        </dependency>

 

複製程式碼

上述pom引入相應的依賴項就可以讓它自行下載。

<4>專案的結構圖當前如下所示:

File--Project Structure--Modules,在main資料夾下新建java資料夾並設為Soueces型別(因為在Sources檔案下可以新建java class檔案)

同時將thrift生成的HelloWorld.java檔案複製到該目錄下

<5>實現介面Iface

java程式碼:HelloWorldImpl.java

package com.jmust.thrift.demo;

import org.apache.thrift.TException;

/**
 * Created by Administrator on 2017/3/31.
 */
public class HelloWorldImpl implements HelloWorldService.Iface {

    public HelloWorldImpl() {
    }

    @Override
    public String sayHello(String username) throws TException {
        return "Hi,"+username+"Welcome to my blog http://www.cnblogs.com/zfygiser";
    }
}

 

<6>服務端TSimpleServer

java程式碼:HelloServer.java

/**
 * Created by Administrator on 2017/3/31.
 */
package com.jmust.thrift.demo;

import org.apache.thrift.TProcessor;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TSimpleServer;
import org.apache.thrift.transport.TServerSocket;

public class HelloServer {
    public final static int SERVER_PORT = 7099;
    private static String SERVER_IP = "localhost";

    public void startServer() {
        try {
            System.out.println("HelloWorld Server start...");

            TServerSocket serverTransport = new TServerSocket(SERVER_PORT);
            TServer.Args args = new TServer.Args(serverTransport);
            TProcessor process = new HelloWorldService.Processor(new HelloWorldImpl());
            TBinaryProtocol.Factory portFactory = new TBinaryProtocol.Factory(true, true);
            args.processor(process);
            args.protocolFactory(portFactory);

            TServer server = new TSimpleServer(args);
            server.serve();

        } catch (Exception e) {
            System.out.println("Server start error");
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        HelloServer server = new HelloServer();
        server.startServer();
    }
}

 

複製程式碼

<7>編寫客戶端程式碼

java程式碼:Client.java

package com.jmust.thrift.demo;

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 Administrator on 2017/4/1.
 */
public class Client {
    public static final int SERVER_PORT = 7099;
    public static final String SERVER_IP = "localhost";

    public void startClient(String username) {
        TTransport tTransport = null;
        try {
            tTransport = new TSocket(SERVER_IP, SERVER_PORT);
            //協議要和服務端一致
            TProtocol protocol = new TBinaryProtocol(tTransport);
            HelloWorldService.Client client = new HelloWorldService.Client(protocol);
            tTransport.open();

            String result = client.sayHello(username);
            System.out.println("Thrift client result=" + result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Client client = new Client();
        client.startClient("zfy");
    }
}

 

客戶端測試成功,截圖如下: