This feed contains pages about Erlang.
erlang basic distributed application
Posted Mon 09 Jun 2008 06:15:31 PM EDTErlang
with OTP is a fairly powerful framework for creating
distributed redundant applications. The basic
gen_server behavior can easily extended to create a
redundant server with built in failover. With Mnesia you
also get a replicated Database.
I've been trying to figure out how exactly this is supposed to work, so I've been working on a quick application to demonstrate this. It's nothing fancy, just a simple set(k,v) and get(k) API.
The files are available as a tarball from here ddict.tgz
-module(ddict). -behaviour(gen_server). -export([start/0,stop/0,terminate/2]). -export([init/1, handle_call/3, handle_cast/2,handle_info/2]). -export([create_schema/0]). -export([get/1,set/2]). -define(GD,{global, ddict}). -include_lib("stdlib/include/qlc.hrl"). -record(rec, {key, value}). init_mnesia() -> mnesia:start(), ok = mnesia:wait_for_tables([rec], 2000). init(_Arg) -> process_flag(trap_exit, true), io:format("dict server starting~n"), init_mnesia(), {ok, []}. start() -> gen_server:start_link(?GD, ddict, [], []). stop() -> gen_server:cast(?GD, stop). terminate(Reason, State) -> io:format("dict server terminating~n"). %"model" methods do_get(Key) -> Res = mnesia:dirty_read({rec, Key}), case Res of [] -> undefined; [Rec] -> Rec#rec.value end. do_set(Key, Value) -> F = fun() -> Row = #rec{key=Key, value=Value}, mnesia:write(Row) end, {atomic, ok} = mnesia:transaction(F), ok. %"controller" methods handle_call({get, Key}, From, State) -> Rec = do_get(Key), {reply, Rec, State}; handle_call({set, Key, Value}, From, State) -> Rec = do_set(Key, Value), {reply, Rec, State}. handle_cast(stop, State) -> io:format("ddict server stopping~n"), {stop, normal, State}. handle_info(Info, State) -> {noreply, State}. %"client api" methods get(Key) -> gen_server:call(?GD, {get, Key}). set(Key,Value) -> gen_server:call(?GD, {set, Key, Value}). create_schema() -> mnesia:create_schema([node()|nodes()]), mnesia:start(), %this is defnitely wrong lists:foreach(fun(N) -> io:format("starting mnesia on ~w~n", [N]), rpc:call(N, mnesia, start, []) end, nodes()), mnesia:create_table(rec, [ {disc_copies, [node()|nodes()]}, {attributes, record_info(fields, rec)} ]).download file "main gen_server file"
The key to this server being distributed is the use of {global, ddict} as the server name, instead of {local, ddict}. This enables other nodes in the cluster to see this server.
do_get() and do_set() are the "model"
like methods that deal with mnesia. handle_call
defines the gen_server api. get() and set() are helper
functions that call the remote gen server. If there was more to
this module, it would be a good idea to put these methods in
separate modules.
The one thing I am not sure about is the
create_schema() method. I'm sure there is a propper
way to initalize mnesia on a cluster, I just have no idea what it
is yet 
To make this into a propper gen server the supervisor and application needs to be defined with the following three files:
-module(ddict_sup). -behaviour(supervisor). -export([start_link/0]). -export([init/1]). start_link() -> supervisor:start_link(ddict_sup, []). init(_Args) -> {ok, {{one_for_one, 10, 60}, [{ddict, {ddict, start, []}, permanent, brutal_kill, worker, [ddict]}]}}.download file "/ramblings/files/erlang/ddict/ddict_sup.erl"
-module(ddict_app). -behaviour(application). -export([start/2, stop/1,go/0]). start(_Type, _Args) -> ddict_sup:start_link(). stop(_State) -> io:format("ddict server terminating~n"), ok. go() -> application:start(ddict).download file "/ramblings/files/erlang/ddict/ddict_app.erl"
{application, ddict,
[
{mod, {ddict_app,[]}}
]}.
download
file "/ramblings/files/erlang/ddict/ddict.app"To get erlang to start this application on boot, a config file for each node needs to be written:
[{kernel,
[{distributed, [{ddict, 3000, [one@media, {two@media}]}]},
{sync_nodes_optional, [two@media]},
{sync_nodes_timeout, 5000}
]
}
].
download
file "/ramblings/files/erlang/ddict/one.config"
[{kernel,
[{distributed, [{ddict, 3000, [one@media, {two@media}]}]},
{sync_nodes_optional, [one@media]},
{sync_nodes_timeout, 5000}
]
}
].
download
file "/ramblings/files/erlang/ddict/two.config"To create the initial database I ran the
ddict:create_schema method, which I'm sure is
completely incorrect, but it works:
erl -sname one -config one.config
erl -sname two -config two.config
(one@media)1> ddict:create_schema().
starting mnesia on two@media
{atomic,ok}
(one@media)2> mnesia:info().
...
running db nodes = [two@media,one@media]
disc_copies = [rec,schema]
[{one@media,disc_copies},{two@media,disc_copies}] = [schema,rec]
...
ok
download
file "/ramblings/files/erlang/ddict/create_db.txt"Once that is done, the application can be started with
erl -pa . -sname one -config one.config -s ddict_app go erl -pa . -sname two -config two.config -s mnesia start -s ddict_app go
I have to start mnesia separately on the second VM because I haven't yet figured out how mnesia should be started when dealing with distributed applications. mnesia needs to be running on both nodes, but not the ddict application itself.
Once it is running, you can call ddict:set("Foo","bar") and ddict:get("Foo"). You can also kill either VM, and it will restart the server after 3 seconds on the other node.