2017/10/30

protobuf

Protocol Buffers - Google's data interchange format Protocol Buffers 是一種輕便高效的結構化數據存儲格式,可以用於資料序列化,適合做儲存資料或 RPC 資料交換的格式,可用在通訊協定、數據儲存等領域,是一種與程式語言無關、平台無關、可擴展的序列化結構資料格式。

安裝 protobuf compiler

在 mac 可透過 macport 安裝 protobuf 的 compiler for java, cpp

sudo port install protobuf-java

安裝時會同時安裝

protobuf-cpp

如果要用 protobuf 3,則要安裝 protobuf3-java,如果已經裝了 protobuf-java,必須要先移除後才能安裝 protobuf3

sudo port uninstall protobuf-java
sudo port uninstall protobuf-cpp

sudo port install protobuf3-java

安裝時會同時安裝

maven3
maven_select
protobuf3-cpp

使用 protoc 的 command line 指令

protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR --python_out=DST_DIR --go_out=DST_DIR --ruby_out=DST_DIR --javanano_out=DST_DIR --objc_out=DST_DIR --csharp_out=DST_DIR path/to/file.proto

ex:

protoc -I=. --java_out=. ./file.proto

.proto 資料格式文件

安裝 protobuf compiler 後,需要編寫一個 .proto 的資料格式文件,用來描述要傳遞的資料內容。

目前官方有提供 proto3 的規格,但還沒有把 tutorial 的部分由 proto2 更新到 proto3。

以下為 proto3 language guide 的內容:

Defining A Message Type 定義 message

syntax = "proto3";

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
}

如果是 proto2 就不需要寫 syntax = "proto3";

Specifying Field Types 指定資料型別

資料型別可以是 scalar type 或是 enum 或是其他 message

  • Assigning Tags

    等號後面的的數字 field number 代表 message 中的標記欄位,不能改變。最小的field number 設為 1, 編碼後的tag佔1 byte。最大的field number 設為 2^29 - 1, 編碼後的tag佔5 bytes。19000~19999 是 protobuf 保留的 field number,不能在 .proto 中使用

    field number 需要的 bytes 數量
    1~15 1
    16~2047 2
    2^11~2^18-1 3
    2^18~2^25-1 4
    2^25~2^32-1 5
  • Specifying Field Rules

    singular : 0 or 1 個值,不能超過1個

    repeated : 0 or 1 or more,序列化和反序列化過程中,會保留 repeated values 的順序。在proto3中,repeated fields of scalar numeric types默認使用packed方式。

  • Adding More Message Types

    在一個.proto文件中可以定義多個messages。

    message SearchRequest {
      string query = 1;
      int32 page_number = 2;
      int32 result_per_page = 3;
    }
    
    message SearchResponse {
     ...
    }
  • Add Comments

    使用 // 註釋

    message SearchRequest {
      string query = 1;
      int32 page_number = 2;  // Which page number do we want?
      int32 result_per_page = 3;  // Number of results to return per page.
    }
  • Reserved Fields

    如果更新一個message時,需要完全移除某個欄位,但未來可能又重新使用這個欄位標籤,這可能會在導入舊版本的.proto文件時產生嚴重的bug。因此,需要將deleted fields標記為reserved。reserved field number 和 field name 應該分開寫。

    message Foo {
      reserved 2, 15, 9 to 11;
      reserved "foo", "bar";
    }
  • What's Generated From Your .proto?

    使用protobuf的編譯器protoc編譯.proto文件時,編譯器會產生與message類型相關的所選語言的代碼。包括但不限於:

    1. getting and setting field values
    2. serializing your messages to an output stream
    3. parsing your messages from an input stream

    以 C++ 及 Java 為例: C++ :產生1個 .h 文件和1個 .cc 文件 Java:產生1個 .java 文件,其中每個message class都包括Builder

  • Scalar Value Types

.proto Type Java Type C++ Type 備註
double double double
float float float
int32 int int32 使用variable-length encoding。如果你的資料可能含有負數,那麼請使用sint32。
int64 long int64 使用variable-length encoding。如果你的字段可能含有負數,那麼請使用sint64。
uint32 int[1] uint32 使用variable-length encoding。
uint64 long[1] uint64 使用variable-length encoding。
sint32 int int32 使用variable-length encoding。對於有負數的整數,比平常使用的int32效率還好。
sint64 long int64 使用variable-length encoding。
fixed32 int[1] uint32 總是4個 bytes。如果數值總是比 228 大的話,這個資料型別會比uint32效率還好。
fixed64 long[1] uint64 總是8個 bytes。如果數值總是比 256 大的話,這個資料型別會比uint64效率還好。
sfixed32 int int32 4個 bytes
sfixed64 long int64 8個 bytes
bool boolean bool
string String string 必須是UTF-8編碼或者7-bit ASCII編碼
bytes ByteString string 可能包含任意順序的資料
  • Default Values

    當parse一個message時,如果編碼的message不包括某個singular field,解碼的message的相應的field則被設為默認值。

    1. strings : empty string
    2. bytes : empty bytes
    3. bools : false
    4. numeric types : 0
    5. enums : first defined enum value, which must be 0
    6. message fields : 依賴於不同的語言實現。
    7. repeated fields : empty list

    一旦一個message被解析,我們並不知道某個field是明確地被設為默認值,還是未設定從而自動被設為默認值。因此,我們定義message時,並不能依賴默認值的行為。 例如,如果你有一個bool field isMine, true表示這個方塊(是Mine),false表示這個方塊(不是Mine)。當你收到一個message isMine = false,那這個方塊到底(不是雷)還是(未設定)呢? 因此,不要依賴任何默認值的行為。更應該注意的是,如果一個field的value是默認值,將不會被序列化。

  • Enumerations

    1. 第一個 enum value 必須為0。
    2. 必須有1個0值作為枚舉的默認值。proto2使用第一個枚舉值作為默認值,為了和proto2兼容,必須將一個枚舉值設為0。
    3. 可以定義aliases來使得不同的枚舉量映射到同一枚舉值。
    message SearchRequest {
      string query = 1;
      int32 page_number = 2;
      int32 result_per_page = 3;
      enum Corpus {
        UNIVERSAL = 0;
        WEB = 1;
        IMAGES = 2;
        LOCAL = 3;
        NEWS = 4;
        PRODUCTS = 5;
        VIDEO = 6;
      }
      Corpus corpus = 4;
    }
  • Using Other Message Types

    可在一個 message 中使用另一個 message

    message SearchResponse {
      repeated Result results = 1;
    }
    
    message Result {
      string url = 1;
      string title = 2;
      repeated string snippets = 3;
    }

    Nested Types

    message Outer {       // Level 0
      message MiddleAA {  // Level 1
        message Inner {   // Level 2
          int64 ival = 1;
          bool  booly = 2;
        }
      }
      message MiddleBB {  // Level 1
        message Inner {   // Level 2
          int32 ival = 1;
          bool  booly = 2;
        }
      }
    }
  • Importing Definitions

    使用另一個.proto文件中的定義,需要import

    1. import : 只能直接使用,不可傳遞
    2. import public : 可以傳遞
  • Using proto2 Message Types

    proto2的enum不能用在proto3中,其他的都理論上是相容的。

  • Updateing Message Type

    更新 Message Type 定義時,要注意以下事項

    1. 不要修改任一個 field 的 numeric 標籤
    2. 增加新的 field 時,舊訊息可以用新的 code parsing,同樣的,parsing 舊訊息的程式碼還是能用來 parsing 新訊息。
    3. 可以移除 fields,只要沒有重用 rag number,但也可以修改欄位名稱作為標記 ex: 加上 prefix: "OBSOLETE_",或是將該欄位標記為 reserved。
    4. int32, uint32, int64, uint64, and bool are all compatible,也就是說可以隨時改變為這幾種資料型別中的任一種。
    5. sint32 and sint64 可以互換,但跟其他整數型別不相容。
    6. string and bytes 如果使用 UTF-8 就是相容的。
    7. 內嵌的訊息是跟 bytes 相容的。
    8. fixed32 跟 sfixed32 相容。fixed64 跟 sfixed64 相容
    9. enum 跟 int32, uint32, int64, and uint64 相容。
  • Unknown Fields proto3 可處理未定義的欄位資料,但不同語言的實作有可能會刪除這些未知的欄位,這跟 proto2 的做法不同。

  • Any

    可使用 Any 嵌入沒有 .proto 定義的 message,Any 就是以 bytes 的方式序列化,使用 Any 必須要 import google/protobuf/any.proto

    import "google/protobuf/any.proto";
    
    message ErrorStatus {
      string message = 1;
      repeated google.protobuf.Any details = 2;
    }

    Any 用來取代 proto2 的 extensions

  • Oneof 訊息只會包含多個欄位其中的某一個欄位,Oneof 不能設定為 repeated

    message SampleMessage {
      oneof test_oneof {
        string name = 4;
        SubMessage sub_message = 9;
      }
    }
  • Map

    map<string, Project> projects = 3;

    Map 不能為 repeated,maps are sorted by key

  • Packages

    package foo.bar;
    message Open { ... }
  • Defining Services

    可定義 RPC service interface,compiler 會自動產生 code and stubs

    service SearchService {
      rpc Search (SearchRequest) returns (SearchResponse);
    }

    protobuf 最直覺的 RPC 系統為 gRPC: a language- and platform-neutral open source RPC system developed at Google

proto3 與 proto2 的差異

proto3 比 proto2 支持更多語言,語法更簡單,去掉了一些複雜的語法和特性。

  1. 在第一行非空白非註釋行,必須寫:

    syntax = "proto3";
  2. 移除了 "required"

  3. "repeated" 默認採用 packed 編碼;

    在 proto2 中,需要明確使用 [packed=true] 來指定 packed 編碼方式

  4. 語言增加 Go、Ruby、JavaLite

  5. 移除了 default

    在 proto2 中,可以使用 default 選項為某一字段指定默認值。在 proto3 中,字段的默認值只能根據資料類型由系統決定。在資料被設置為默認值的時候,該字段不會被序列化。這樣可以節省空間,提高效率。

    但這樣就無法區分某資料是根本沒有數值,還是被設定為 default value。這在 proto3 中問題不大,但在 proto2 中會有問題。更新協議的時候使用 default 選項為某個字段指定了一個與原來不同的默認值,舊的程式取到的該值會與新程式不一樣。

  6. enum 類型的第一個數值必須為 0

  7. 移除了對分組的支援

    分組的功能完全可以用消息嵌套的方式來實現,並且更清晰。在 proto2 中已經把分組語法標註為"expired"了。

  8. 舊程式在解析新增字段時,會把不認識的變數丟棄,再序列化後新增的變數就不見了

    在 proto2 中,舊程式雖然會忽視不認識的新增變數,但並不會將其丟棄,再序列化的時候那些字段會被原樣保留。

    但官方終於同意在 proto3 中恢復 proto2 的處理方式了。

  9. 移除了對擴展的支持,新增了 Any 類型

    Any 類型是用來替代 proto2 中的擴展的。目前還在開發中。

    proto2 中的擴展特性很像 Swift 語言中的擴展。理解起來有點困難,使用起來更是會帶來不少混亂。

    相比之下,proto3 中新增的 Any 類型有點像 C/C++ 中的 void* 。

  10. 增加了 JSON 映射特性

ref: Protobuf 的 proto3 與 proto2 的區別

References

比起 JSON 更方便、更快速、更簡短的 Protobuf 格式

Protocol_Buffers wiki

Google Protocol Buffer 的使用和原理

Protobuf 語法指南

protoc java

ScalaPB: protobuf for scala


erlang gpb

erlang_protobuffs

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/02

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