分布式编程
天生分布式的程序
优点:
- 性能: 不同部分在不同的机器上并行运行来让程序跑得更快。
- 可靠性:如果一台机器出了故障,可以在另一台机器上继续。
- 可扩展性:可以通过添加机器提升处理能力。
COOKIE 保护系统
每个节点都有一个COOKIE,如果它想与其他任何节点通信,它的COOKIE就必须和对方节点的COOKIE相同。
Erlang集群的定义就是一组带有相同COOKIE的互连节点。
COOKIE从不会在网络中明文传输,它只用来对某次会话进行初始认证。
COOKIE的三种设置方法:
- $HOME/.erlang.COOKIE 存放相同的COOKIE chmod 400 .erlang.COOKIE 文件只能被它的所有者访问。
- erl -setCOOKIE C (不安全,别人容易查看到)
- erlang:set_COOKIE(node(),C) 把本地COOKIE设置成原子C
分布式Erlang
高度信任,任何节点都可以在其他Erlang节点上执行任意操作。
编写一个用于分布式的程序demo
-module(kvs).
-export([start/0,store/2,lookup/1]).%% 启动进程监听收到的消息
start() -> register(kvs,spawn(fun() -> loop() end)).%% 调用rpc存入Key-Value
store(Key,Value) -> rpc({store,Key,Value}).
%% 查询存入Key-Value
lookup(Key) -> rpc({lookup,Key}).rpc(Q) ->kvs ! {self(),Q},receive{kvs,Replay} ->Replayend.loop() ->receive{From,{store,Key,Value}} ->put(Key,{ok,Value}),From ! {kvs,true},loop();{From,{lookup,Key}} ->From ! {kvs,get(Key)},loop()end.
一台电脑启动执行
kvs:start().kvs:store(weather,raining).kvs:lookup(weather).
一台电脑启动两个节点执行
%% 开启一个节点gandalf@localhost
erl -sname gandalf
kvs:start().%% 开启第二个节点bilbo@localhost
erl -sname bilbo
rpc:call(gandalf@localhost,kvs,store,[weather,fine]).
rpc:call(gandalf@localhost,kvs,lookup,[weather]).
两台服务器相互通信(同局网),需要设置相同的COOKIE 才能通信
%% 开启一个节点gandalf@doris.myerl.example.com
erl -name gandalf -setCOOKIE abc
kvs:start().%% 开启第二个节点bilbo@doris.myerl.example.com
erl -name bilbo -setCOOKIE abc
rpc:call(gandalf@doris.myerl.example.com,kvs,store,[weather,fine]).
rpc:call(gandalf@doris.myerl.example.com,kvs,lookup,[weather]).
两台服务器相互通信(外网)
erl -name ... -setCOOKIE ... -kernel inet_dist_listen_min Min\inet_dist_listen_max Max
相互调用的内置函数
rpc提供了许多远程过程调用服务。
global里的函数可以用来在分布式系统里注册名称和加锁,以及维护一个全连接网络。
%% rpc最重要的函数,它会在指定节点上执行apply(Mod,Function,Args)
call(Node,Mod,Function,Args) -> Result | {badrpc,Reason}-spec spawn(Node,Fun) -> Pid
-spec spawn(Node,Mod,Func,ArgList) -> Pid-spec spawn_link(Node,Fun) -> Pid
-spec spawn_link(Node,Mod,Func,ArgList) -> Pid%% 强制断开与某节点的连接
-spec disconnect_node(Node) -> bool | ignored-spec monitor_node(Node,Flag) -> true%% Arg不传查找本地节点名称,Arg可以是Pid、引用或者端口
-spec node(Arg) -> Node%% 查询所有与我们连接的节点
-sepc nodes() -> [Node]%% 查询节点是否活着
-spec is_alive() -> bool()
例子:远程分裂
-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} -> Responseend.%% 监听需要调用的本地方法
loop() -> receive{rpc,Pid,M,F,A} ->Pid ! {self(),(catch apply(M,F,A))},end.
%% 开启一个gandalf节点
erl -name gandalf -setCOOKIE abc%% 开启一个bilbo节点
erl -name bilbo -setCOOKIE abc%% 通过bilbo在gandalf开启一个进程,执行指定任务
Pid = dist_demo:start('gandalf@doris.myerl.example.com').
dist_demo:rpc(Pid,erlang,node,[]).
例子:模拟文件传输
Pid = dist_demo:start('gandalf@doris,myerl.example.com').%% 获取当前文件路径
dist_demo:rpc(Pid,file,get_cwd,[]).%% 获取文件目录
dist_demo:rpc(Pid,file,list_dir,["."]).%% 获取文件具体内容
dist_demo:rpc(Pid,file,read_file,["dist_demo.erl"]).
基于套接字的分布式模型
不可信环境中,更安全。
lib_chan 显式控制自己的机器能分裂出哪些进程。
%% 启动一个基于$HOME/.erlang_config/lib_chan.conf的服务器
-spec start_server() -> true
%% 指定配置文件启动服务器
-spec start_server(Config) -> true%% 客户端连接上服务器,然后开始相互发消息
%% ip、端口号、服务器名、密码、传递参数
-spec connect(Host,Port,S,P,ArgsC) -> {ok,Pid} | {error,Why}
Config 配置部分内容
%% 开始监听端口号:NNNN
{port,NNNN} %% S服务器名称、P服务器密码、模块、方法、传递参数(这个方法类似启动类)
{service,S,password,P,mfa,SomeMode,SomeFunc,SomeArgS}
就会通过分裂SomeMod: SomeFunc(MM, ArgsC, SomeArgsS)创建一个进程,负责处理来自客户端的消息。 这里的MM是一个代理进程的PID,可以用来向客户端发送消息。参数ArgsC来自于 客户端的连接调用
例子:
配置
{port,1234}.
{service,nameServer,password,"ABXy45",mfa,mod_name_server,start_me_up,notUserd}.
connect(Host,1234,nameServer,"ABXy45",nil). 服务器会分裂mod_name_server:start_me_up(MM, nil, notUsed)。MM是一个代理进程的PID,用来和客户端通信。
服务器主类
-module(mod_name_server).
-export([start_me_up/3]).start_me_up(MM,_ArgsC,_ArgS) ->loop(MM).loop(MM) ->receive{chan,MM,{store,K,V}} ->kvs:store(K,V),loop(MM);{chan,MM,{lookup,K}} ->MM ! {send,kvs:lookup(K)},loop(MM);{chan_closed,MM} ->trueend.
特点:
- 客户端给服务器发送一条消息{send,X},它会在mod_name_server变成{chan,MM,X}
- 连接关闭,会受到{chan_closed,MM}格式的消息
- 服务器给客户端发送消息MM !{send,X}
- 服务器主动关闭连接 MM !close
执行例子: