1. 程式人生 > >關於thrift的一些探索——thrift序列化技術

關於thrift的一些探索——thrift序列化技術

thrift的IDL,相當於一個鑰匙。而thrift傳輸過程,相當於從兩個房間之間的傳輸資料。

QQ20160426-3@2x

圖-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檔案大小如下圖:

QQ20160512-0@2x

thrift binary file是json檔案大小的 0.63。為什麼檔案小這麼多,我認為有以下幾個原因,① json是文字的,而thrift是二進位制的,文字檔案跟二進位制檔案的區別,可以參考我的另一篇文章, 二進位制檔案跟普通文字檔案的區別。② thrift序列化過程中,由於IDL在client、servo端都有一套,所以沒有傳輸一些欄位比如field name等等,只需要傳遞field id, 這樣也是一些節省位元組的方法。另外,提醒一下,序列化跟壓縮不是一回事,比如上面的檔案壓縮為xz看看大小如下圖:

QQ20160512-5@2x

壓縮後體積變為原來的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