-module(kvs).
-export([start/0,store/2,lookup/1]).
start() ->register(kvs,spawn(fun()->loop()end)).
store(Key,Value) ->rpc({store,Key,Value}).
lookup(Key) -> rpc({lookup,Key}).
rpc(Q) ->
kvs ! {self(),Q},
receive
{kvs ,Reply} ->
Reply
end.
loop() ->
receive
{From,{store ,Key,Value}} ->
put(Key,{ok,Value}),
From! {kvs ,true},
loop();
{From,{lookup ,Key}} ->
From! {kvs ,get(Key)},
loop()
end.
在我们上一章我们完成了一个简单名字服务,并在同一台机器上进行了简单的侧试
让我们继续下一步
让客户机和服务器运行于同一个局域网内的不同机器上
我们要准备使用两个节点,第一个节点是doris.myerl.example.com上的gandlf,另外一个节点是george.myerl.example.com上的bilbo。在这之前,我们分别在两个不同机器上启动两个终端窗口。。我们把这两个窗口分别叫做doris和george。在完成这些工作之后,我们可以很简单地在两台机器上输入下面这些命令。
步骤1:在doris上启动一个Erlang节点。
PS C:\Users\admin> doris $ erl -name gandalf -setcookie abc
(gandalf@doris.myerl.example.com) 1> kvs:start().
true
步骤2:在george上启动一个Erlang节点并向gandalf发送一些命令。
PS C:\Users\admin> george $ er1 -name bilbo -setcookie abc
(bilbo@george.myerl.example.com) 1>rpc:call(gandalf@doris.myerl.example.com,
kvs ,store,[weather,cold]).
true
(bilbo@george.myerl.example.com) 2>rpc:call(gandalf@doris.myerl.example.com,
kvs, lookup , [weather]).
{ok,cold}
这两个程序的运作方式就和同一台机器不同节点上的运行情况一模一样。
不过为了保证上面的这些步骤顺利进行,我们做了一些比在同一台机器上两个节点的情形更为复杂的设置,这里一共有4个步骤。
(1)以-name参数启动Erlang。如果在同一台机器上启动两个节点,使用“短”名(用-sname标志指定),但是如果它们运行在不同的网络上,就需要使用-name。
如果两台机器都在同一个子网内工作,也可以使用-sname。换言之,只有在不需要DNS服务的情况下,才能使用-sname。
(2)确保两个节点之间使用相同的cookie。这就是为何两个节点的启动都要使用命令行参数-setcookie abc。
(3)确保使用的节点主机名称正确无误,并且能被DNS正确解析。在这个例子中,域名myerl.example.com是直接对应本地网络,你可以在本地的/etc/hosts里加入想对应的域名入口点。
(4)确保两个系统之中需要运行的代码版本相同。
分布式原语
分布式Erlang的核心概念是节点。一个节点就是一个自给自足的系统,它是一个包含了地址空间和独立进程集的完整虚拟机。
访问单个节点或者节点集都受到cookie系统的保护。每个节点都有一个cookie,而目必须保证所有要和这个节点通信的其他节点都有相同的cookie。为此,在一个分布式的Erlang系统中,所有节点都必须用相同的magic cookie来启动,或者执行erlang:set_cookie来将它们的cookie改为相同的值。
具有相同cookie而且彼此互相连接的节点集称为Erlang 集群。
下面这些BIF在分布式编程中常常会用到。
@spec spawn(Node,Fun) ->Pid
这个原语和spawn(Fun)没有多大区别,但新创建的进程位于Node节点之上。
@spec spawn(Node,Mod,Func,ArgList) ->Pid
这个原语和spawn(Mod, Func,ArgList)没有多大区别,不过新创建的进程位于Node节点上。spawn(Mod,Func,Args)原语创建一个会执行apply(Mod,Func,Args)的新进程,它返回新进程的PID。
@spec spawn_link(Node,Fun) -> Pid
这个原语和spawn_link (Fun)没有多大区别,新创建的进程会在Node节点上运行。
@spec spawn_link(Node,Mod,Func,ArgList) ->Pid
这个原语和spawn(Node,Mod, Func,ArgList)没有多大区别,只是新创建的进程会与当前进程互相链接。
@spec disconnect_node(Node) -> bool() | ignored
强制断开一个节点的连接。
@spec monitor_node(Node,Flag) -> true
如果Flag为true就会打开监视,如果Flag为false就会关闭监视。在监视被打开时若有新的Node加入或者离开Erlang集群,执行这个BIF的进程就会收到{nodeup,Node}和{nodedown,Node}消息。
@spec node() -> Node
这个原语会返回本地节点的名字。如果本地节点不是分布式的,就会返回nonode@nohost。
@spec node(Arg) -> Node
这个原语会返回Arg所指定的节点。Arg可以是一个PID、一个引用或者一个端口。如果本地节点不是分布式的,就会返回nonode@nohost。
@spec nodes() ->[Node]
这个原语会返回网络上与当前节点连接的所有其他节点列表。
@spec is_alive() -> bool()
如果本地节点状态正常而且是分布式系统的一个部分,那么这个原语就会返回true,否则返回false。
另外,我们可以使用send原语来向一个在分布式Erlang节点集中注册的进程发送消息。下面这个语法的意思就是,向节点Node上名为RegName的注册进程发送消息Msg。
{RegName,Node} ! Msg
一个启动远程进程的样例
-module(dist_demo).
-export([rpc/4,start/1]).
start(Node) ->
spawn(Node,fun() ->loop()end).
rpc(Pid,M,F,A)->
Pid ! {rpc,self(),M,F,A},
receive
{Pid,Response} ->
Response
end.
loop() ->
receive
{rpc,Pid,M,F,A}->
Pid ! {self(),(catch apply(M,F,A))},loop()
end.
然后我们启动两个节点,两个节点都必须加载这个代码。我们是在一台机器上,我们只要在同一个目录里启动两个Erlang节点就可以了。
在主机doris上,我们启动一个名为gandalf的节点:
PS C:\Users\admin> doris $ er1 -name gandalf -setcookie abc
(gandalf@doris.myerl.example.com)1>
然后在主机george上,我们启动一个名为bilbo的节点,不要忘了,必须使用相同的cookie:george
PS C:\Users\admin>$ er1 -name bilbo -setcookie abc
(bilbo@george.myerl.example.com)1>
现在(在biblo上)我们可以启动一个运行在远程节点( gandalf结点)上的进程:
(bilbo@george.myerl.example.com)1> Pid =
dist_demo:start('gandalf@doris.myerl.example.com').
<3042.40.0>
这里返回的Pid就是一个运行在远程节点上的进程标识符,然后我们就可以调用dist_demo: rpc/4来执行一个远程节点上的远程调用:
(bilbo@george.myerl.example.com)2> dist_demo:rpc(Pid,erlang,node,[]).
'ganda1f@doris.myerl.example.com'
这里我们在远程节点上对erlang:node()求值,然后返回运算结果。
到这里了 我们的简单测试就完成了