Erlang:在Erlang集群中创建单例gen_server的最佳方法是什么?

7

背景:

我想在erlang集群中启动一个全局唯一的gen_server进程。如果该进程停止或运行它的节点关闭,则应在其他节点上启动该进程。

该进程是监管器的一部分。问题是,在第二个节点上启动监管器失败,因为gen_server已经在第一个节点上进行全局注册了。

问题:

  • 在gen_server的start_link函数内检查该进程是否已经进行全局注册并在这种情况下返回已运行进程的{ok,Pid},而不是启动新的gen_server实例,这样做可以吗?
  • 如果使用上述方法,那么这样一来,一个进程将成为多个监管器的一部分,如果某个进程出现故障,则所有其他节点上的所有监管器都会尝试重新启动该进程。第一个监管器将创建一个新的gen_server进程,并且其他监管器将再次链接到该进程。
  • 我是否应该在gen_server的start_link函数中使用global:trans()

示例代码:


start_link() ->
    global:trans({?MODULE, ?MODULE}, fun() ->
        case gen_server:start_link({global, ?MODULE}, ?MODULE, [], []) of
            {ok, Pid} -> 
                {ok, Pid};
            {error, {already_started, Pid}} ->  
                link(Pid), 
                {ok, Pid};
            Else -> Else
        end     
    end).
2个回答

6
如果你返回一个你没有链接的东西的 {ok, Pid},那么依赖于返回值的监管者会感到困惑。如果你不打算让监管者使用它作为 start_link 函数,那么你可以这样做。
你的方法似乎应该可以工作,因为每个节点都会尝试启动新实例,如果全局实例死亡。你可能会发现需要增加你的监管者设置中的 MaxR 值,因为每当集群成员更改时,你会收到进程消息。
我过去创建全局单例的一种方法是在所有节点上运行进程,但让其中一个(赢得全局注册竞赛的那个)成为主节点。其他进程监视主节点,当主节点退出时,尝试成为主节点。(如果他们没有赢得注册竞赛,那么他们会监视那个赢得竞赛的进程的 pid)。如果这样做,你必须自己处理全局名称注册(即不要使用 gen_server:start({global, ...)功能),因为你希望进程无论是否赢得注册都能启动,在每种情况下它的行为都会有所不同。
进程本身必须更加复杂(它必须在主和非主模式下运行),但会很快稳定,并且不会产生大量日志垃圾和监管者启动尝试。
我通常需要几轮修订来解决边角情况,但在我看来,这比编写 OTP 分布式应用程序更省事。这种方法与分布式应用程序相比还有另一个优点,即你不必静态配置涉及到的节点列表,任何节点都可以成为运行进程的主副本的候选节点。你的方法也具有这个属性。

我认为这正是我所说的。我已经在我的问题中添加了一个示例代码块,以展示我的预期方法。你能解释一下为什么我不应该使用gen_server:start({global,..})吗? - Rumpelstilz
你的代码看起来相当合理。你可能不需要全局事务,因为某些进程将会赢得注册竞赛。我的答案是不正确的,我想提供一个自定义的全局冲突解决器 - 因此不使用内置的{global, Name}支持。 - archaelus
今天喝的咖啡不够多——除了名称冲突之外,我没有使用{global,...}支持的充分理由。 - archaelus
感谢您深入了解此事。但我不确定是否理解您的观点。难道每个节点上的监督程序已经处理了监控部分(即非主进程所做的工作)? - Rumpelstilz

4

我看到的问题是,你需要额外的应用程序包装器源代码,并且节点名称必须事先在内核配置中知道和配置。 - Rumpelstilz
2
+1 这是正确的方式。如果您想自己构建此系统,那么:a)如果没有竞争条件,很难做对;b)您可以以分布式应用程序管理的源代码为例。并且,您可以从Erlang生成配置文件,例如,从配置生成器节点查询所有节点,如果您不想将节点名称设置为静态。另一方面,当节点是动态的时候,查找节点存在额外的故障可能性(如何确定您拥有所需的所有节点?)。 - Peer Stritzinger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接