2019独角兽企业重金招聘Python工程师标准>>>
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时、事务过程中出现异常,向其他节点询问事务结果时也都会进行事务计数器的更新。