问题现象:
mnesia集群中的节点全部停止,然后再次启动,偶然出现所有节点都在一直等待数据同步的现象。
问题原因:
翻看相关源码后发现问题:
mnesia_contorller.erl
%% 选择可以进行数据加载的 table
initial_safe_loads() ->
case val({schema, storage_type}) of
ram_copies ->
%% 如果表为仅在内存中存储, 可以直接加载
%% 因此停止前记录的 down 掉的节点设置为 []
Downs = [],
Tabs = val({schema, local_tables}) -- [schema],
LastC = fun(T) -> last_consistent_replica(T, Downs) end,
lists:zf(LastC, Tabs);
disc_copies ->
%% 如果表为 磁盘存储, 需要判断停止前集群节点的存活状态
Downs = mnesia_recover:get_mnesia_downs(),
Tabs = val({schema, local_tables}) -- [schema],
LastC = fun(T) -> last_consistent_replica(T, Downs) end,
lists:zf(LastC, Tabs)
end.
last_consistent_replica(Tab, Downs) ->
%% 查找表结构信息
Cs = val({Tab, cstruct}),
%% 查找该表所有复制的节点信息
Copies = mnesia_lib:copy_holders(Cs),
...
%% 查找可以用来复制的远端节点
%% 注意 是远端节点 表示除去了节点本身
%% 另外 集群中先于本节点停止的节点需排除
BetterCopies0 = mnesia_lib:remote_copy_holders(Cs) -- Downs,
...
if
%% 该表仅在本节点进行拷贝复制
Copies == [node()] ->
{true, {Tab, local_only}};
...
%% 不是集群中最后一个停止的节点
%% 为防止可能的数据不一致 需从远端记载数据
BetterCopies /= [], Masters /= [node()] ->
false;
%%
true ->
{true, {Tab, initial}}
end.
这么一来,不是集群中最后一个停止的节点先启动时,对于disc存储类型的表,都不会进行加载。并且在mneia_gvar会记录{{Tab, where_to_read}, nowhere}。
调用mnesia:wait_for_tables(Tabs, Timeout)后最终会在mnesia_gvar中查找{Tab, where_to_read}对应的值。
如果对应的Table未加载,即为nowhere,并且Timeout设置为infinity,那么程序就会出现死等。
如果Timeout设置为具体的值,那么超时仍未完成加载的话,需要业务根据实际需求进行特殊处理。通常是抛出异常让程序无法正常启动。
注:ejabberd采用的死等的方式,rabbitmq则是采用超时抛出异常停止运行。