global_name_server
global_name_server
是kernel
繼code_server,rex
后第3個(gè)啟動(dòng)的gen_server
1. 主要用途:
- 注冊全局名稱服務(wù)
register_name/2,register_name/3
- 全局鎖
set_lock/1,set_lock/2,set_lock/3
- 保證一個(gè)函數(shù)在加鎖情況下執(zhí)行
trans/2,trans/3,trans/4
2. 申明
申明部分見kernel.erl
% kernel.erl
Global = #{id => global_name_server,
start => {global, start_link, []},
restart => permanent,
shutdown => 2000,
type => worker,
modules => [global]},
3. global
的自旋鎖set_lock
過程
% global.erl
set_lock({_ResourceId, _LockRequesterId}, [], _Retries, _Times) ->
true;
set_lock({_ResourceId, _LockRequesterId} = Id, Nodes, Retries, Times) -> {times, Times}}),
case set_lock_on_nodes(Id, Nodes) of
true ->
?trace({set_lock_true, Id}),
true;
false = Reply when Retries =:= 0 ->
% 嘗試一段時(shí)間后鉴吹,直接返回結(jié)果
Reply;
false ->
random_sleep(Times),
% 不斷的進(jìn)行嘗試,自選鎖機(jī)制
set_lock(Id, Nodes, dec(Retries), Times + 1)
end.
set_lock_on_nodes(_Id, []) ->
true;
set_lock_on_nodes(Id, Nodes) ->
case local_lock_check(Id, Nodes) of
true ->
Msg = {set_lock, Id},
% 使用gen_server來保證,鎖成功
{Replies, _} = gen_server:multi_call(Nodes, global_name_server, Msg),
?trace({set_lock, {me, self()}, Id, {nodes, Nodes}, {replies, Replies}}),
check_replies(Replies, Id, Replies);
false = Reply ->
Reply
end.
% set_lock 的實(shí)現(xiàn)函數(shù)
handle_call({set_lock, Lock}, {Pid, _Tag}, S0) ->
{Reply, S} = handle_set_lock(Lock, Pid, S0),
{reply, Reply, S};
handle_set_lock(Id, Pid, S) ->
?trace({handle_set_lock, Id, Pid}),
% step1 檢查鎖是否被占
case can_set_lock(Id) of
{true, PidRefs} ->
% step2,檢查是否已經(jīng)鎖了服傍,沒有的話插入鎖
case pid_is_locking(Pid, PidRefs) of
true ->
{true, S};
false ->
{true, insert_lock(Id, Pid, PidRefs, S)}
end;
false = Reply ->
{Reply, S}
end.
can_set_lock({ResourceId, LockRequesterId}) ->
case ets:lookup(global_locks, ResourceId) of
[{ResourceId, LockRequesterId, PidRefs}] ->
% 這是一個(gè)可重入式鎖
{true, PidRefs};
[{ResourceId, _LockRequesterId2, _PidRefs}] ->
false;
[] ->
{true, []}
end.
4. register_name
過程
假設(shè)一個(gè)分布式系統(tǒng)有N個(gè)非hidden節(jié)點(diǎn)(erlang:length(nodes()) = N
)
- 在
boss
節(jié)點(diǎn)加鎖gen_server:multi_call([Boss]], global_name_server, {set_lock,{global,pid()}})
- 在所有節(jié)點(diǎn)上加鎖
gen_server:multi_call(Nodes, global_name_server, {set_lock,{global,pid()}})
- 在所有節(jié)點(diǎn)上注冊
gen_server:multi_call(Nodes,global_name_server,{register, Name, Pid, Method})
- 在其余N個(gè)節(jié)點(diǎn)刪除鎖
gen_server:multi_call(Nodes, global_name_server, {del_lock, Id})
- 在
boss
節(jié)點(diǎn)刪除鎖gen_server:multi_call([Boss]], global_name_server, {del_lock, Id})
一共要gen_servre:call
N x 3(加鎖外遇,注冊洛搀,刪鎖)
次扳抽,效率喜人
% global.erl
%% 函數(shù)入口
trans_all_known(Fun) ->
Id = {?GLOBAL_RID, self()},
% step 1 and step 2
Nodes = set_lock_known(Id, 0),
try
% step 3
Fun(Nodes)
after
% step 4 and step 5
delete_global_lock(Id, Nodes)
end.
set_lock_known(Id, Times) ->
Known = get_known(),
Nodes = [node() | Known],
Boss = the_boss(Nodes),
%% Use the same convention (a boss) as lock_nodes_safely. Optimization.
% step 1,先在boss節(jié)點(diǎn)設(shè)置鎖 {?GLOBAL_RID, self()}
case set_lock_on_nodes(Id, [Boss]) of
true ->
% step2,在所有節(jié)點(diǎn)設(shè)置鎖
case lock_on_known_nodes(Id, Known, Nodes) of
true ->
Nodes;
false ->
% 萬一不成功斤贰,還得先解除智哀,這下子IO次數(shù)多了去了。
del_lock(Id, [Boss]),
random_sleep(Times),
set_lock_known(Id, Times+1)
end;
false ->
random_sleep(Times),
set_lock_known(Id, Times+1)
end.
register_name(Name, Pid, Method0) when is_pid(Pid) ->
Method = allow_tuple_fun(Method0),
Fun = fun(Nodes) ->
% step3,在鎖成功設(shè)置之后荧恍,向所有節(jié)點(diǎn)注冊Name
case (where(Name) =:= undefined) andalso check_dupname(Name, Pid) of
true ->
gen_server:multi_call(Nodes,
global_name_server,
{register, Name, Pid, Method}),
yes;
_ ->
no
end
end,
?trace({register_name, self(), Name, Pid, Method}),
gen_server:call(global_name_server, {registrar, Fun}, infinity).
delete_global_lock(LockId, Nodes) ->
TheBoss = the_boss(Nodes),
% step4 其余節(jié)點(diǎn)刪除鎖
del_lock(LockId, lists:delete(TheBoss, Nodes)),
% step5 Boss節(jié)點(diǎn)刪除鎖
del_lock(LockId, [TheBoss]).
5. 優(yōu)缺點(diǎn)
缺點(diǎn)
- 不能對
hidden
節(jié)點(diǎn)進(jìn)行register_name
加鎖操作瓷叫。 - 自旋鎖對多個(gè)節(jié)點(diǎn)操作,
IO
次數(shù)太高,不如redis
分布式鎖來的輕巧摹菠。
優(yōu)點(diǎn)
- 語言層面的實(shí)現(xiàn)盒卸,不用借助第三方
- 分布式的存儲(chǔ),所有的節(jié)點(diǎn)上都保留一份辨嗽,所以在讀方面會(huì)占優(yōu)勢
6.總結(jié)
了解系統(tǒng)實(shí)現(xiàn)全局鎖的實(shí)現(xiàn)方式與缺陷世落,在使用當(dāng)中避免一些性能瓶頸。