thrift的使用:(Java、Python之間跨語言呼叫)
阿新 • • 發佈:2020-06-29
由於公司現在的開發業務模組中,有使用到Java作為客戶端呼叫python伺服器端業務處理,因此在底下研究了下,結合了網上的優質文章,在此做一下記錄。
- thrift是一個軟體框架,用來進行可擴充套件且跨語言的服務的開發。它結合了功能強大的軟體堆疊和程式碼生成引擎,以構建在C++,Java,Go,Python,PHP,Ruby,Erlang,Perl,C#,Cocoa,JavaScript,Node.js,Smalltalk,and OCaml這些變成語言間無縫結合的。高效的服務。
- thrift最初由facebook開發用作系統內個語言之間的RPC通訊,2007年由facebook貢獻到Apache基金,08年5月進入Apache卵化器。支援多種語言之間的RPC方式的通訊:Java語言client可以構造一個物件,呼叫相應服務方法來呼叫python語言的服務,跨越語言的C/S RPC呼叫。
- thrift允許定義一個簡單的定義檔案中的資料型別和服務介面,以作為輸入檔案,編譯器生成程式碼用來方便地生成RPC客戶端和服務端通訊的無縫跨程式語言。
環境準備
本文是在window上進行演示的。有關在linux上安裝Python環境,請參考(https://blog.csdn.net/gdkyxy2013/article/details/79457590),個人感覺非常nice的。
本文使用的環境演示
- thrift-0.9.3
- thrift-0.9.3.tar.gz 下載地址:http://archive.apache.org/dist/thrift/0.9.3/
- hrift-0.9.3.exe 下載地址:http://archive.apache.org/dist/thrift/0.9.3/
- Python-3.8.3
thrift安裝
- 在D盤(任意碟符)新建一個Thrift資料夾,將下載的thrift-0.9.3重新命名為thrift.exe後放到該資料夾下。
- 配置環境變數,如圖
- 接下來測試thrift的環境變數是否安裝正確,Ctrl+R輸入cmd開啟DOS視窗,在視窗輸入“thrift -version”
Python環境安裝
- 雙擊下載的Python-3.8.3安裝包,勾選"新增到環境變數",一步步預設安裝即可。
- 開啟命令視窗,輸入python --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的編譯器會根據這個介面定義來生成服務端和客戶端的介面實現程式碼。
案例實現
- 依賴引用
java 的maven依賴
<!--thrift-->
<dependency>
<groupId>org.apache.thrift</groupId>
<artifactId>libthrift</artifactId>
<version>0.9.3</version>
</dependency>
首先使用Thrift之前需要定義一個符合Thrift語言規範的.thrift格式的介面檔案,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 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 data.thrift所在的路徑
生成Python的客戶端服務端程式碼
thrift -gen py data.thrift所在的路徑
Thrift的呼叫
java服務端介面服務程式碼:
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: ");
System.out.println(person.getUsername());
System.out.println(person.getAge());
System.out.println(person.isMarried());
}
}
開啟服務(在實際應用中保證只有一個)
public class ThriftServer {
public static void main(String[] args) throws Exception {
TNonblockingServerSocket serverSocket = new TNonblockingServerSocket(8899);
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();
}
}
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 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(600)
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)
except Thrift.TException, tx:
print '%s' % tx.message
Thrift的傳輸格式(協議層)
Thrift之所以被稱為一種高效的RPC框架,其中一個重要的原因就是它提供了高效的資料傳輸。
以下是Thrift的傳輸格式種類:
- TBinaryProtocol: 二進位制格式。效率顯然高於文字格式。
- TBinaryProtocol: TCompactProtocol:壓縮格式。在二進位制基礎上進一步壓縮。
- TBinaryProtocol: TJSONProtocol:JSON格式。
- TBinaryProtocol: TSimpleJSONProtocol:提供JSON只寫協議(缺少元資料資訊),生成的檔案很容易用過指令碼語言解析。
- TBinaryProtocol: 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執行緒,訊息就會被立即處理,因此多條訊息能被並行處理。效率進一步得到了提高。 - TThreadSelectorServer
它與THsHaServer的主要區別在於,TThreadSelectorServer允許你用多個執行緒來處理網路I/O。它維護了兩個執行緒池,一個用來處理網路I/O,另一個用來進行請求的處理。 - TThreadPoolServer
它使用的是一種多執行緒服務模型,使用標準的阻塞式I/O。它會使用一個單獨的執行緒來接受連線,一旦接收了一個連線,它會被放入ThreadPoolExecutor中的一個worker執行緒裡處理。worker執行緒被繫結到特定的客戶端連線上,直到它關閉。一旦關閉,該worker執行緒就又回到了執行緒池中。
這意味著,如果有1萬個併發的客戶端連線,你就需要執行1萬個執行緒,所以它對系統資源的消耗不像其他型別的server一樣那麼"友好"。此外,如果客戶端數量超過了執行緒池中的最大執行緒數,在有一個worker執行緒可用之前,請求將被阻塞在哪裡。如果提前知道了將要連線到伺服器上的客戶端數量,並且不介意執行大量執行緒的話,TThreadPoolServer可能是個很好的選擇。
由於一直在做JAVA方面的開發,Python一直沒有接觸。搭建開發環境試執行時沒有執行起來(實在抱歉),準備的個人例子無法在本文演示。
本文來自:https://blog.csdn.net/zw19910924/article/details/78178539