1. 程式人生 > 其它 >Apache Thrift極簡入門

Apache Thrift極簡入門

簡介

Apache Thrift是Facebook開源的跨語言的RPC通訊框架,目前已經捐獻給Apache基金會管理,由於其跨語言特性和出色的效能,在很多網際網路公司得到應用,有能力的公司甚至會基於thrift研發一套分散式服務框架,增加諸如服務註冊、服務發現等功能。

RPC即Remote Procedure Call,翻譯為遠端過程呼叫。任何RPC協議的實現終極目標都是讓使用者在呼叫遠端方法的時候就像是呼叫本地方法一樣簡單,從而提高使用遠端服務的效率。

現代網際網路架構多數基於SOA思想而搭建,即面向服務化的架構。服務提供方稱為Provider,服務的使用方稱為Consumer,有時也把服務提供方稱為Server端,使用方稱為Client端,即典型的CS模型。這裡的遠端呼叫,主要指跨程序的呼叫,Provider和Consumer可能是同一機器的不同程序,也可能在不同的機器,通過網路相互通訊,大部分情況下兩者會部署在不同的物理機器上,這種情況下由於網路通訊的開銷就會對RPC框架的效能要求極高。

  thrift

下面分別從服務端和客戶端的視角來介紹Thrift在RPC中的應用。

服務端(Server)

服務端需要釋出一個服務給別人使用,首先要約定好服務的介面,包括以下幾個部分:

  • 服務的名稱
  • 服務使用時的引數
  • 返回結果

Thrift自己規定了一套介面定義語言(IDL)來描述服務,用字尾為.thrift的檔案來描述,比如我們要提供一個打招呼的服務,傳入姓名,然後返回一段友好的語句,Thrift檔案HelloService.thrift的內容如下:

namespace java com.yuanwhy.service
service HelloService{
    string sayHello(1:string name)
}

Thrift檔案定義好之後,就約定好了介面的使用方式,但是仍然還不能使用,需要我們用thrift命令來生成對應的程式語言的檔案,比如用一下命令來生成HelloService.class的Java檔案。

thrift -r --gen java HelloService.thrift

命令列引數 -r 代表遞迴生成裡面引用的其他檔案, --gen 後面跟生成的目標語言,最後跟上thrift檔案。

生成的Java檔案裡會有一個介面HelloService.Iface,這就是一個普通的Java Interface,服務端要提供該介面的實現,實現之前需要引用libthrift的jar包,比如我們這麼實現:

package com.yuanwhy.service;

import org.apache.thrift.TException;
public class HelloServiceImpl implements HelloService.Iface {

    @Override
    public String sayHello(String name) throws TException {
        return "Hello, " + name + "!";
    }
}

這樣,服務端就實現了服務的邏輯部分,但是要讓別人在網路上真正可用,我們還得把這個服務釋出出去,釋出的方式就是藉助Socket程式設計,監聽一個對外服務的埠,這也是網路通訊的基本套路。利用Thrift提供的API,在HelloServiceProvider類中啟動Thrift服務:

package com.yuanwhy.demo;

import com.yuanwhy.service.HelloService;
import com.yuanwhy.service.HelloServiceImpl;
import org.apache.thrift.TProcessor;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TSimpleServer;
import org.apache.thrift.transport.TServerSocket;
import org.apache.thrift.transport.TTransportException;

public class HelloServiceProvider {
    /**
     * 啟動 Thrift 伺服器
     *
     * @param args
     */
    public static void main(String[] args) {
        try {
            // 設定服務埠為 7911
            TServerSocket serverTransport = new TServerSocket(7911);
            TProcessor processor = new HelloService.Processor(new HelloServiceImpl());
            TServer server = new TSimpleServer(new TServer.Args(serverTransport).processor(processor));
            System.out.println("Start server on port 7911...");
            server.serve();
        } catch (TTransportException e) {
            e.printStackTrace();
        }
    }
}

執行main函式之後,服務端就會監聽7911埠,開始對外提供sayHello的服務了。

客戶端(Client)

客戶端想要使用服務端通過thrift釋出的服務,只需要遵循面向介面程式設計的基本思想,引用Java介面,用thrift的API連線服務端即可,比如HelloServiceConsumer類中這麼使用sayHello服務:

package com.yuanwhy.demo;

import com.yuanwhy.service.HelloService;
import org.apache.thrift.TApplicationException;
import org.apache.thrift.TEnum;
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 why on 2015/10/26.
 */
public class HelloServiceConsumer {
    public static void main(String[] args) {
        TTransport transport = new TSocket("0.0.0.0", 7911);
        try {
            transport.open();
            TProtocol protocol = new TBinaryProtocol(transport);
            HelloService.Client client = new HelloService.Client(protocol);
            System.out.println(client.sayHello("yuanwhy"));
            transport.close();
        } catch (TApplicationException e) {

            if (e.getType() == TApplicationException.MISSING_RESULT) {
                System.out.println("null");
            }

        } catch (TException e){

        }
    }

}

客戶端執行結果會列印Hello, yuanwhy!,表明服務呼叫成功。仔細觀察一下客戶端的程式碼會發現,基本和普通的Socket程式設計沒有太大的區別,只是又被thrift做了一層的封裝,讓我們可以按照約定的介面直接像client.sayHello("yuanwhy")這樣呼叫遠端服務。

注意:客戶端如果是在不同的Java專案中呼叫服務,只需要服務端把thrift檔案或者生成的Java介面檔案以API的方式提供出來即可,客戶端絕對不需要引用HelloServiceImpl實現類,因為目的就是讓邏輯在服務端實現,對客戶端透明。

另外,當服務端返回null時,客戶端會拋一個Type為TApplicationException.MISSING_RESULT的異常出來,如果不處理就會影響客戶端正常的流程。這一點可以在Thrift的生成程式碼中看出來:

public String recv_sayHello() throws org.apache.thrift.TException
{
    sayHello_result result = new sayHello_result();
    receiveBase(result, "sayHello");
    if (result.isSetSuccess()) {
    return result.success;
    }
    throw new org.apache.thrift.TApplicationException(org.apache.thrift.TApplicationException.MISSING_RESULT, "sayHello failed: unknown result");
}

總結

以上對Thrift的使用只做了個簡單的介紹,真正在專案中使用Thrift還會涉及很多,比如各種Thrift資料結構的使用,在對Thrift介面進行升級過程中struct的欄位最好保留原有欄位順序以達到相容目的,還比如客戶端應該建立連線池機制,而不是每次呼叫服務時都去新建一次TCP連線等等。

理解Thrift基本的執行模式,對後續更深入研究RPC實現機制會有更好的幫助。本文所用的示例程式碼可以參考https://github.com/yuanwhy/thrift-demo , 更多Thrift資料結構及其使用方法,可以直接訪問官網https://thrift.apache.org 來學習。



連結:https://www.jianshu.com/p/89a4ba971a48