由於公司現在的開發業務模塊中,有使用到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