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