一、 概念
Apache的Thrift軟件框架,是用來進行可伸縮的、跨語言的服務開發,它通過一個代碼生成引擎來構建高效、無縫的服務,這些服務能夠實現跨語言調度,目前支持的語言有: C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, OCaml and Delphi 等。
二、 安裝Thrift
目前官網最新的版本是v0.10.0。下面主要介紹基于mac os系統的Thrift的安裝。windows系統可參考官網教程進行安裝。
mac os官網提供的安裝方法比較復雜,這里介紹下mac 下的home brew的安裝方法。
打開終端,如果你的mac還沒有安裝home brew,那么先要安裝home brew,使用以下命令:
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
然后安裝最新版本的Thrift:
brew install thrift
然后安裝最新版本的Thrift:
brew install thrift
等待安裝完成,然后輸入以下命令,如果打印了Thrift的版本信息,表示安裝成功:
thrift -version
三、 Thrift支持的類型
-
基本類型
bool:布爾值(true或者false)
byte:8位的有符號字節(java的byte類型)
i16:16位的有符號整數(java的short類型)
i32:32位的有符號整數(java的int類型)
i64:64位的有符號長整型(java的long類型)
double:一個64位的浮點數(java的double類型)
string: 一個utf8編碼的字符串文本(java的String) -
Structs
Thrift的structs用來定義一個通用對象,但是沒有繼承關系。 -
集合類型
list:一個有序的元素列表。元素可以重復。
set:一個無序的元素集合,集合中元素不能重復。
map:一個鍵值對的數據結構,相當于Java中的HashMap。 -
異常類型Exceptions
Thrift的異常類型,除了是繼承于靜態異常基類以外,其他的跟struct是類似的。表示的是一個異常對象。 -
服務類型Services
Thrift 的service類型相當于定義一個面向對象編程的一個接口。Thrift的編譯器會根據這個接口定義來生成服務端和客戶端的接口實現代碼。
四、一個簡單的Thrift調用實例
- 編寫.thrift文件,也就是IDL(接口描述語言)文件
以下是data.thrift文件:
namespace java thrift.generated
namespace py py.thrift.generatedtypedef i16 short
typedef i32 int
typedef i64 long
typedef bool boolean
typedef string Stringstruct 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 date
}service PersonService {Person getPersonByUsername(1: required String username) throws (1: DataException dataException),void savePerson(1: required Person person) throws (1: DataException dataException)
}
- 使用thrift的編譯器,生成客戶端和服務端的代碼
生成java的客戶端服務端代碼:
thrift --gen java src/thrift/data.thrift
生成Python的客戶端服務端代碼:
thrift --gen py src/thrift/data.thrift
其他語言的生成也是類似。
- Thrift的調用
以java作為服務端,java作為客戶端以及Python作為客戶端對服務端進行請求。
java服務端代碼:
public class PersonServiceImpl implements PersonService.Iface {@Overridepublic 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;}@Overridepublic void savePerson(Person person) throws DataException, TException {System.out.println("Got client param: ");System.out.println(person.getUsername());System.out.println(person.getAge());System.out.println(person.isMarried());}
}
java客戶端代碼:
public class ThriftClient {public static void main(String[] args) {TTransport transport = new TFramedTransport(new TSocket("localhost", 8899), 600);TProtocol protocol = new TCompactProtocol(transport);PersonService.Client client = new PersonService.Client(protocol);try {transport.open();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(30);person1.setMarried(true);client.savePerson(person1);} catch (Exception e) {throw new RuntimeException(e.getMessage(), e);} finally {transport.close();}}}
python客戶端代碼:
__author__ = '作者'from py.thrift.generated import PersonService
from py.thrift.generated import ttypesfrom thrift import Thrift
from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TCompactProtocolimport sysreload(sys)
sys.setdefaultencoding('utf8')try:tSocket = TSocket.TSocket('localhost', 8899)tSocket.setTimeout(600)transport = TTransport.TFramedTransport(tSocket)protocol = TCompactProtocol.TCompactProtocol(transport)client = PersonService.Client(protocol)transport.open()person = client.getPersonByUsername('張三')print person.usernameprint person.ageprint person.marriedprint '------------------'newPerson = ttypes.Person()newPerson.username = '李四'newPerson.age = 30newPerson.married = Trueclient.savePerson(newPerson)except Thrift.TException, tx:print '%s' % tx.message
先運行服務端,然后再運行客戶端,觀察服務端和客戶端的輸出來理解下thrift的一個調用流程是什么樣的。
五、Thrift的架構原理
以下是thrift的客戶端和服務端交互的一個原理圖:
如上圖,客戶端在進行遠程方法調用時,首先是通過Thrift的編譯器生成的客戶端,將調用信息(方法名,參數信息)以指定的協議進行封裝,而傳輸層TTransport是對協議層的封裝進行處理(比如封裝成幀frame),并通過網絡發送出去。服務端這邊流程跟客戶端相反,收到客戶端發過來的數據后,首先經過傳輸層對傳過來的數據進行處理,然后使用特定的協議(跟客戶端是一一對應的)進行解析,然后再通過生成的Processor調用用戶編寫的代碼,如果有返回值的話,返回值以逆向的順序,即通過協議層封裝,然后傳輸層處理對數據進行發送,到了客戶端那邊就是對服務端返回的數據進行處理,使用特定協議進行解析,然后得到一個調用個的結果。
以上就是Thrift的RPC調用的一個完整流程。
六、 Thrift的傳輸格式(協議層)
Thrift之所以被稱為一種高效的RPC框架,其中一個重要的原因就是它提供了高效的數據傳輸。
以下是Thrift的傳輸格式種類:
TBinaryProtocol: 二進制格式。效率顯然高于文本格式。
TCompactProtocol:壓縮格式。在二進制基礎上進一步壓縮。
TJSONProtocol:JSON格式。
TSimpleJSONProtocol:提供JSON只寫協議(缺少元數據信息),生成的文件很容易用過腳本語言解析。
TDebugProtocol:使用易懂的刻度文本格式,以便于調試。
以上可以看到,在線上環境,使用TCompactProtocol格式效率是最高的,同等數據傳輸占用網絡帶寬是最少的。
七、Thrift的數據傳輸方式(傳輸層)
TSocket:阻塞式socket。
TFramedTransport:以frame為單位進行傳輸,非阻塞式服務中使用。
TFileTransport:以文件形式進行傳輸。
TMemoryTransport:將內存用于I/O,Java是現實內部實際使用了簡單的ByteArrayOutputStream。
TZlibTransport:使用zlib進行壓縮,與其他傳輸方式聯合使用。當前無java實現。
八、Thrift的服務模型
TSimpleServer
簡單的單線程服務模型,常用于測試。只在一個單獨的線程中以阻塞I/O的方式來提供服務。所以它只能服務一個客戶端連接,其他所有客戶端在被服務器端接受之前都只能等待。
TNonblockingServer
它使用了非阻塞式I/O,使用了java.nio.channels.Selector,通過調用select(),它使得程序阻塞在多個連接上,而不是單一的一個連接上。TNonblockingServer處理這些連接的時候,要么接受它,要么從它那讀數據,要么把數據寫到它那里,然后再次調用select()來等待下一個準備好的可用的連接。通用這種方式,server可同時服務多個客戶端,而不會出現一個客戶端把其他客戶端全部“餓死”的情況。缺點是所有消息是被調用select()方法的同一個線程處理的,服務端同一時間只會處理一個消息,并沒有實現并行處理。
THsHaServer(半同步半異步server)
針對TNonblockingServer存在的問題,THsHaServer應運而生。它使用一個單獨的線程專門負責I/O,同樣使用java.nio.channels.Selector,通過調用select()。然后再利用一個獨立的worker線程池來處理消息。只要有空閑的worker線程,消息就會被立即處理,因此多條消息能被并行處理。效率進一步得到了提高。
TThreadedSelectorServer
它與THsHaServer的主要區別在于,TThreadedSelectorServer允許你用多個線程來處理網絡I/O。它維護了兩個線程池,一個用來處理網絡I/O,另一個用來進行請求的處理。
TThreadPoolServer
它使用的是一種多線程服務模型,使用標準的阻塞式I/O。它會使用一個單獨的線程來接收連接。一旦接受了一個連接,它就會被放入ThreadPoolExecutor中的一個worker線程里處理。worker線程被綁定到特定的客戶端連接上,直到它關閉。一旦連接關閉,該worker線程就又回到了線程池中。
這意味著,如果有1萬個并發的客戶端連接,你就需要運行1萬個線程。所以它對系統資源的消耗不像其他類型的server一樣那么“友好”。此外,如果客戶端數量超過了線程池中的最大線程數,在有一個worker線程可用之前,請求將被一直阻塞在那里。
如果提前知道了將要連接到服務器上的客戶端數量,并且不介意運行大量線程的話,TThreadPoolServer可能是個很好的選擇。
九、 其他
Facebook開源了一個簡化thrift java開發的一個庫——swift。swift是一個易于使用的、基于注解的java庫,主要用來創建thrift可序列化類型和服務。
github地址:https://github.com/facebook/swift
十、 總結
Thrift是一個跨語言的RPC框架,如果有跨語言交互的業務場景,Thrift可能是一個很好的選擇。如果使用恰當,thrift將是一個非常高效的一個RPC框架。開發時應根據具體場景選擇合適的協議,傳輸方式以及服務模型。缺點就是Thrift并沒有像dubbo那樣提供分布式服務的支持,如果要支持分布式,需要開發者自己去開發集成。