erlang抽象码与basho的protobuf(一)使用

basho的riak是应用erlang编写的类dynamo kv存储,其实现贯彻了松耦合、模块化的特征,各类核心组件均可替换:

分布式组件riak_core可以用于构建其它的分布式应用,https://github.com/rzezeski/try-try-try是一个利用riak_core构建分布式应用的教学实例;

存储组件riak_kv和riak_serach是基于riak_core实现的分布式kv存储;

webmachine是基于mochiweb封装的通用REST框架;

protobuffs是erlang版的protocol buffers,最初位于https://github.com/ngerakines/erlang_protobuffs

bitcask是riak的默认存储引擎,通过nif实现了文件读写方法,之前本博客有提到过;

eleveldb通过nif实现了对leveldb的封装;

还有很多使用的组件,项目中能用的尽量拿来用吧。

此次将分析protobuffs这个组件,它本身完成的功能倒是容易描述清楚:“google的protocol buffers提供了跨语言的数据交换方法,支持c++/java/python,但是不支持erlang,basho帮我们实现了这个愿望,根据我们的proto文件描述的数据结构,产生对应的编解码erlang头文件和源文件”,但是看到它的实现后,我突然发现,erlang世界还有很多未知的地方需要探索。

言归正传,protocol buffers的资料请各位读者自行google,这里直接开始分析protobuffs的实现,版本为0.6.0。

先定义一个proto文件rds_la.proto如下:

 

package rds_la;

message la_record {

  required string name = 1;

  required int32 timestamp = 2;

  required string query = 3;

  required int32 query_time = 4;

  required int32 response_time = 5;

}

这是前日所开发的项目中用于传输一条日志记录的结构,其中name和query都是字符串,name最多64字节,而query最多1024字节。

利用protobuf附带的工具protoc-erl编译rds_la.proto得到rds_la_pb.hrl和rds_la_pb.erl。

rds_la_pb.hrl的内容主要为:

 

-record(la_record, {

    name = erlang:error({required, name}),

    timestamp = erlang:error({required, timestamp}),

    pb_query = erlang:error({required, pb_query}),

    query_time = erlang:error({required, query_time}),

    response_time = erlang:error({required, response_time})

}).

为rds_la生成了一个record,名为la_record,由于message la_record的所有域均为required,可以看到,protobuffs贴心的为record la_record每个域设置了默认值erlang:error({required, name}),而如果为optional,则不设置默认值,如果为repeated,也不会设置默认值,但是如果有值,该域将会是一个列表list。

rds_la_pb.erl的主要内容为:

 

-file("src/rds_la_pb.erl", 1).

 

-module(rds_la_pb).

 

-export([encode_la_record/1, decode_la_record/1]).

 

-export([encode/1, decode/2, iolist/2]).

 

-record(la_record,

        {name, timestamp, pb_query, query_time, response_time}).

 

 

encode_la_record(Record)

    when is_record(Record, la_record) ->

    encode(la_record, Record).

 

encode(la_record, Record) ->

    iolist_to_binary(iolist(la_record, Record)).

 

iolist(la_record, Record) ->

    [pack(1, required,

          with_default(Record#la_record.name, none), string, []),

     pack(2, required,

          with_default(Record#la_record.timestamp, none), int32,

          []),

     pack(3, required,

          with_default(Record#la_record.pb_query, none), string,

          []),

     pack(4, required,

          with_default(Record#la_record.query_time, none), int32,

          []),

     pack(5, required,

          with_default(Record#la_record.response_time, none),

          int32, [])].

...

enum_to_int(pikachu, value) -> 1.

 

int_to_enum(_, Val) -> Val.

 

decode_la_record(Bytes) when is_binary(Bytes) ->

    decode(la_record, Bytes).

 

decode(enummsg_values, 1) -> value1;

decode(la_record, Bytes) when is_binary(Bytes) ->

    Types = [{5, response_time, int32, []},

             {4, query_time, int32, []}, {3, pb_query, string, []},

             {2, timestamp, int32, []}, {1, name, string, []}],

    Decoded = decode(Bytes, Types, []), 

    to_record(la_record, Decoded). 

...

to_record(la_record, DecodedTuples) ->

    lists:foldl(fun ({_FNum, Name, Val}, Record) ->

                        set_record_field(record_info(fields, la_record), Record,

                                         Name, Val)

                end,

                #la_record{}, DecodedTuples).

...

以上便是生成的文件的内容,标注红色的地方需要读者注意,它们便是protobuffs_compile所生成的内容。

其中encode_la_record/1和decode_la_record/1是la_record的编解码函数;io_list/2则记录了编码时需要对record la_record的每个field需要做的动作;enum_to_int/2和int_to_enum/2用于enum值生成;decode/2记录了解码时生成的域应该放置在record la_record的哪个域;to_record/2记录了构造record la_record的过程。

不过这里有个很萌的pikachu是干嘛的?

未完待续...

 

你可能感兴趣的:(protobuf)