2014年9月10日

erlang Mnesia replication測試

本篇簡介紹如何使用Mnesia的RAM replicates功能。使用erlang啟動數個node並將之相連接,這些node可以在單台機器上執行,也可以是在不同機器上執行。假設對某一個node裡的mnesia插入資料時,其它的node裡的mnesia也會一併插入此資料。這樣一來如果某一個node毀掉後其它node還能保留著完整資料以達fault-tolerant。

本篇示範在同一台機器上執行2個node,步驟如下

1.建立目錄給mnesia儲存檔案。在disk裡先建立2個資料夾分別給node1, node2使用。例如 /Users/james/Develop/erlang-test-node/node1 /Users/james/Develop/erlang-test-node/node2

2.啟動node1(注意啟動erlang時的路徑)

cd /Users/james/Develop/erlang-test-node/node1
erl -sname node1

Erlang R16B02 (erts-5.10.3) [source] [64-bit] [smp:4:4] [async-threads:10] [kernel-poll:false]

Eshell V5.10.3  (abort with ^G)
(node1@jamestekiMacBook-Pro)1>

3.啟動node2(注意啟動erlang時的路徑)

cd /Users/james/Develop/erlang-test-node/node2
erl -sname node2

Erlang R16B02 (erts-5.10.3) [source] [64-bit] [smp:4:4] [async-threads:10] [kernel-poll:false]

Eshell V5.10.3  (abort with ^G)
(node2@jamestekiMacBook-Pro)1>

4.將node1, node2相連

(node1@jamestekiMacBook-Pro)2> net_adm:ping('node2@jamestekiMacBook-Pro'). (1)
pong
(node1@jamestekiMacBook-Pro)3> nodes(). (2)
['node2@jamestekiMacBook-Pro']

(1)node1執行net_adm:ping/1指令來ping node2。收到回傳pong就代表連上了

(2)列出與自已相連的node,可以從回傳值看到已連接'node2@jamestekiMacBook-Pro'

到node2查看是否已與node1相連

(node2@jamestekiMacBook-Pro)1> nodes().
['node1@jamestekiMacBook-Pro']

5.準備測試用程式碼。將test_mnesia.erl分別copy至node1, node2所執行的當前目錄,即/Users/james/Develop/erlang-test-node/node1、/Users/james/Develop/erlang-test-node/node2

程式碼的重點會在於create_schema跟create_table指令的參數要指定nodes

例如 do_this_once()裡的 mnesia:create_schema([node(),'node2@jamestekiMacBook-Pro']), 其中node()指的是當前的node,'node2@jamestekiMacBook-Pro'指的是node2

本例子的do_this_once()只要在node1執行一次即可,node2不需執行。

test_mnesia.erl

-module(test_mnesia).
-import(lists, [foreach/2]).
-compile(export_all).

-include_lib("stdlib/include/qlc.hrl").

-record(shop, {item, quantity, cost}).
-record(cost, {name, price}).
-record(design, {id, plan}).

do_this_once() ->
    mnesia:create_schema([node(),'node2@jamestekiMacBook-Pro']),
    mnesia:start(),
    mnesia:create_table(shop,   [{ram_copies, [node(), 'node2@jamestekiMacBook-Pro']},{attributes, record_info(fields, shop)}]),
    mnesia:create_table(cost,   [{ram_copies, [node(), 'node2@jamestekiMacBook-Pro']},{attributes, record_info(fields, cost)}]),
    mnesia:create_table(design, [{ram_copies, [node(), 'node2@jamestekiMacBook-Pro']},{attributes, record_info(fields, design)}]),
    mnesia:stop().

start() ->
    mnesia:start(),
    mnesia:wait_for_tables([shop,cost,design], 20000).

reset_tables() ->
    mnesia:clear_table(shop),
    mnesia:clear_table(cost),
    F = fun() ->
        foreach(fun mnesia:write/1, example_tables())
    end,
    mnesia:transaction(F).

example_tables() ->
    [%% The shop table
     {shop, apple,   20,   2.3},
     {shop, orange,  100,  3.8},
     {shop, pear,    200,  3.6},
     {shop, banana,  420,  4.5},
     {shop, potato,  2456, 1.2},
     %% The cost table
     {cost, apple,   1.5},
     {cost, orange,  2.4},
     {cost, pear,    2.2},
     {cost, banana,  1.5},
     {cost, potato,  0.6}
    ].

demo(select_shop) ->
    do(qlc:q([X || X <- mnesia:table(shop)]));

add_shop_item(Name, Quantity, Cost) ->
    Row = #shop{item=Name, quantity=Quantity, cost=Cost},
    F = fun() ->
        mnesia:write(Row)
    end,
    mnesia:transaction(F).

do(Q) ->
    F = fun() -> qlc:e(Q) end,
    {atomic, Val} = mnesia:transaction(F),
    Val.

6.編譯並執行

在node1執行資料庫初始化與replicates

(node1@jamestekiMacBook-Pro)3> c('test_mnesia').  (1)
{ok,test_mnesia}
(node1@jamestekiMacBook-Pro)4> test_mnesia:do_this_once(). (2)
stopped
(node1@jamestekiMacBook-Pro)5>
=INFO REPORT==== 10-Sep-2014::15:37:39 ===
    application: mnesia
    exited: stopped
    type: temporary

(node1@jamestekiMacBook-Pro)5>test_mnesia:start(). (3)
ok
(node1@jamestekiMacBook-Pro)6> test_mnesia:reset_tables(). (4)
{atomic,ok}
(node1@jamestekiMacBook-Pro)7> test_mnesia:demo(select_shop). (5)
[{shop,potato,2456,1.2},
 {shop,apple,20,2.3},
 {shop,orange,100,3.8},
 {shop,pear,200,3.6},
 {shop,banana,420,4.5}]

(1)編譯test_mnesia

(2)此function會先初始化db,接著建立3個table,當執行完後可以發現2個node裡的當前執行路徑下已自動建立下列資料夾Mnesia.node1@jamestekiMacBook-Pro or Mnesia.node2@jamestekiMacBook-Pro

(3)start mnesia(因為do_this_once()裡最後有再將mnesia做stop)

(4)清除table資料,並插入一些測試資料

(5)將shop table的資料印出,可以看出shop table已有一些資料

7.至node2查詢shop table是否也有跟node1相同的資料

(node2@jamestekiMacBook-Pro)2> c('test_mnesia'). (1)
{ok,test_mnesia}
(node2@jamestekiMacBook-Pro)3> test_mnesia:start(). (2)
ok
(node2@jamestekiMacBook-Pro)4> test_mnesia:demo(select_shop). (3)
[{shop,potato,2456,1.2},
 {shop,apple,20,2.3},
 {shop,orange,100,3.8},
 {shop,pear,200,3.6},
 {shop,banana,420,4.5}]

(1)在node2編譯test_mnesia

(2)start mnesia(註:不需要再初始化db了,因為在node1已經執行了會將table、資料同步過來)

(3)查詢table資料, 由結果可知資料已經有replicates

8.將node1關掉(直接把console關掉),並在node2插入資料

(node2@jamestekiMacBook-Pro)5> test_mnesia:add_shop_item(iphone6, 10, 20). (1)
{atomic,ok}
(node2@jamestekiMacBook-Pro)6> test_mnesia:demo(select_shop). (2)
[{shop,iphone6,10,20},    
 {shop,potato,2456,1.2},
 {shop,apple,20,2.3},
 {shop,orange,100,3.8},
 {shop,pear,200,3.6},
 {shop,banana,420,4.5}]

(1)執行插入一筆記錄 ex:iphone6

(2)查詢看是否有插入成功

9.將node1重新開啟並做連結,最後查看mnesia是否有做replicates

jamestekiMacBook-Pro:~ james$ cd /Users/james/Develop/erlang-test-node/node1
jamestekiMacBook-Pro:node1 james$ erl -sname node1Erlang R16B02 (erts-5.10.3) [source] [64-bit] [smp:4:4] [async-threads:10] [kernel-poll:false]

Eshell V5.10.3  (abort with ^G)
(node1@jamestekiMacBook-Pro)1> net_adm:ping('node2@jamestekiMacBook-Pro'). (1)
pong
(node1@jamestekiMacBook-Pro)2> nodes(). (2)
['node2@jamestekiMacBook-Pro']
(node1@jamestekiMacBook-Pro)3> test_mnesia:start(). (3)
ok
(node1@jamestekiMacBook-Pro)4> test_mnesia:demo(select_shop). (4)
[{shop,iphone6,10,20},
 {shop,potato,2456,1.2},
 {shop,apple,20,2.3},
 {shop,orange,100,3.8},
 {shop,pear,200,3.6},
 {shop,banana,420,4.5}]

(1) node1啟動後跟node2做連結

(2) 確認是否已經連上node2

(3) start mnesia

(4) 確認資料是否已經replicates

小結

本篇示範在同一台機器上啟動2個node來做replicates,如果要在不同機器上要做replicates的話需要再設定cookie才能work,有興趣的人請再參考相關資料。

參考資料

Programming Erlang, Second Edition.