Thrift應(yīng)用詳解與實例剖析

Thrift簡介

The Apache Thrift software framework, for scalable cross-language services development, combines a software stack with a code generation engine to build services that work efficiently and seamlessly between C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, OCaml and Delphi and other languages.
http://thrift.apache.org

通過官方介紹弹惦,我們可以了解到Thrift是一個軟件框架,可以提供跨語言的服務(wù)開發(fā)。Thrift框架包含一個軟件棧说订,包含生成各種語言的引擎,我們通過Thrift提供的接口定義語言(IDL)來定義接口澎蛛,然后引擎會自動生成各種語言的代碼瞻想。

Thrift最初是由Facebook研發(fā),主要用于各服務(wù)之間的RPC通信德谅,支持跨語言,常用的語言比如C++萨螺、Java窄做、Python愧驱、PHP、Ruby椭盏、Erlang组砚、Perl、Haskell掏颊、C#糟红、Cocoa、JavaScript乌叶、Node.js盆偿、Smalltalk、 OCaml and Delphi and other languages

Thrift是一個典型的C/S(客戶端/服務(wù)端)結(jié)構(gòu)枉昏,客戶端和服務(wù)端可以使用不同的語言開發(fā)陈肛。既然客戶端和服務(wù)端可以使用不同的語言開發(fā),那么一定就要有一種中間語言來關(guān)聯(lián)客戶端和服務(wù)端的語言兄裂,這種語言就是IDL(Interface Definition Language)句旱。

Thrift架構(gòu)

image

Thrift傳輸格式
圖中,TProtocol(協(xié)議層)晰奖,定義數(shù)據(jù)傳輸格式谈撒,例如:

  • TBinaryProtocol:二進制格式;
  • TCompactProtocol:壓縮格式匾南;
  • TJSONProtocol:JSON格式啃匿;
  • TSimpleJSONProtocol:提供JSON只寫協(xié)議, 生成的文件很容易通過腳本語言解析;
  • TDebugProtocol:使用易懂的可讀的文本格式蛆楞,以便于debug

Thrift數(shù)據(jù)傳輸方式
TTransport(傳輸層)溯乒,定義數(shù)據(jù)傳輸方式,可以為TCP/IP傳輸豹爹,內(nèi)存共享或者文件共享等)被用作運行時庫裆悄。

  • TSocket:阻塞式socker;
  • TFramedTransport:以frame為單位進行傳輸臂聋,非阻塞式服務(wù)中使用光稼;
  • TFileTransport:以文件形式進行傳輸;
  • TMemoryTransport:將內(nèi)存用于I/O孩等,java實現(xiàn)時內(nèi)部實際使用了簡單的ByteArrayOutputStream艾君;
  • TZlibTransport:使用zlib進行壓縮, 與其他傳輸方式聯(lián)合使用肄方,當(dāng)前無java實現(xiàn)冰垄;

Thrift支持的服務(wù)模型

  • TSimpleServer:簡單的單線程服務(wù)模型,常用于測試权她;
  • TThreadPoolServer:多線程服務(wù)模型播演,使用標(biāo)準(zhǔn)的阻塞式IO冀瓦;
  • TNonblockingServer:多線程服務(wù)模型,使用非阻塞式IO(需使用TFramedTransport數(shù)據(jù)傳輸方式)写烤;
  • THsHaServer:THsHa引入了線程池去處理,其模型把讀寫任務(wù)放到線程池去處理拾徙;HaIf-sync/HaIf-async的處理模式洲炊,HaIf-async是在處理IO事件上(accept/read/write io),HaIf-sync用于handler對rpc的同步處理尼啡。

Thrift實際上是實現(xiàn)了C/S模式暂衡,通過代碼生成工具將thrift文生成服務(wù)器端和客戶端代碼(可以為不同語言),從而實現(xiàn)服務(wù)端和客戶端跨語言的支持崖瞭。用戶在Thirft文件中聲明自己的服務(wù)狂巢,這些服務(wù)經(jīng)過編譯后會生成相應(yīng)語言的代碼文件,然后客戶端調(diào)用服務(wù)书聚,服務(wù)器端提服務(wù)便可以了唧领。

一般將服務(wù)放到一個.thrift文件中,服務(wù)的編寫語法與C語言語法基本一致雌续,在.thrift文件中有主要有以下幾個內(nèi)容:變量聲明(variable)斩个、數(shù)據(jù)聲明(struct)和服務(wù)接口聲明(service, 可以繼承其他接口)。

Thrift數(shù)據(jù)類型

  • Thrift不支持無符號類型驯杜,因為很多編程語言不存在無符號類型受啥,比如Java。
  • byte:有符號字節(jié)
  • i16:16位有符號整數(shù)
  • i32:32位有符號整數(shù)
  • i64:64位有符號整數(shù)
  • dubbo:64位有浮點數(shù)
  • string:字符串
  • bool: 布爾值 (true or false), one byte
  • 特殊類型(括號內(nèi)為對應(yīng)的Java類型):binary(ByteBuffer):未經(jīng)過編碼的字節(jié)流

Thrift容器類型

  • 集合中的元素可以是除了service之外的任何類型鸽心,包括exception
  • list<T>:一系列由T類型的數(shù)據(jù)組成的有序列表滚局,元素可以重復(fù)
  • set<T>:一系列由T類型的數(shù)據(jù)組成的無序集合,元素不可以重復(fù)
  • map<K,V>:一個字典結(jié)構(gòu)顽频,key為K類型藤肢,value為V類型,相當(dāng)于Java中的HashMap

Thrift工作原理

  • 如何實現(xiàn)多語言的通信冲九?
  • 數(shù)據(jù)傳輸使用socket(多種語言均支持)谤草,數(shù)據(jù)在以特定的格式(String等)發(fā)送,接收方語言進行解析
  • 定義thrift的文件莺奸,由thrift文件(IDL)生成雙方語言的接口丑孩、model,在生成的model以及接口中會有解碼編碼的代碼灭贷。

Thrift IDL文件

namespace java com.example.project
struct News{
    1:i32 id;
    2:string title;
    3:string content;
    4:string mediaFrom;
    5:string author;
}

service IndexNewsOperatorServices{
    bool indexNews(1:NewsModel indexNews),
    bool removeNewsById(1:i32 id)
}

結(jié)構(gòu)體(struct)

  • 就像C語言一樣温学,Thrift 支持struct 類型,目的就是將一些數(shù)據(jù)聚合在一起甚疟,方便傳輸管理仗岖。struct 的定義形式如下:
struct Peopel{
    1:string name;
    2:i32 age;
    3:string gender;
}

枚舉(Enums)

  • 枚舉的定義形式和Java的Enum定義類似:
enum Gender{
    MALE,       
   FEMALE  
}     

異常(exception)

  • Thrift 支持自定義exception逃延,規(guī)則與struct 一樣。
exception RequestException{
    1:i32 code;
    2:string reason;
}

服務(wù)(service)

  • Thrift 定義服務(wù)相當(dāng)于Java中創(chuàng)建Interface一樣轧拄,創(chuàng)建的service經(jīng)過代碼生成命令之后揽祥,就會生成客戶端和服務(wù)端的框架代碼,定義形式如下:
service HelloWorldService{
    //service中定義的函數(shù)檩电,相當(dāng)于Java interface中定義的方法
    string doAction(1:string name,2:i32 age);
}

類型定義

  • Thrift 支持類型C++一樣的typedef定義:
typedef i32 int
typedef i64 long

常量(const)

  • Thrift 也支持常量定義拄丰,使用const關(guān)鍵字:
const i32 MAX_RETRIES_TIME = 10;
const string MY_WEBSITE = "http://facebook.com"

命名空間

  • Thrift 的命名空間相當(dāng)于Java中的package的意思,主要目的是組織代碼俐末。Thrift使用關(guān)鍵字namespace定義命名空間:
namespace java com.test.thrift.demo
  • 格式是:namespace 語言名 路徑

文件包含

  • Thrift 也支持文件包含料按,相當(dāng)于C/C++中的include,Java中的import卓箫,使用關(guān)鍵字include定義:
include "global.thrift"

注釋

  • Thrift 注釋方式支持shell風(fēng)格的注釋载矿,支持C/C++風(fēng)格的注釋,即#和//開頭的語句都當(dāng)做注釋烹卒,/**/包裹的語句也是注釋闷盔。

可選與必選

  • Thrift 提供兩個關(guān)鍵字required、optional甫题,分別用于表示對應(yīng)的字段是必填的還是可選的
struct Peopel{
    1:required string name;
    2:optional i32 age;
}

Windows平臺下Thrift安裝與使用

下載與安裝:http://thrift.apache.org/download

image.png

下載thrift-0.12.0.exe文件命名為thrift .exe放在D盤下的一個thtift文件夾中馁筐。

建議:修改名稱為thrift.exe,方便使用

建議:配置環(huán)境變量
在系統(tǒng)變量Path中添加thrift路徑(D:\thrift)到環(huán)境變量中

這樣就可以在dos窗口來使用thrift命令了坠非,”thrift -version ”

image.png

生成代碼

  • 了解了如何定義thrift文件之后敏沉,我們需要用定義好的thrift文件生成我們需要的目標(biāo)語言的源碼
  • 首先需要定義thrift接口描述文件,如data.thrift
namespace java thrift.generated

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

struct Person{
    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 data
}

service PersonService{
    Person getPersonByUsername(1: required String username) throws (1: DataException dataException),

    void savePerson(1: required Person person) throws (1: DataException dataException)
}
  • 生成代碼炎码,在idea控制臺上輸入:thrift --gen java src/thrift/data.thrift

    image.png

  • 引入maven或gradle依賴

//maven依賴
<dependency>
    <groupId>org.apache.thrift</groupId>
    <artifactId>libthrift</artifactId>
    <version>0.12.0</version>
</dependency>

//gradle依賴
compile group: 'org.apache.thrift', name: 'libthrift', version: '0.12.0'

實現(xiàn)Thrift定義的service

/**
 * @author: huangyibo
 * @Date: 2019/3/13 23:38
 * @Description: 實現(xiàn)Thrift定義的服務(wù)接口
 */
public class PersonServiceImpl implements PersonService.Iface {

    @Override
    public Person getPersonByUsername(String username) throws DataException, TException {
        System.out.println("Got client Param:" + username);
        Person person = new Person();
        person.setUsername(username);
        person.setAge(20);
        person.setMarried(false);
        return person;
    }

    @Override
    public void savePerson(Person person) throws DataException, TException {
        System.out.println("Got client Param:" + person);
        System.out.println(person.getUsername());
        System.out.println(person.getAge());
        System.out.println(person.isMarried());
    }
}

Thrift服務(wù)端實現(xiàn)

public class ThriftServer {
    public static void main(String[] args) throws TTransportException {
        //非阻塞socket
        TNonblockingServerSocket serverSocket = new TNonblockingServerSocket(8899);
        //高可用server
        THsHaServer.Args arg = new THsHaServer.Args(serverSocket).minWorkerThreads(2).maxWorkerThreads(4);

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

        arg.protocolFactory(new TCompactProtocol.Factory());
        arg.transportFactory(new TFramedTransport.Factory());
        arg.processorFactory(new TProcessorFactory(processor));

        TServer server = new THsHaServer(arg);

        System.out.println("Thrift server started!");

        server.serve();
    }
}

Thrift客戶端實現(xiàn)

public class ThriftClient {

    public static void main(String[] args) {
        TTransport transport = new TFramedTransport(new TSocket("localhost",8899),1000);
        TProtocol protocol = new TCompactProtocol(transport);
        PersonService.Client client = new PersonService.Client(protocol);

        try {
            transport.open();//打開socket
            Person person = client.getPersonByUsername("張三");
            System.out.println(person.getUsername());
            System.out.println(person.getAge());
            System.out.println(person.isMarried());

            System.out.println("-------------------------");

            Person person1 = new Person();
            person1.setUsername("李四");
            person1.setAge(20);
            person1.setMarried(false);
            client.savePerson(person1);

        } catch (Exception e) {
            throw new RuntimeException(e.getMessage(),e);
        } finally {
            transport.close();
        }
    }
}

Thrift Python實現(xiàn)的服務(wù)端和客戶端

修改Thrift的IDL文件

namespace py py.thrift.generated

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

struct Person{
    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 data
}

service PersonService{
    Person getPersonByUsername(1: required String username) throws (1: DataException dataException),

    void savePerson(1: required Person person) throws (1: DataException dataException)
}

通過thrift命令生成Python版thrift代碼

thrift --gen py src/thrift/data.thrift

實現(xiàn)Thrift定義的service(Python)

# _*_ coding:utf-8 _*_
__author__ = 'huangyibo'

from py.thrift.generated import ttypes

class PersonServiceImpl:
    
    def getPersonByUsername(self, username):
        print 'Got client param:' + username
        person = ttypes.Person()
        person.username = username
        person.age = 20
        person.married = False
        return person

    def savePerson(self, person):
        print 'Got client param:'
        print person.username
        print person.age
        print person.married

Thrift服務(wù)端代碼實現(xiàn)(Python)

# _*_ coding:utf-8 _*_
__author__ = 'huangyibo'

from py.thrift.generated import PersonService
from PersonServiceImpl import PersonServiceImpl

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

try:
    personServiceHandler = PersonServiceImpl()
    processor = PersonService.Processor(personServiceHandler)
    
    serverSocket = TSocket.TServerSocket(port=8899)
    transportFactory = TTransport.TFramedTransportFactory()
    protocolFactory = TCompactProtocol.TCompactProtocolFactory()

    server = TServer.TSimpleServer(processor,serverSocket,transportFactory,protocolFactory)
    server.service()

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

Thrift客戶端代碼實現(xiàn)(Python)

# _*_ coding:utf-8 _*_
__author__ = 'huangyibo'

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(1000)

    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 = 20
    newPerson.married = True

    client.savePerson(newPerson)

    transport.close()

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

這樣就可以實現(xiàn)Java客戶端調(diào)用Python服務(wù)端盟迟、Python客戶端調(diào)用Java服務(wù)端(其他語言之間的跨語言調(diào)用和這個類似)、Java客戶端調(diào)用Java服務(wù)端潦闲、Python客戶端調(diào)用Python服務(wù)端攒菠,特別是跨語言異構(gòu)平臺之間的調(diào)用價值很大,且比基于HTTP調(diào)用方式的RPC框架效率高很多歉闰。

參考:

Thrift 使用方法

Thrift小試牛刀

Thrift IDL

Thrift 的原理和使用

Apache Thrift系列詳解(一) - 概述與入門

Thrift入門初探(2)--thrift基礎(chǔ)知識詳解

Thrift原理與使用實例

Thrift學(xué)習(xí)筆記—IDL基本類型

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末辖众,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子和敬,更是在濱河造成了極大的恐慌凹炸,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件昼弟,死亡現(xiàn)場離奇詭異啤它,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進店門变骡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來离赫,“玉大人,你說我怎么就攤上這事塌碌≡ㄐ兀” “怎么了?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵誊爹,是天一觀的道長蹬刷。 經(jīng)常有香客問我,道長频丘,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任泡态,我火速辦了婚禮搂漠,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘某弦。我一直安慰自己桐汤,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布靶壮。 她就那樣靜靜地躺著怔毛,像睡著了一般。 火紅的嫁衣襯著肌膚如雪腾降。 梳的紋絲不亂的頭發(fā)上拣度,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天,我揣著相機與錄音螃壤,去河邊找鬼抗果。 笑死,一個胖子當(dāng)著我的面吹牛奸晴,可吹牛的內(nèi)容都是我干的冤馏。 我是一名探鬼主播,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼寄啼,長吁一口氣:“原來是場噩夢啊……” “哼逮光!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起墩划,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤涕刚,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后走诞,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體副女,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了碑幅。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片戴陡。...
    茶點故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖沟涨,靈堂內(nèi)的尸體忽然破棺而出恤批,到底是詐尸還是另有隱情,我是刑警寧澤裹赴,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布喜庞,位于F島的核電站,受9級特大地震影響棋返,放射性物質(zhì)發(fā)生泄漏延都。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一睛竣、第九天 我趴在偏房一處隱蔽的房頂上張望晰房。 院中可真熱鬧,春花似錦射沟、人聲如沸殊者。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽猖吴。三九已至,卻和暖如春挥转,著一層夾襖步出監(jiān)牢的瞬間海蔽,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工扁位, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留准潭,地道東北人。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓域仇,卻偏偏與公主長得像刑然,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子暇务,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,446評論 2 348