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是干嘛的?
未完待续...