2017年10月23日

Thrift

Apache Thrift 提供了一個產生不同語言程式碼的 compiler engine,可在不同程式語言(C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, OCaml and Delphi)環境之間,透過 Thrift 以 RPC 方式傳輸資料。

安裝 thrift compiler tool

thrift compiler tool 是產生 thrift 程式碼的工具。

在 macos,可透過 macport 安裝,在 thrift Portfile 中查看目前支援的語言是 java, c#, C, haskell, php, erlang,還不是很清楚為什麼不支援 go, python, ruby 等語言。

sudo port install thrift +java +erlang
$ thrift --version
Thrift version 0.10.0

在 CentOS 可直接編譯

cd thrift-0.10.0
./configure
make
sudo make install

.thrift 文件

在安裝 thrift compiler 後,要撰寫一份 .thrift 定義文件,該檔案是一份以 thrift types 及 Services 定義的interface definition,其中 Services 就是 server 要實作的功能,並提供給 client 呼叫,然後才能用 compiler 產生某個對應程式語言的程式碼,使用方式如下。

thrift --gen <language> <Thrift filename>

Thrift Types

Base Types

一般程式語言都提供的最基本的資料類型

  • bool: A boolean value (true or false)
  • byte: An 8-bit signed integer
  • i16: A 16-bit signed integer
  • i32: A 32-bit signed integer
  • i64: A 64-bit signed integer
  • double: A 64-bit floating point number
  • string: A text string encoded using UTF-8 encoding

Special Types

  • binary: a sequence of unencoded bytes

    這是上述 string 的特殊格式,可用在跟 java 系統之間有更好的資料收送,未來可能會提升為 base type

Structs

定義 common object,這等同於 OOP 中的 class,但沒有繼承的機制,前面的數字 1: 是要讓 compiler 可針對不同版本 IDL 識別,可填上 default value,也可以設定為 optional。

ex:

struct User {  
    1: i16 gender = 1,  
    2: string username,  
    3: string password,  
    4: i32 id  
}

Containers

有三種 container types

  • list: 有順序的元素集合 An ordered list of elements,會對應到 STL vector, Java ArrayList, native arrays in scripting languages

  • set: 沒有順序的元素集合 An unordered set of unique elements,會對應到 an STL set, Java HashSet, set in Python, etc.

    Note: PHP 不支援 sets,會以 List 方式處理。

  • map: 唯一的 key 對應 value 的集合 A map of strictly unique keys to values,會對應到 an STL map, Java HashMap, PHP associative array, Python/Ruby dictionary, etc.

    如果有初始值時,可以在不同的程式語言中,以自訂的 code generator 替代為 custom data type

    為了提供最佳的相容性,key 的 data type 最好使用 Basic Type

Exceptions

Exception 等同於 structs,是繼承自不同程式語言的 native exception base class

exception NotFoundException{  
    1:i16 errorType,  
    2:string message  
}

Services

以 Thrift type 定義 services,其作用就像是 OOP 裡面的 interface,Thrift compiler 可以自動產生實作 client, server 的 stubs codes,service 之間有提供繼承的機制。

service 包含了一組 functions,每一個都有一個參數 list 及 return type。

return type 可以使用 void,也就是不回傳資料,但實際上,server 還是會回傳一個 response 給 client,用來告訴 client 已經把所有工作都做完了。

可以在 function 上增加一個 oneway modifier,這代表 clietn 不會等待 server 的 response,這表示只能保證 client 會呼叫 server 的 function,但不能保證 server 會依照呼叫的順序執行該 function。

service <name> {  
  <returntype> <name> (<arguments>)[throws (<exceptions>)]
}
service UserService{  
  void saveUser(1:User user),  
  User get(1:i32 id) throws (1:NotFoundException nfe),
  oneway void zip()
}
service Calculator extends shared.SharedService 

其他

  • include

    通過include引用其他的thrift文件,默認在當前路徑下尋找,也可以在相對路徑下尋找,需要通過編譯參數 -I 來設置

  • namespace 與 java 的 package 作用一樣

namespace java thrift.sa
namespace python thrift.sa
  • 常數
const i32 INT32CONSTANT = 9853
const map<string,string> MAPCONSTANT = {'hello':'world', 'goodnight':'moon'}
  • enum
enum Operation {  
  ADD = 1,  
  SUBTRACT = 2,  
  MULTIPLY = 3,  
  DIVIDE = 4  
}

Java 支援的傳輸格式, 方式

  • 支援的傳輸格式

    1. TBinaryProtocol 二進制格式.
    2. TCompactProtocol 壓縮格式
    3. TJSONProtocol JSON格式
    4. TSimpleJSONProtocol 提供 JSON 只寫協議, 生成的文件很容易通過腳本語言解析
    5. TDebugProtocol 使用易懂的可讀的文本格式,以便於debug
  • 支援的數據傳輸方式

    1. TSocket 阻塞式 socket server
    2. TFramedTransport 以frame為單位進行傳輸,非阻塞式服務中使用
    3. TFileTransport 以文件形式進行傳輸。
    4. TMemoryTransport 將 memory 用於I/O,java 實作時內部實際使用了簡單的ByteArrayOutputStream
    5. TZlibTransport – 使用zlib進行壓縮, 與其他傳輸方式聯合使用,目前無java實現
  • 支援的服務模型

    1. TSimpleServer 簡單的單線程服務模型,常用於測試
    2. TThreadPoolServer 多線程服務模型,使用標準的阻塞式IO。
    3. TNonblockingServer 多線程服務模型,使用非阻塞式IO(需使用TFramedTransport數據傳輸方式)

Example - bookservice.thrift

先寫 bookservice.thrift,然後以 thrift compiler 產生 java 及 erlang 的 code

namespace java tw.com.maxkit.test

struct Book_info{
    1: i32 book_id;
    2: string book_name;
    3: string book_author;
    4: double book_price;
    5: string book_publisher
}

service BookSender{
    void ping(),

    i32 add(
        1:i32 num1, 2:i32 num2
    ),

    bool sender(
        1: list<Book_info> books
    );

    oneway void sender2(
        1: list<Book_info> books
    );
}

產生 java 及 erlang 的 code

thrift --gen erl bookservice.thrift
thrift --gen java bookservice.thrift
gen-erl/
    book_sender_thrift.erl
    book_sender_thrift.hrl
    bookservice_constants.hrl
    bookservice_types.erl
    bookservice_types.hrl
gen-java/tw/com/maxkit/test/
    Book_info.java
    BookSender.java

Java

BookServiceHandler 實作 Server Side 的四個 function

import tw.com.maxkit.test.*;


public class BookServiceHandler implements BookSender.Iface {

    public BookServiceHandler() {
    }

    public void ping() {
        System.out.println("ping()");
    }

    public int add(int n1, int n2) {
        System.out.println("add(" + n1 + "," + n2 + ")");
        return n1 + n2;
    }

    public boolean sender(java.util.List<Book_info> books) throws org.apache.thrift.TException {

        System.out.println("Sender get books");
        for(Book_info b: books) {
            System.out.println("get book "+b.book_id);
        }
        return true;
    }

    public void sender2(java.util.List<Book_info> books) throws org.apache.thrift.TException {
        System.out.println("Sender2 get books");
        for(Book_info b: books) {
            System.out.println("get book "+b.book_id);
        }
    }

}

BookServer

import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TServer.Args;
import org.apache.thrift.server.TSimpleServer;
import org.apache.thrift.transport.TSSLTransportFactory;
import org.apache.thrift.transport.TServerSocket;
import org.apache.thrift.transport.TServerTransport;
import org.apache.thrift.transport.TSSLTransportFactory.TSSLTransportParameters;

import tw.com.maxkit.test.*;

public class BookServer {

    public static BookServiceHandler handler;

    public static BookSender.Processor processor;

    public static void main(String [] args) {
        try {
            handler = new BookServiceHandler();
            processor = new BookSender.Processor(handler);

            Runnable simple = new Runnable() {
                public void run() {
                    simple(processor);
                }
            };
            Runnable secure = new Runnable() {
                public void run() {
                    secure(processor);
                }
            };

            new Thread(simple).start();
            new Thread(secure).start();
        } catch (Exception x) {
            x.printStackTrace();
        }
    }

    public static void simple(BookSender.Processor processor) {
        try {
            TServerTransport serverTransport = new TServerSocket(9090);
            TServer server = new TSimpleServer(new Args(serverTransport).processor(processor));

            // Use this for a multithreaded server
            // TServer server = new TThreadPoolServer(new TThreadPoolServer.Args(serverTransport).processor(processor));

            System.out.println("Starting the simple server...");
            server.serve();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void secure(BookSender.Processor processor) {
        try {
            TSSLTransportParameters params = new TSSLTransportParameters();
            // The Keystore contains the private key
            params.setKeyStore("keystore", "max168kit", null, "PKCS12");

            TServerTransport serverTransport = TSSLTransportFactory.getServerSocket(9091, 0, null, params);
            TServer server = new TSimpleServer(new Args(serverTransport).processor(processor));

            // Use this for a multi threaded server
            // TServer server = new TThreadPoolServer(new TThreadPoolServer.Args(serverTransport).processor(processor));

            System.out.println("Starting the secure server...");
            server.serve();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

BookClient

import org.apache.thrift.TException;
import org.apache.thrift.transport.TSSLTransportFactory;
import org.apache.thrift.transport.TTransport;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TSSLTransportFactory.TSSLTransportParameters;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocol;
import tw.com.maxkit.test.BookSender;
import tw.com.maxkit.test.Book_info;

import java.util.ArrayList;

public class BookClient {
    public static void main(String[] args) {

        String servertype="simple";
        if (args.length == 1) {
            //System.out.println("Please enter 'simple' or 'secure'");
            //System.exit(0);
            servertype=args[0];
        }

        try {
            TTransport transport;
            if (servertype.contains("simple")) {
                transport = new TSocket("localhost", 9090);
                transport.open();
            } else {
                TSSLTransportParameters params = new TSSLTransportParameters();
                params.setTrustStore("keystore", "max168kit", null, "PKCS12");
                transport = TSSLTransportFactory.getClientSocket("localhost", 9091, 0, params);
            }

            TProtocol protocol = new TBinaryProtocol(transport);
            BookSender.Client client = new BookSender.Client(protocol);

            perform(client);

            transport.close();
        } catch (TException x) {
            x.printStackTrace();
        }
    }

    private static void perform(BookSender.Client client) throws TException {
        client.ping();
        System.out.println("ping()");

        int sum = client.add(1, 1);
        System.out.println("1+1=" + sum);

        Book_info b1 = new Book_info(1, "name1", "author1", 11.1, "publisher1");

        Book_info b2 = new Book_info(2, "name2", "author2", 22.2, "publisher2");

        ArrayList list = new ArrayList();
        list.add(b1);
        list.add(b2);

        boolean result = client.sender(list);
        System.out.println("sender1 result="+result);

        client.sender2(list);
        System.out.println("sender2 done");

    }
}

Erlang

book_server.erl

-module(book_server).
-include("book_sender_thrift.hrl").

%% API
-export([start/0, handle_function/2, ping/0, add/2, sender/1, sender2/1, stop/1]).

debug(Data)->
  io:format("Debug info:~s~n",[Data]).
debug(Format, Data) ->
  error_logger:info_msg(Format, Data).

ping() ->
  debug("ping()",[]),
  ok.

add(N1, N2) ->
  debug("add(~p,~p)",[N1,N2]),
  N1+N2.

sender(_L1) ->
  true.

sender2(_L1) ->
  ok.

start()->
  start(9090).

start(Port)->
  Handler = ?MODULE,
  debug("1",[]),
  Res=thrift_socket_server:start([{handler, Handler},
    {service, book_sender_thrift},
    {port, Port},
    {name, book_server}]),
  debug("2 ~p ~n",[Res]),
  Res.

stop(Server)->
  thrift_socket_server:stop(Server).

%%handle_function(Function, Args) when is_atom(Function), is_tuple(Args) ->
%%  case Function of
%%    ping ->
%%      {reply, ping()};
%%    add ->
%%      {reply, add(tuple_to_list(Args))};
%%    % add function here
%%    _ ->
%%      error
%%  end.
handle_function(Function, Args) when is_atom(Function), is_tuple(Args) ->
  debug("handle_function ~n",[]),
  case apply(?MODULE, Function, tuple_to_list(Args)) of
    ok -> ok;
    Reply -> {reply, Reply}
  end.

book_client.erl

-module(book_client).

%% API
-export([]).

%% API
-export([ping/0]).

-spec ping() -> ok.

ping() ->
  io:format("call thrift server\n"),
%%  {ok, Port} = application:get_env(larzio, agent_port),
%%  {ok, Ip} = application:get_env(larzio, agent_server),
  Port = 9090,
  Ip = "localhost",
  {ok, Client0} = thrift_client_util:new(Ip, Port, book_sender_thrift, []),
  {Client1, {ok, ok}} = thrift_client:call(Client0, ping, []),
  {Client1, {ok, AddResult}} = thrift_client:call(Client0, add, [1, 2]),
  io:format("add result ~p ~n", [AddResult]),
  {_Client8, ok} = thrift_client:close(Client1),
  io:format("call thrift done~n"),
  ok.

Reference

thrift 教程

初探Thrift客戶端異步模式

[Android] apache thrift 入門與android上的實作

Apache Thrift 官方JAVA教程

erlang+thrift配合開發

erlang server.erl

2017年10月16日

thrift, protobuf, avro 比較

以往在跨不同程式語言的系統之間,會使用 SOAP(Web Service), XML 或是 JSON,但這幾個方案目前目前大都是用在網頁上。thrift, protobuf, avro 是三種跨語言通信方案,在不同程式語言之間,資料必須要以有效率的方式,序列化後,再進行傳輸,到另一端反序列化。另外 thrift 及 avro 還將傳輸機制內建在函式庫中,可在不同程式語言環境之間,進行 RPC 遠端呼叫。

thrift, protobuf, avro 簡述

Google Protocol Buffers 是一種序列化與結構化數據的一種機制,具有跨平台、解析速度快、序列化數據體積小、擴展性高、使用簡單的特點。

Apache thrift 是由 Facebook 主導開發的一個跨平台、支持多語言的,通過定義 IDL 文件,自動生成 RPC 客戶端與服務端通信代碼的工具,可產生在 C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, and OCaml 這些語言之間互相傳遞資料的使用程式碼。其中一方是 Server,另一端是 client。

Apache Avro 是一個二進制的數據序列化系統。實際上 Avro 除了序列化之外,也提供了 RPC 功能。 Avro 是屬於 Hadoop的一個子項目,由 Hadoop 的 創始人 Doug Cutting 開始開發,用於支援大量數據交換的應用,依賴 Schema 來實現資料結構定義,Schema 是以JSON 對象來表示, Avro 也被作為一種 RPC 框架來使用。客戶端希望同服務器端交互時,就需要交換雙方通信的協議,它類似於模式,需要雙方來定義,在 Avro中被稱為Message。

thrift avro protobuf 的優缺點比較

一般認定,protobuf 因為不包含 RPC 的部分,如果單純要對資料進行傳輸前的序列化或是接收後的反序列化,不管是序列化速度跟資料大小,都是最佳選擇。

如果要在不同的程式語言之間進行 RPC 呼叫,那麼 avro 的性能及效率,會是比 thrift 還好的選擇,但因為 avro 官方支援的程式語言比較少一點,如果以涵蓋的程式語言範圍來看,就要選用 thrift

thrift

優點

支持非常多的語言 (C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, and OCaml)
thrift文件生成目標代碼,簡單易用
消息定義文件支持註釋
數據結構與傳輸實作分離,支援多種消息格式
包含完整的客戶端/服務端stack,可快速實現RPC
支援 同步 和 異步 兩種通信方式

缺點

不支援動態特性

avro

優點

binary 消息,性能好/效率高
使用JSON描述模式(schema)
schema 和 資料 統一存儲,消息自描述,不需要生成stub代碼(支持生成IDL)
RPC調用在握手階段交換模式定義
包含完整的客戶端/服務端 stack,可快速實現RPC
支援 同步 和 異步 通信
支援 動態 消息
模式定義允許定義數據的排序(序列化時會遵循這個順序)
提供了基於Jetty內核的服務基於Netty的服務

缺點

只支援 Avro 自己的序列化格式
語言綁定(python, java, C, C++, C#) 不如Thrift豐富

protobuf

優點

binary 消息,性能好/效率高(空間和時間效率都很不錯)
proto 文件生成目標代碼,簡單易用
序列化反序列化直接對應程序中的數據類別,不需要解析後再進行映射(XML,JSON都是這種方式)
支援向前相容(新加字段採用默認值)和向後相容(忽略新加字段),簡化升級
支援多種語言(類似IDL文件)

缺點

官方只支援 C++, JAVA 和 Python 語言
binary 可讀性差
binary 不具有自描述特性
不具備動態特性(可以通過動態定義生成消息類型或者動態編譯支持)
只有序列化和反序列化技術,沒有RPC功能

References

跨語言通信方案比較——thrift、protobuf和avro

protobuf和thrift對比

三種通用應用層協議protobuf、thrift、avro對比,完爆xml,json,http

Thrift、protocolbuffer、avro這幾種序列化之間的比較

使用Apache Avro

2017年10月2日

macport 如何安裝舊版軟體

如果在 macport 直接以 sudo port upgrade outdated 更新軟體,都會直接更新到最新版本,但有時候為了軟體的相容性問題,還是需要安裝舊版的軟體,以下記錄安裝舊版軟體的過程。

以 erlang 為例,目前 macport 最新為 20.0 版,如要安裝 erlang 19.3 版,要用以下程序處理。

由於 macport 的權限問題,必須在 /tmp 執行下列的程序。

cd /tmp
mkdir port
cd port

git clone --single-branch https://github.com/macports/macports-ports.git
cd macports-ports

因為 erlang 19.3 版,在 erlang Portfile history 可找到該 Portfile History,在 19.3 版的地方,點擊 <>,可查看該 commit 的網址https://github.com/macports/macports-ports/blob/e80897a5cc8f3583eac1bff12a62db6dc8ce4f99/lang/erlang/Portfile

因此我們用 git 指令切換到該 commit

git checkout e80897a5cc8f3583eac1bff12a62db6dc8ce4f99

安裝 19.3 版 erlang

cd lang/erlang

sudo port install +hipe+ssl+wxwidgets

以指令查詢目前 active 的 erlang 版本

sudo port installed | grep erlang

結果為

  erlang @19.3_0+hipe+ssl+wxwidgets (active)
  erlang @20.0_0+hipe+ssl+wxwidgets

如果版本錯誤,可用以下指令切換至 19.3

sudo port activate erlang @19.3

更新 macport 軟體可用以下的 script,在 uninstall inactive 時,可排除 erlang,避免舊版 erlang 被移除。

port_upgrade.sh

#!/bin/bash
echo "upgrade port..."
echo "!port selftupdate"
sudo port selfupdate

echo "!port upgrade outdated"
sudo port upgrade outdated

echo "!port installed inactive"
# list inactive installed packages
sudo port installed inactive

echo "!port uninstall inactive"
# uninstall inactive installed packages
#sudo port uninstall inactive
sudo port uninstall inactive and not erlang

References

InstallingOlderPort