对Riak Core的探索 (3) - 依赖注入 IoC

[size=x-large]二、 基于Riak Core分布式应用开发[/size]

开发一个基于Riak Core的分布式数据处理系统,意味着它要处理的数据将在hash后映射到一个环上,也就是说这些数据分布在各个数据节点的partition上,数据的处理也在各个partition上进行。

显然每个数据处理系统有它自己的数据处理逻辑。如果我们实现了这个处理逻辑然后以某种方式注入到Riak Core中,那么相应的系统也就开发成了。

基于Riak Core的开发本质上也是这样的:Riak Core屏蔽了所有和分布式有关的部分,开发者不用关心这些,它提供了某种接口,用户通过这些接口实现数据处理逻辑,然后将实现注入到Riak Core系统中。


[size=large]1. riak_core_vnode behaviour —— 用户处理逻辑的实现接口[/size]

Riak Core为提供了一个riak_core_vnode回调模块作为用户实现业务逻辑的接口。源代码看[url=https://github.com/basho/riak_core/blob/master/src/riak_core_vnode.erl]这里[/url]。riak_core_vnode有两个作用:
[list=1]
[*]作为erlang behaviour提供回调函数实现接口,用户在回调函数中实现业务逻辑;
[*]作为容器进程运行实现上述接口的用户模块,具体来讲它将作为一个基于gen_fsm的有限状态机进程运行。
[/list]
也就是说,riak_core_vnode是作为一个容器以erlang进程的形式运行,而用户的业务逻辑栖身于容器中。(BTW:gen_server/gen_fsm都是这种设计和运行模式。)

我想这是一种典型的IoC(依赖注入)设计:系统将用户实现的功能注入到Riak Core系统中,由此构建出基于Riak Core的分布式应用系统。困难的dynamo分布式实现交给Riak Core完成,用户只关心业务逻辑的实现就行了。

[size=large]2. 业务逻辑的实现,和其他[/size]

主要是实现riak_core_vnode behaviour的接口回调函数。除了业务逻辑的实现,还设计到数据的转移等。

Riak Core将partition上的数据处理操作抽象出来,由用户通过riak_core_vnode behaviour的回调函数实现,vnode的回调函数分为以下几类:
0) vnode进程生命周期管理,在vnode进程诞生和毁灭时将被调用的回调函数init/1, terminate/2
1) 对vnode进程负责的partition上数据的处理,当传来对该partition上的数据继续处理的命令时,由回调函数handle_command/3负责处理。例如,如果对数据的处理是存储,那么无非是get/put之类的命令,这些命令的格式(协议)都是由用户自己定义并实现的。
2) 当集群中增/减物理节点时,或者某个宕掉的物理节点恢复时,就需要对partition进行handoff的处理,其核心是对数据的搬运,它包括一组回调函数(is_empty/1, delete/1, handle_handoff_command/3, handoff_starting/2, handoff_cancelled/1, handoff_finished/2, handle_handoff_data/2, handle_handoff_data/2, encode_handoff_item/2 )
3) 对进程错误的处理:与该vnode相关的进程如果出错会调用回调函数handle_exit
4) Covering:覆盖整个ring空间数据的特殊命令,例如对一段连续范围的搜索命令。某个连续的数据集在hash后有可能会落到所有的partition。

这些回调函数在[url=https://github.com/rzezeski/try-try-try/tree/master/2011/riak-core-the-vnode]rzezeski的博文[/url]中有介绍

[size=large]3. 业务逻辑的注入过程[/size]

我们的分布式数据处理系统作为一个OTP application发布。在application中实现了处理系统的启动,以及业务逻辑的注入。

application启动时会启动supervisor,后者监控着[color=red]riak_core_vnode_master[/color]子进程(riak_core_vnode_master是一个gen_server),参数配制成实现用户逻辑的定制vnode模块,从而实现业务逻辑注入。

riak_core_vnode_master可看成是运行着vnode的容器。

业务逻辑的注入

-module(rts_sup).

-behaviour(supervisor).

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

%% Supervisor callbacks
-export([init/1]).

start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).


init(_Args) ->
VMaster = { rts_vnode_master,
{riak_core_vnode_master, start_link, [rts_vnode]},
permanent, 5000, worker, [riak_core_vnode_master]},

Entry = { rts_entry_vnode_master,
{riak_core_vnode_master, start_link, [rts_entry_vnode]},
permanent, 5000, worker, [riak_core_vnode_master]},

Stat = { rts_stat_vnode_master,
{riak_core_vnode_master, start_link, [rts_stat_vnode]},
permanent, 5000, worker, [riak_core_vnode_master]},

{ok,
{{one_for_one, 5, 10},
[VMaster, Entry, Stat]}}.


有几种执行特定分区上业务逻辑的API方法,都是riak_core_vnode_master模块提供的,这些函数的参数都是三个,分别是:IdxNode, Msg, VMasterMod

1. 同步发送
sync_command(IndexNode, Msg, VMaster)

2. 异步发送
riak_core_vnode_master:sync_spawn_command(IndexNode, ping, rts_vnode_master)

还有一个比较特殊command函数,它有两种

一种一上述两类相似,直接向vnode同步发送命令:
riak_core_vnode_master:command(IdxNode,
{entry, Client, Entry} = Msg,
rts_entry_vnode_master = VMaster).

一种向PreList里的所有节点发送command命令:
command(Preflist, Msg, VMaster)


IdxNode是{Index, Node}这样的tuple,其中Index是partition分区的id号,Node是分区所在的节点,知道了Node和rts_entry_vnode_master,就能得到vnode进程Pid,通过发送事件调用相关业务逻辑:
gen_fsm:send_event(Pid, make_request(Msg, Sender, Index));


IndexNode的计算:
1) 用riak_core_util:chash_key/1函数计算hash值,参数是一个含两个二进制数据的tuple
DocIdx = riak_core_util:chash_key({<<"ping">>, term_to_binary(now())}),
2) 通过哈希值得到Preflist,Preflist的长度可以指定:
PrefList = riak_core_apl:get_primary_apl(DocIdx, 1, rts),

最后得到IndexNod
[{IndexNode, _Type}] = PrefList,


BTW:应用的启动过程
-module(rts_app).
-behaviour(application).

%% Application callbacks
-export([start/2, stop/1]).

start(_StartType, _StartArgs) ->
case rts_sup:start_link() of
{ok, Pid} ->
ok = riak_core:register_vnode_module(rts_vnode),
ok = riak_core_node_watcher:service_up(rts, self()),

ok = riak_core:register_vnode_module(rts_entry_vnode),
ok = riak_core_node_watcher:service_up(rts_entry, self()),

ok = riak_core:register_vnode_module(rts_stat_vnode),
ok = riak_core_node_watcher:service_up(rts_stat, self()),

EntryRoute = {["rts", "entry", client], rts_wm_entry, []},
webmachine_router:add_route(EntryRoute),

{ok, Pid};
{error, Reason} ->
{error, Reason}
end.

stop(_State) ->
ok.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值