讓咱們聊聊Mnesia(一)

Mnesia是什麼

Mensia是Erlang的OTP庫中一個帶有強事務的分佈式KV存儲引擎。能夠很是方便且高效的存儲Erlang的任何數據類型。而且該系統支持持久化和內存表混合使用,不強制要求全部節點的性質相同。
node


Mnesia的分佈式事務是怎麼實現的

Mnesia的事務模型是2PC的模型,基本上能夠分爲如下幾個步驟git

  1.  讓全部參與的Node準備進行提交github

  2. 若是有一個Node回答No,讓全部回答yes的Node回滾緩存

  3. 若是全部Node都回答yes,讓全部Node提交服務器

mnesia_tm.erl中的t_commit完成了進行事務提交的準備工做,在這些準備工做中,arrange函數將緩存在ets中的數據操做轉化成prepare記錄和commit記錄。網絡

t_commit(Type) ->
    {_Mod, Tid, Ts} = get(mnesia_activity_state),
    %先把ETS表拿出來
    Store = Ts#tidstore.store,
    if
    	%單層事務
	Ts#tidstore.level == 1 ->
	    intercept_friends(Tid, Ts),
	    %% N is number of updates
	    case arrange(Tid, Store, Type) of
		{N, Prep} when N > 0 ->
		    multi_commit(Prep#prep.protocol,
				 majority_attr(Prep),
				 Tid, Prep#prep.records, Store);
		{0, Prep} ->
		    multi_commit(read_only,
				 majority_attr(Prep),
				 Tid, Prep#prep.records, Store)
	    end;
	true ->
	    %% nested commit
	    Level = Ts#tidstore.level,
	    [{OldMod,Obsolete} | Tail] = Ts#tidstore.up_stores,
	    req({del_store, Tid, Store, Obsolete, false}),
	    NewTs = Ts#tidstore{store = Store,
				up_stores = Tail,
				level = Level - 1},
	    NewTidTs = {OldMod, Tid, NewTs},
	    put(mnesia_activity_state, NewTidTs),
	    do_commit_nested
    end.

而multi_commit函數完成事務的2PC部分,該函數分支是默認的Erlang事務使用的提交方式運維

%使用簡單的2PC進行,
%1. 讓全部參與的Node準備進行提交
%2a.若是有一個Node回答No,讓全部回答yes的Node回滾
%2b.若是全部Node都回答yes,讓全部Node提交
multi_commit(sym_trans, _Maj = [], Tid, CR, Store) ->
    %% This lightweight commit protocol is used when all
    %% the involved tables are replicated symetrically.
    %% Their storage types must match on each node.
    %%
    %% 1  Ask the other involved nodes if they want to commit
    %%    All involved nodes votes yes if they are up
    %% 2a Somebody has voted no
    %%    Tell all yes voters to do_abort
    %% 2b Everybody has voted yes
    %%    Tell everybody to do_commit. I.e. that they should
    %%    prepare the commit, log the commit record and
    %%    perform the updates.
    %%
    %%    The outcome is kept 3 minutes in the transient decision table.
    %%
    %% Recovery:
    %%    If somebody dies before the coordinator has
    %%    broadcasted do_commit, the transaction is aborted.
    %%
    %%    If a participant dies, the table load algorithm
    %%    ensures that the contents of the involved tables
    %%    are picked from another node.
    %%
    %%    If the coordinator dies, each participants checks
    %%    the outcome with all the others. If all are uncertain
    %%    about the outcome, the transaction is aborted. If
    %%    somebody knows the outcome the others will follow.
    %劃分全部的提交節點爲內存或磁盤
    {DiscNs, RamNs} = commit_nodes(CR, [], []),
    %進入事務提交的準備狀態,這時候事務尚未真正的提交完成
    Pending = mnesia_checkpoint:tm_enter_pending(Tid, DiscNs, RamNs),
    ?ets_insert(Store, Pending),
    %循環的發出提交申請到各參與的節點上
    {WaitFor, Local} = ask_commit(sym_trans, Tid, CR, DiscNs, RamNs),
    %此處是死等,可是實際上也不是會完全死等
    %什麼狀況會發生死等呢
    %在ask_commit以後,對端節點死掉了,可是在下一次Erts心跳以前
    %對端節點又啓動起來了,OK這就是個有意思的狀況

    %全部節點都返回了贊成Outcome爲do_commit
    {Outcome, []} = rec_all(WaitFor, Tid, do_commit, []),
    ?eval_debug_fun({?MODULE, multi_commit_sym},
		    [{tid, Tid}, {outcome, Outcome}]),
    %向全部磁盤節點廣播提交
    rpc:abcast(DiscNs -- [node()], ?MODULE, {Tid, Outcome}),
    %向全部內存節點廣播提交
    rpc:abcast(RamNs -- [node()], ?MODULE, {Tid, Outcome}),
    case Outcome of
	do_commit ->
	    mnesia_recover:note_decision(Tid, committed),
	    do_dirty(Tid, Local),
	    mnesia_locker:release_tid(Tid),
	    ?MODULE ! {delete_transaction, Tid};
	{do_abort, _Reason} ->
	    mnesia_recover:note_decision(Tid, aborted)
    end,
    ?eval_debug_fun({?MODULE, multi_commit_sym, post},
		    [{tid, Tid}, {outcome, Outcome}]),
    Outcome;

Mnesia中常見問題和解決方法

常見問題

  1. 腦裂
    分佈式

  2. 傳說中的事務無限等待函數

問題成因

  1. 腦裂的成因,主要是網絡不穩定,致使兩個節點長時間的失去聯繫,讓彼此都認爲對方掉線了。而這個時候,兩個節點都接收了大量的數據寫入。當兩個節點自動恢復集羣通訊的時候,沒法經過事務決議合併數據的時候纔會出現。
    post

  2.  在ask_commit以後,對端節點死掉了,可是在下一次Erts心跳以前,對端節點又啓動起來了。OK,這就是這種有意思的狀況。基本上來說,這種事情發生的機率很是小,除非是設計失誤和對Erlang系統不熟悉濫用heart這東西產生的。

解決方法

  1. 對於腦裂問題,沒有什麼特別想說的。首先讓運維作好內網通訊的管理,Mnesia集羣使用專用的內部交換機和交換機熱冗餘,一點都不過度。其次,作好腦裂發生的準備,在應用層面進行處理,能夠參考大神的https://github.com/uwiger/unsplit項目。

  2. 對於這個傳說中的問題,我本身在使用Mnesia的集羣中並無遇到過。解決這問題,首先,要搞清楚Erts集羣是怎麼互相探測是活的,能夠看到我前面的博文http://my.oschina.net/u/236698/blog/389737。其次,在整個集羣內部創建NTP服務器,保證整個集羣的對時穩定性。再次,使用heart時,不要嚴格按照那個心跳時間設置,至少要設置2.5倍節點之間心跳探測時間爲保活時間。

相關文章
相關標籤/搜索