關於thrift的一些探索——thrift序列化技術
thrift的IDL,相當於一個鑰匙。而thrift傳輸過程,相當於從兩個房間之間的傳輸資料。
圖-1
(因為Thrift採用了C/S模型,不支援雙向通訊:client只能遠端呼叫server端的RPC介面,但client端則沒有RPC供server端呼叫,這意味著,client端能夠主動與server端通訊,但server端不能主動與client端通訊而只能被動地對client端的請求作出應答。所以把上圖-1中的箭頭,畫為單向箭頭更為直觀)基於上圖,Thrift的IDL檔案的作用可以描述為,從房間A要傳遞一批資料給房間B,把資料裝在箱子裡,鎖上鎖,然後通過Transport Channel把帶鎖的箱子給房間B,而Thrift IDL就是一套鎖跟鑰匙。房間A跟房間B都有同樣的一個thrift IDL檔案,有了這個檔案就可以生成序列化跟反序列化的程式碼,就像鎖跟鑰匙一樣。而一旦沒有了Thrift IDL檔案,房間A無法將資料序列化好鎖緊箱子,房間B沒有鑰匙開啟用箱子鎖好傳輸過來的資料。因此,IDL檔案的作用就在此。
一、為什麼要用Thrift序列化, 不用純文字的協議
我能想到的用thrift提供的thrift binary protocol而不用json、xml等純文字序列化協議的幾個好處如下:
- 序列化後的體積小, 省流量
- 序列化、反序列化的速度更快,提高效能
- 相容性好,一些介面,涉及多種語言的int32、int64等等跟語言、機器、平臺有關, 用純文字可能有相容性的問題
- 結合thrift transport技術,可以RPC精準地傳遞物件,而純文字協議大多隻能傳遞資料,而不能完完全全傳遞物件
關於以上幾點我個人認為的好處,下面作一下簡單解答
1.1 序列化後的體積小, 省流量
給出測試的IDL Services_A.thrift內容如下,定義了一些資料格式,這個結構體資料複雜度一般,有list、也有list物件、也有一些基本的struct等等。
namespace php Services.test.A namespace java Services.tets.A struct student{ 1:required string studentName, #學生姓名 2:required string sex, #性別 3:required i64 age, #學生年齡 } struct banji{ 1:required string banjiName, #班級名稱 2:required list<student> allStudents, #所有學生 } struct school { 1:required string schoolName, 2:required i64 age, 3:required list<string> zhuanye, #所有專業 4:required list<banji> allBanji, #所有班級 }
分別把Services_A.thrift 序列化為json、跟thrift.bin檔案,序列化到檔案中。對比檔案大小。
這裡用php寫了一個例子, 程式碼如下:
<?php
/**
* Created by PhpStorm.
* User: dengpan
* Date: 16/4/21
* Time: 12:39
*/
ini_set('memory_limit','10240M');
require_once __DIR__ . "/Thrift/ClassLoader/ThriftClassLoader.php";
use Thrift\ClassLoader\ThriftClassLoader;
use Thrift\Protocol\TBinaryProtocol;
use Thrift\Transport\TSocket;
use Thrift\Transport\TFramedTransport;
use Thrift\Transport\TPhpStream;
use Thrift\Transport\TPhpStreamMy;
use Thrift\Transport\TBufferedTransport;
$loader = new ThriftClassLoader();
$loader->registerNamespace('Thrift', __DIR__);
$loader->registerNamespace('Services', __DIR__);
$loader->registerDefinition('Services', __DIR__);
$loader->register();
require "Types.php";
$school = [];
$school['schoolName'] = "hahhaha";
$school['age'] = 60;
$school['zhuanye'] = [
'專業1',
'專業2',
'專業3',
];
$nameArr = ["張三", "李四", "王五", "王菲", "張韶涵", "王祖賢", "范冰冰", "新垣結衣", "詹姆斯", "諾維茨基"];
$sexArr = ["男", "女"];
$allBanji = [];
for($i = 0; $i < 1000; $i ++)
{
$banji = [];
$banji['banjiName'] = "計算機" + $i + "班";
for($j = 0; $j < 1000; $j ++) {
$banji['allStudents'][] = new student(
[
'studentName' => $nameArr[rand(0, count($nameArr) - 1)],
'sex' => $sexArr[rand(0, count($sexArr) - 1)],
'age' => rand(0, 6) + 18,
]
);
}
$allBanji[] = new banji($banji);
}
$school['allBanji'] = $allBanji;
$sc = new school($school);
$str = json_encode($school);
file_put_contents("/tmp/json_1", $str);
$transport = new TBufferedTransport(new TPhpStreamMy(null, '/tmp/thrift_1.bin'), 1024, 1024);
$sc->write(new TBinaryProtocol($transport, true, true));
最終的結果, json檔案跟thrift binary檔案大小如下圖:
thrift binary file是json檔案大小的 0.63。為什麼檔案小這麼多,我認為有以下幾個原因,① json是文字的,而thrift是二進位制的,文字檔案跟二進位制檔案的區別,可以參考我的另一篇文章, 二進位制檔案跟普通文字檔案的區別。② thrift序列化過程中,由於IDL在client、servo端都有一套,所以沒有傳輸一些欄位比如field name等等,只需要傳遞field id, 這樣也是一些節省位元組的方法。另外,提醒一下,序列化跟壓縮不是一回事,比如上面的檔案壓縮為xz看看大小如下圖:
壓縮後體積變為原來的3.3%、4.4%。可以先序列化再壓縮傳輸,壓縮的compress-level要指定合理,不然壓縮跟解壓縮佔據系統資源而且耗時大。
1.2序列化、反序列化的速度更快,提高效能
下面給出一個序列化反序列化測試
分別用① java Thrift binary protocol跟java fastjson庫去序列以上對應格式為json, ②用C++ Thrift binary protocol與c++ jsoncpp序列化,對比速度,
java 測試程式碼如下:
public class ThriftDataWrite3 {
private static final Random sRandom = new Random();
public static void main(String[] args) throws IOException, TException{
//構造school物件
String[] nameArr = {"張三", "李四", "王五", "趙6", "王祖賢", "趙敏", "漩渦鳴人", "諾維茨基", "鄧肯", "克萊爾丹尼斯", "長門", "彌彥", "威少"};
int nameArrLength = nameArr.length;
school sc = new school();
sc.setSchoolName("哈哈哈哈哈哈");
sc.setAge(12);
List<String> l