erlang 全局名字服务global_name_server

global_name_server

global_name_serverkernelcode_server,rex后第3个启动的gen_server

1. 主要用途:

  1. 注册全局名称服务 register_name/2,register_name/3
  2. 全局锁 set_lock/1,set_lock/2,set_lock/3
  3. 保证一个函数在加锁情况下执行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 ->
      % 尝试一段时间后,直接返回结果
      Reply;
    false ->
      random_sleep(Times),
      % 不断的进行尝试,自选锁机制
      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 的实现函数
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,检查是否已经锁了,没有的话插入锁
      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}] ->
      % 这是一个可重入式锁
      {true, PidRefs};
    [{ResourceId, _LockRequesterId2, _PidRefs}] ->
      false;
    [] ->
      {true, []}
  end.

4. register_name 过程

假设一个分布式系统有N个非hidden节点(erlang:length(nodes()) = N)

  1. boss节点加锁gen_server:multi_call([Boss]], global_name_server, {set_lock,{global,pid()}})
  2. 在所有节点上加锁gen_server:multi_call(Nodes, global_name_server, {set_lock,{global,pid()}})
  3. 在所有节点上注册 gen_server:multi_call(Nodes,global_name_server,{register, Name, Pid, Method})
  4. 在其余N个节点删除锁 gen_server:multi_call(Nodes, global_name_server, {del_lock, Id})
  5. boss节点删除锁 gen_server:multi_call([Boss]], global_name_server, {del_lock, Id})

一共要gen_servre:call N x 3(加锁,注册,删锁) 次,效率喜人

% global.erl
%% 函数入口
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节点设置锁 {?GLOBAL_RID, self()}
    case set_lock_on_nodes(Id, [Boss]) of
        true ->
            % step2,在所有节点设置锁
            case lock_on_known_nodes(Id, Known, Nodes) of
                true ->
                    Nodes;
                false -> 
                    % 万一不成功,还得先解除,这下子IO次数多了去了。
                    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,在锁成功设置之后,向所有节点注册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 其余节点删除锁
    del_lock(LockId, lists:delete(TheBoss, Nodes)),
    % step5 Boss节点删除锁
    del_lock(LockId, [TheBoss]).
    

5. 优缺点

缺点
  1. 不能对hidden节点进行register_name加锁操作。
  2. 自旋锁对多个节点操作,IO次数太高,不如redis分布式锁来的轻巧。
优点
  1. 语言层面的实现,不用借助第三方
  2. 分布式的存储,所有的节点上都保留一份,所以在读方面会占优势

6.总结

了解系统实现全局锁的实现方式与缺陷,在使用当中避免一些性能瓶颈。

7. 参考文献

  1. https://github.com/erlang/otp/blob/master/lib/kernel/src/global.erl
  2. https://github.com/erlang/otp/blob/master/lib/kernel/src/kernel.erl
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容