我正在试图找出解决Elixir应用程序的以下负载分配/流程唯一性保证问题的最佳方法。
该应用程序
我的Elixir应用程序在n
个不同的节点上启动(从大型池中随机选择,没有固定的IP或主机名称预先知道)形成一个集群(我不确定什么是进行节点发现的最佳方式但是现在让我们忽略它。
简而言之,应用程序的主要目的是使两个系统随时间保持同步,基本上是一个集成。每个用户都有一个集成,可以添加新的集成,也可以随时删除现有集成。
问题
我希望每次集成都有一个Erlang进程,因为它在概念上非常优雅并带来许多好处(例如每个集成都有一个自然的同步点)。看来这也是扩展系统的方法。
问题在于,这个过程显然需要在整个集群中是唯一的(如果两个进程试图同步相同的集成,很难预测数据会发生什么),并且我想自动将工作重新分配为节点失败或新的整合进来。
此外,在部署新版本的应用程序时,新群集将在旧群集关闭之前启动(我们不依赖于热代码重新加载)。这个过渡阶段需要以某种方式处理。
可能的解决方案
一种解决方案可能是依靠全球流程。启动时,节点会自行注册,连接到其他已注册的节点,然后尝试启动全局Scheduler
进程的副本,该进程的唯一作用是跨节点启动集成过程。
虽然这提供了容错功能,但它不能保证每个集成一个进程,因为群集可以通过网络分区分成两个。它也不处理新旧集群在线并且旧集群仍在工作的短暂时期。
某种全局锁定机制(通过共享的Redis实例?)可用于处理网络分区和应用程序重启,但这看起来相当糟糕。
有什么建议吗?
谢谢!
答案 0 :(得分:6)
出于解释的目的,我们假设:
UID
PID
NID
{NID, PID}
问题是要确保1-to-1
和UID
之间存在{NID, PID}
映射。
基本上有两种解决方法:
<强> I 即可。引入共享状态,类似寄存器,跟踪UID
和{NID, PID}
之间的映射。我们称之为The Register。如果这是具有共享模式的mnesia数据库,则Redis实例,单独的节点或其他任何内容都是实现细节。在任何情况下,每个新进程都需要在开始集成特定UID
之前在The Register注册。在网络分区的情况下,节点发生故障或以标准方式处理的其他灾难,例如:根据您的要求,您可以根据相应的CAP theorem设计注册。
<强> II 即可。通过算法为给定的PID
分配NID
到UID
。这与hashtable的工作方式类似。作为一个例子,让我们说UID
是一个整数(如果它不是,那么任何数据结构都可以简化为hash function的整数)。您可以选择如下节点:
NID = UID % NX
其中NX
是节点数量(%
当然是modulo operation)。在每个节点上,您可以将过程注册为UID
。完成后,您可以根据UID
唯一地处理每个集成商流程 - 您使用%
操作获取NID
,UID
本身获取PID
{1}}在节点上。
第二种方法要求节点数量不会发生变化,例如:监视节点,如果一个节点发生故障,另一个节点将被启动以替换它。它也适用于每个节点都是主从配对,并且它们之间发生了一些复制。
这两种方法的区别在于,在第一种情况下,您只有一个故障点 - 如果寄存器变得不可用,则无法启动新的集成器。而在第二种情况下,UID
与其集成器进程之间的分配是完全分布式和异步的 - 如果一个节点发生故障,则其他节点不间断地工作,这使得它更容易扩展。
但是如果节点数量发生变化,第一种方法仍然可以像以前一样工作,而在第二种方法中,这也会导致哈希函数发生变化。这需要重新平衡进程(在节点之间移动),以便仍然可以正确地解决它们。