mnesia里的lamport clock

mnesia使用"wait-die"机制预防死锁,"wait-die"是基于时间戳的,mnesia采用了lamport clock算法作为"wait-die"机制的时间戳。

Lamport clock是解决分布式系统中事件发生时序的一种方式。 Lamport定义了一个关系叫做happens-before,记为-> 。a->b意味着所有的进程都同意事件a发生在事件b之前。在以下两种情况下,可以很容易的得到这个关系:

1)如果事件a和事件b是同一个进程中的并且事件a发生在事件b前面,那么a->b

2)如果进程A发送一条消息m给进程B,a代表进程A发送消息m的事件,b代表进程B接收消息m的事件,那么a->b

Lamport logical clock算法大致实现为:

每个进程Pi维护一个本地计数器Ci,相当于logical clock,按照以下规则更新Ci:

1)每次执行一个事件(例如通过网络发送消息,或者将消息交换给应用层,或者其他的一些内部事件 )之前,将Ci加1

2)当进程Pi发送消息m给Pj的时候,在消息m上附上Ci

3)当接收进程Pj接收到进程Pi发送的消息时,更新自己的Cj = max{Cj,Ci}

Lamport论文链接:http://www.stanford.edu/class/cs240/readings/lamport.pdf

===========================================================

在mnesia中充当这个计数器的就是事务ID,事务ID统一由mnesia_tm进程负责初始化和更新。mnesia_locker进程就是根据比较事务ID的大小来执行"wait-die"机制和防止饿死现象的。

allowed_to_be_queued(WaitForTid, Tid) ->
    case get(pid_sort_order) of
	undefined -> WaitForTid > Tid;
	r9b_plain ->
	    cmp_tid(true, WaitForTid, Tid) =:= 1;
	standard  ->
	    cmp_tid(false, WaitForTid, Tid) =:= 1
    end.
mnesia主要在以下几种情况下更新事务ID

1)本节点请求开始新事务时

在执行一个新事务前,会向mnesia_tm进程获取一个事务ID和临时存储空间,mnesia_tm收到请求后会将事务ID计数器加1并赋给该事务。

mnesia_tm.erl:

doit_loop(#state{coordinators=Coordinators,participants=Participants,supervisor=Sup}=State) ->
    receive
    ...
    {From, start_outer} ->
        case catch ?ets_new_table(mnesia_trans_store, [bag, public]) of
        Etab ->
            tmlink(From),
            C = mnesia_recover:incr_trans_tid_serial(),
            ?ets_insert(Etab, {nodes, node()}),
            Tid = #tid{pid = tmpid(From), counter = C},
            ...

mnesia_recover.erl:

incr_trans_tid_serial() ->
    ?ets_update_counter(mnesia_decision, serial, 1).
2)参与其他节点发起的事务结束时

事务涉及多个节点时,事务发起者会将带有本节点事务ID的信息告诉所有参与的节点。当参与节点处理事务结束时,不管是正常结束还是异常结束,都会更新本节点的事务计数器。

mnesia_tm.erl:

transaction_terminated(Tid)  ->
    mnesia_checkpoint:tm_exit_pending(Tid),
    Pid = Tid#tid.pid,
    if
	node(Pid) == node() ->
	    unlink(Pid);
	true ->  %% Do the Lamport thing here
	    mnesia_recover:sync_trans_tid_serial(Tid)
    end.

mnesia_recover.erl:

sync_trans_tid_serial(ThatCounter) when is_integer(ThatCounter) ->
    ThisCounter = trans_tid_serial(),
    if
	ThatCounter > ThisCounter ->
	    set_trans_tid_serial(ThatCounter + 1);
	true ->
	    ignore
    end;
sync_trans_tid_serial(Tid) ->
    sync_trans_tid_serial(Tid#tid.counter).

set_trans_tid_serial(Val) ->
    ?ets_insert(mnesia_decision, {trans_tid, serial, Val}).
其他情况:例如,mnesia启动进行dump时、事务过程中出现异常,向其他节点询问事务结果时也都会进行事务计数器的更新。 

你可能感兴趣的:(mnesia里的lamport clock)