[thrift] thrift基本原理及使用

參考文章RPC 基本原理與 Apach Thrift 初體驗
java

RPC基本原理

RPC(Remote Procedure Call),遠程過程調用,大部分的RPC框架都遵循以下三個開發步驟:python

1. 定義一個接口說明文件:描述了對象(結構體)、對象成員、接口方法等一系列信息;
2. 經過RPC框架所提供的編譯器,將接口說明文件編譯成具體的語言文件;
3. 在客戶端和服務器端分別引入RPC編譯器所生成的文件,便可像調用本地方法同樣調用服務端代碼;

   
   
   

  

RPC通訊過程以下圖所示
RPC通訊過程
通訊過程包括如下幾個步驟:web

一、客戶過程以正常方式調用客戶樁(client stub,一段代碼);
二、客戶樁生成一個消息,而後調用本地操做系統;
三、客戶端操做系統將消息發送給遠程操做系統;
四、遠程操做系統將消息交給服務器樁(server stub,一段代碼);
五、服務器樁將參數提取出來,而後調用服務器過程;
六、服務器執行要求的操做,操做完成後將結果返回給服務器樁;
七、服務器樁將結果打包成一個消息,而後調用本地操做系統;
八、服務器操做系統將含有結果的消息發送回客戶端操做系統;
九、客戶端操做系統將消息交給客戶樁;
十、客戶樁將結果從從消息中提取出來,返回給調用它的客戶過程;

   
   
   

  

全部這些步驟的效果是,將客戶過程對客戶樁發出的本地調用轉換成對服務器過程的本地調用,而客戶端和服務器都不會意識到有中間步驟的存在。apache

這個時候,你可能會想,既然是調用另外一臺機器的服務,使用 RESTful API 也能夠實現啊,爲何要選擇 RPC 呢?咱們能夠從兩個方面對比:編程

  • 資源粒度。RPC 就像本地方法調用,RESTful API 每一次添加接口均可能須要額外地組織開放接口的數據,這至關於在應用視圖中再寫了一次方法調用,並且它還須要維護開發接口的資源粒度、權限等;
  • 流量消耗。RESTful API 在應用層使用 HTTP 協議,哪怕使用輕型、高效、傳輸效率高的 JSON 也會消耗較大的流量,而 RPC 傳輸既可使用 TCP 也可使用 UDP,並且協議通常使用二制度編碼,大大下降了數據的大小,減小流量消耗。

對接異構第三方服務時,一般使用 HTPP/RESTful 等公有協議,對於內部的服務調用,應用選擇性能更高的二進制私有協議。json

Thrift架構

thrift主要用於各個服務之間的RPC通訊,支持跨語言。thrift是一個典型的CS結構,客戶端和服務端可使用不一樣的語言開發,thrift經過IDL(Interface Description Language)來關聯客戶端和服務端。thrift的總體架構圖以下圖所示
服務器

thrift架構

圖中Your Code是用戶實現的業務邏輯,接下來的 FooService.ClientFoo.write()/read()是thrift根據IDL生成的客戶端和服務端的代碼,對應於RPC中Client stub和Server stub。TProtocol 用來對數據進行序列化與反序列化,具體方法包括二進制,JSON 或者 Apache Thrift 定義的格式。TTransport 提供數據傳輸功能,使用 Apache Thrift 能夠方便地定義一個服務並選擇不一樣的傳輸協議。
以下圖所示爲thrift的網絡棧結構
thrift網絡棧結構

thirft使用socket進行數據傳輸,數據以特定的格式發送,接收方進行解析。咱們定義好thrift的IDL文件後,就可使用thrift的編譯器來生成雙方語言的接口、model,在生成的model以及接口代碼中會有解碼編碼的代碼。markdown

TTransport層

表明thrift的數據傳輸方式,thrift定義了以下幾種經常使用數據傳輸方式網絡

  • TSocket: 阻塞式socket;
  • TFramedTransport: 以frame爲單位進行傳輸,非阻塞式服務中使用;
  • TFileTransport: 以文件形式進行傳輸;

TProtocol層

表明thrift客戶端和服務端之間傳輸數據的協議,通俗來說就是客戶端和服務端之間傳輸數據的格式(例如json等),thrift定義了以下幾種常見的格式多線程

  • TBinaryProtocol: 二進制格式;
  • TCompactProtocol: 壓縮格式;
  • TJSONProtocol: JSON格式;
  • TSimpleJSONProtocol: 提供只寫的JSON協議;

thrift支持的Server模型

thrift主要支持如下幾種服務模型

  • TSimpleServer: 簡單的單線程服務模型,經常使用於測試;
  • TThreadPoolServer: 多線程服務模型,使用標準的阻塞式IO;
  • TNonBlockingServer: 多線程服務模型,使用非阻塞式IO(須要使用TFramedTransport數據傳輸方式);
  • THsHaServer: THsHa引入了線程池去處理,其模型讀寫任務放到線程池去處理,Half-sync/Half-async處理模式,Half-async是在處理IO事件上(accept/read/write io),Half-sync用於handler對rpc的同步處理;

thrift IDL文件

thrift IDL不支持無符號的數據類型,由於不少編程語言中不存在無符號類型,thrift支持一下幾種基本的數據類型

  • byte: 有符號字節
  • i16: 16位有符號整數
  • i32: 32位有符號整數
  • i64: 63位有符號整數
  • double: 64位浮點數
  • string: 字符串

此外thrift還支持如下容器類型:

  • list: 一系列由T類型的數據組成的有序列表,元素能夠重複;
  • set: 一系列由T類型的數據組成的無序集合,元素不可重複;
  • map: 一個字典結構,Key爲K類型,Value爲V類型,至關於java中的HashMap;

thrift容器中元素的類型能夠是除了service以外的任何類型,包括exception

thirft支持struct類型,目的就是講一些數據聚合在一塊兒,方便傳輸管理,struct定義形式以下:

struct People {
    1:string name;
    2:i32 age;
    3:string gender;
}
   
   
   

  

thrift支持枚舉類型,定義形式以下:

enum Gender {
    MALE,
    FEMALE
}
   
   
   

  

thrift支持自定義異常類型exception,異常定義形式以下:

exception RequestException {
    1:i32 code;
    2:string reason;
}
   
   
   

  

thrift定義服務至關於Java中建立接口同樣,建立的service通過代碼生thrift代碼生成工具編譯後就會生成客戶端和服務端的框架代碼,service的定義形式以下:

service HelloWorldService {
    // service中能夠定義若干個服務,至關於Java Interface中定義的方法
    string doAction(1:string name, 2:i32 age);
}
   
   
   

  

thrift支持給類型定義別名,以下所示:

typedef i32 int
typedef i64 long
   
   
   

  

thrift也支持常量的定義,使用const關鍵字:

const i32 MAX_RETRIES_TIME = 10;
const string MY_WEBSITE = "http://facebook.com";
   
   
   

  

thrift支持命名空間,命名空間至關於Java中的package,主要用於組織代碼,thrift使用關鍵字namespace定義命名空間,格式是namespace 語言名 路徑,以下示例所示:

namespace java com.test.thrift.demo
   
   
   

  

thrift也支持文件包含,至關於CPP中的include,Java中的import,使用關鍵字include:

include "global.thrift"
   
   
   

  

#///**/均可以做爲thrift文件中的註釋。

thrift提供兩個關鍵字requiredoptional,分別用於表示對應的字段是必填的仍是可選的(推薦儘可能使用optional),以下所示:

struct People {
    1:required string name;
    2:optional i32 age;
}
   
   
   

  

thrift應用示例

本示例中咱們使用java編寫thrift的服務端程序,使用python編寫thrift的客戶端程序。

首先定義thrift IDL文件

// data.thrift
namespace java thrift.generated
namespace py py.thrift.generated

typedef i16 short
typedef i32 int
typedef i64 long
typedef bool boolean
typedef string String

// struct關鍵字用於定義結構體,至關於面向對象編程語言中的類
struct Person {
    // 至關於定義類中的成員,並生成相應的get和set方法,optional表示username這個成員能夠沒有
    1: optional String username,
    2: optional int age,
    3: optional boolean married
}

// 定義一個異常類型,用於接口中可能拋出的異常
exception DataException {
    1: optional String message,
    2: optional String callStack,
    3: optional String date
}

// 定義服務接口
service PersonService {
    Person getPersonByUsername(1: required String username) throws (1: DataException data),
    void savePerson(1: required Person person)
}
   
   
   

  

執行thrift --gen java src/thrift/data.thrift生成對應的java代碼,並引入到Java工程當中,代碼結構以下圖所示

thrift示例代碼結構

編寫Java服務端代碼,data.thrift的service中定義了兩個服務,咱們須要定義相應服務的實現類(至關於handler),以下所示:

import thrift.generated.DataException;
import thrift.generated.Person;
import thrift.generated.PersonService;

public class PersonServiceImpl implements PersonService.Iface {
    @Override
    public Person getPersonByUsername(String username) throws DataException {
        System.out.println("Got Client Param: " + username);

        return new Person().setUsername(username).setAge(20).setMarried(false);
    }

    @Override
    public void savePerson(Person person) throws DataException {
        System.out.println("Got Client Param:");

        System.out.println(person.username);
        System.out.println(person.age);
        System.out.println(person.married);
    }
}
   
   
   

  

同時咱們須要藉助thrift爲咱們提供的類庫實現一個服務器來監聽rpc請求,代碼以下所示:

import org.apache.thrift.TProcessorFactory;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.server.THsHaServer;
import org.apache.thrift.server.TServer;
import org.apache.thrift.transport.TFramedTransport;
import org.apache.thrift.transport.TNonblockingServerSocket;
import thrift.generated.PersonService;

public class ThriftServer {
    public static void main(String[] args) throws Exception {

        // 定義服務器使用的socket類型
        TNonblockingServerSocket tNonblockingServerSocket = new TNonblockingServerSocket(8899);

        // 建立服務器參數
        THsHaServer.Args arg = new THsHaServer.Args(tNonblockingServerSocket).minWorkerThreads(2).maxWorkerThreads(4);

        // 請求處理器
        PersonService.Processor<PersonServiceImpl> processor = new PersonService.Processor<>(new PersonServiceImpl());

        // 配置傳輸數據的格式
        arg.protocolFactory(new TCompactProtocol.Factory());
        // 配置數據傳輸的方式
        arg.transportFactory(new TFramedTransport.Factory());
        // 配置處理器用來處理rpc請求
        arg.processorFactory(new TProcessorFactory(processor));

        // 本示例中使用半同步半異步方式的服務器模型
        TServer server = new THsHaServer(arg);
        System.out.println("Thrift Server Started!");
        // 啓動服務
        server.serve();
    }
}
   
   
   

  

編寫python客戶端,執行thrift --gen py src/thrift/data.thrift生成對應的python代碼,並引入到python工程當中,代碼結構以下圖所示

thrift python代碼結構

python客戶端代碼以下所示:

# -*- coding:utf-8 -*-
__author__ = 'kpzhang'

from py.thrift.generated import PersonService
from py.thrift.generated import ttypes

from thrift import Thrift
from  thrift.transport import TSocket
from  thrift.transport import TTransport
from  thrift.protocol import TCompactProtocol

import sys

reload(sys)
sys.setdefaultencoding('utf8')

try:
    tSocket = TSocket.TSocket("localhost", 8899)
    tSocket.setTimeout(900)

    transport = TTransport.TFramedTransport(tSocket)
    protocol = TCompactProtocol.TCompactProtocol(transport)
    client = PersonService.Client(protocol)

    transport.open()

    person = client.getPersonByUsername("張三")

    print person.username
    print person.age
    print person.married

    print '---------------------'

    newPerson = ttypes.Person();
    newPerson.username = "李四"
    newPerson.age = 30
    newPerson.married = True

    client.savePerson(newPerson)

    transport.close()

except Thrift.TException, tx:
    print '%s' % tx.message
   
   
   

  

客戶端能夠向調用本地的方法同樣調用服務端的方法。

相關文章
相關標籤/搜索