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)
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
下載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 ”
生成代碼
- 了解了如何定義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
引入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框架效率高很多歉闰。