erlang中的分布式算法模拟
distributed algorithms simulation in erlang
我是 Erlang 的初学者。我想用它来观察 "textbooks" 分布式算法(领导者选举、共识...)的执行情况,以用于教学目的。在那个阶段,我将我的系统的拓扑结构描述为一个图(从 int 到 int 列表的字典),并基于此,我用它们的邻居列表实例化和初始化我的节点。它工作正常,但似乎有点临时。必须有一种更通用的方法来做到这一点。可以提供帮助的常用库或工具?
如果不是,你觉得我这样做有意义吗?见下文。
-module(control).
-export([init/1, init/4]).
% create N processes 1 .. N and return a dict from
% 1..N to Pids
create(_, 0, Dict) -> Dict ;
create(Module, N, Dict) ->
Id = spawn(Module, proc, [nil]),
Id ! init_state,
create(Module, N-1, dict:append(N, Id, Dict)).
% broadcast a Signal to all processes in Dict
broadcast(Dict, Signal) ->
F = fun (_, [Y]) -> Y ! Signal end,
dict : map (F, Dict),
ok.
% send a Signal to process Dict(I)
send(Dict, I, Signal) ->
[Id] = dict : fetch(I, Dict),
Id ! Signal,
ok.
% wait for N ok signals
syncr(0) -> ok;
syncr(N) ->
receive
ok -> io : format("ok received ~n", []), syncr(N-1)
end.
% init neighbors according to topology Graph
init_topology(Dict, Graph) ->
F = fun(X) -> [Res] = dict : fetch(X, Dict), Res end,
IdToPId = fun(L) -> lists : map(F, L) end,
G = fun (I, [Y]) ->
[LId] = dict : fetch(I, Graph),
LPId = IdToPId(LId),
Y ! {neighbors, LPId, self()} end,
dict : map (G, Dict),
ok.
% init all states with unary function Signal : I -> term.
init_state(Dict, Signal) ->
F = fun (I, [Y]) -> Y ! {init, Signal(I), self()} end,
dict : map (F, Dict),
ok.
% init all process according to the given topology and Signal : I -> term
% returns a pair of function Send and Broadcast
init(Module, N, Graph, Signal) ->
Dict = create(Module, N, dict:new()),
init_topology(Dict, Graph),
init_state(Dict, Signal),
syncr(2 * N),
Send = fun (I, S) -> send(Dict, I, S) end,
Broadcast = fun (S) -> broadcast(Dict, S) end,
{ Send, Broadcast }.
% this is a particular instanciation of a consensus algorithm with
% N nodes
% a complete graph topology
% some initialisation of the process states
% the S and B returned functions allows me to interact with the system
% to start the algorithm for instance
init(N) -> {S, B} = control : init(consensus, N, topology : complete(N), fun(_)
-> random : uniform(1000) end), {S,B}.
OTP 有一些死记硬背的方法来处理上述任务。我说 "rote" 而不是 "generic" 因为它们是 OTP 约定而不是通用分布式算法实现的普遍接受的解决方案。
OTP 在 application
/supervisor
/[=12 中为诸如初始化进程图、向它们发送消息、终止、同步信号等之类的事情提供解决方案=]* 概念。维护进程注册表可以通过 global
模块、gproc
等实用程序或使用进程组来处理——但是您处理进程组的方式似乎更有可能是 PID 列表更好比进程注册表,甚至你当前的进程字典更合适(分配给给定进程的整数标签是没有意义的,你不太可能需要向特定进程 N 发送消息,而不需要它本身启动消息链,其中如果你已经知道它的 PID)。
无论如何,当您尝试解决实际问题而不是理论问题时,启动流程图的 OTP 方法最有用。在理论系统中,没有什么会失败,代码是完美的,并且没有用户——你正在为一个理想的案例建模。在现实世界中,东西坏了,需要监控,错误比比皆是,人们被电线和网络电缆绊倒,日常的常见情况通常是一团糟。现实世界的混乱确实是 OTP 旨在帮助应对的,它只是偶然地以提供一种死记硬背的方法将程序声明为过程图的方式这样做——事实上这是偶然的是为什么"actor model" 和分布式计算理论的语言在 Erlang/OTP 文档中使用不多。
以 OTP 方式启动系统的一个副作用是监控、监督和其他一些实际有用的东西(如 "useful in a production environment where users depend on a system being available")application
给你 "just happen" 而无需需要为监控、调试或临时监督策略添加一堆代码。但这些可能不是您真正关心的事情。
OTP 没有提供的是一组通用的共识算法实现。在大多数实际系统中(同样,这是 Erlang 的真正关注点)会做出将共识的复杂性降至最低的决定,并依赖任意条件来强制遵守领导模型,即使这暂时具有破坏性。
因此,一方面 "Yes"、Erlang/OTP 提供了死记硬背的方法来处理您上面编写的大部分代码,但另一方面 "No"、Erlang/OTP在设计时并未考虑任何特定的分布式计算理论模型,因此它的编程文化和框架有意识地决定将更高的优先级放在为生产中的健壮系统部署提供捷径的功能上,并降低实现的优先级或坚持理论基础,因此您可能会缺少某些功能。
我是 Erlang 的初学者。我想用它来观察 "textbooks" 分布式算法(领导者选举、共识...)的执行情况,以用于教学目的。在那个阶段,我将我的系统的拓扑结构描述为一个图(从 int 到 int 列表的字典),并基于此,我用它们的邻居列表实例化和初始化我的节点。它工作正常,但似乎有点临时。必须有一种更通用的方法来做到这一点。可以提供帮助的常用库或工具?
如果不是,你觉得我这样做有意义吗?见下文。
-module(control).
-export([init/1, init/4]).
% create N processes 1 .. N and return a dict from
% 1..N to Pids
create(_, 0, Dict) -> Dict ;
create(Module, N, Dict) ->
Id = spawn(Module, proc, [nil]),
Id ! init_state,
create(Module, N-1, dict:append(N, Id, Dict)).
% broadcast a Signal to all processes in Dict
broadcast(Dict, Signal) ->
F = fun (_, [Y]) -> Y ! Signal end,
dict : map (F, Dict),
ok.
% send a Signal to process Dict(I)
send(Dict, I, Signal) ->
[Id] = dict : fetch(I, Dict),
Id ! Signal,
ok.
% wait for N ok signals
syncr(0) -> ok;
syncr(N) ->
receive
ok -> io : format("ok received ~n", []), syncr(N-1)
end.
% init neighbors according to topology Graph
init_topology(Dict, Graph) ->
F = fun(X) -> [Res] = dict : fetch(X, Dict), Res end,
IdToPId = fun(L) -> lists : map(F, L) end,
G = fun (I, [Y]) ->
[LId] = dict : fetch(I, Graph),
LPId = IdToPId(LId),
Y ! {neighbors, LPId, self()} end,
dict : map (G, Dict),
ok.
% init all states with unary function Signal : I -> term.
init_state(Dict, Signal) ->
F = fun (I, [Y]) -> Y ! {init, Signal(I), self()} end,
dict : map (F, Dict),
ok.
% init all process according to the given topology and Signal : I -> term
% returns a pair of function Send and Broadcast
init(Module, N, Graph, Signal) ->
Dict = create(Module, N, dict:new()),
init_topology(Dict, Graph),
init_state(Dict, Signal),
syncr(2 * N),
Send = fun (I, S) -> send(Dict, I, S) end,
Broadcast = fun (S) -> broadcast(Dict, S) end,
{ Send, Broadcast }.
% this is a particular instanciation of a consensus algorithm with
% N nodes
% a complete graph topology
% some initialisation of the process states
% the S and B returned functions allows me to interact with the system
% to start the algorithm for instance
init(N) -> {S, B} = control : init(consensus, N, topology : complete(N), fun(_)
-> random : uniform(1000) end), {S,B}.
OTP 有一些死记硬背的方法来处理上述任务。我说 "rote" 而不是 "generic" 因为它们是 OTP 约定而不是通用分布式算法实现的普遍接受的解决方案。
OTP 在 application
/supervisor
/[=12 中为诸如初始化进程图、向它们发送消息、终止、同步信号等之类的事情提供解决方案=]* 概念。维护进程注册表可以通过 global
模块、gproc
等实用程序或使用进程组来处理——但是您处理进程组的方式似乎更有可能是 PID 列表更好比进程注册表,甚至你当前的进程字典更合适(分配给给定进程的整数标签是没有意义的,你不太可能需要向特定进程 N 发送消息,而不需要它本身启动消息链,其中如果你已经知道它的 PID)。
无论如何,当您尝试解决实际问题而不是理论问题时,启动流程图的 OTP 方法最有用。在理论系统中,没有什么会失败,代码是完美的,并且没有用户——你正在为一个理想的案例建模。在现实世界中,东西坏了,需要监控,错误比比皆是,人们被电线和网络电缆绊倒,日常的常见情况通常是一团糟。现实世界的混乱确实是 OTP 旨在帮助应对的,它只是偶然地以提供一种死记硬背的方法将程序声明为过程图的方式这样做——事实上这是偶然的是为什么"actor model" 和分布式计算理论的语言在 Erlang/OTP 文档中使用不多。
以 OTP 方式启动系统的一个副作用是监控、监督和其他一些实际有用的东西(如 "useful in a production environment where users depend on a system being available")application
给你 "just happen" 而无需需要为监控、调试或临时监督策略添加一堆代码。但这些可能不是您真正关心的事情。
OTP 没有提供的是一组通用的共识算法实现。在大多数实际系统中(同样,这是 Erlang 的真正关注点)会做出将共识的复杂性降至最低的决定,并依赖任意条件来强制遵守领导模型,即使这暂时具有破坏性。
因此,一方面 "Yes"、Erlang/OTP 提供了死记硬背的方法来处理您上面编写的大部分代码,但另一方面 "No"、Erlang/OTP在设计时并未考虑任何特定的分布式计算理论模型,因此它的编程文化和框架有意识地决定将更高的优先级放在为生产中的健壮系统部署提供捷径的功能上,并降低实现的优先级或坚持理论基础,因此您可能会缺少某些功能。