1. 使用说明
官方说法:
This server is used to assist diskless Erlang nodes that fetch all Erlang code from another machine.
简单的说就是帮助那些磁盘较小的机器来从网络启动erlang节点
一般配合erl_prim_loader
使用,boot_server
针对其他的slave
来说,就是本地erl_prim_loader
代理
2. 使用方法
2.1 在master上开启boot_server服务
erl -setcookie ${COOKIE} -name ${NAME}@${IP} -kernel start_boot_server true -kernel boot_server_slaves = ${SLAVE_HOSTS} .....
注意:
-kernel start_boot_server true 如果使用app.config文件,请在app.config文件配置,这个环境变量是必须的,否则不会启动boot_server
SLAVE_HOSTS:其实是可以接入的IP白名单
如果需要动态添加SLAVE_HOST,请调用 erl_boot_server:add_slave/1 方法
example:
erl -setcookie cookie -name boot_server@192.168.36.84 -kernel start_boot_server true -kernel boot_server_slaves = '[{192,168,36,84}]'
2.2 在slave机器上开启erlang节点
erl -setcookie ${COOKIE} -name ${NAME}@${IP} -hosts ${HOST} -loader inet ......
注意:
-loader inet 是必须的,指定从网络来载入模块和文件
-hosts ${HOST} HOST 是 master 节点的IP, 但是要保证 ${IP} 在2.1中的白名单中
example:
erl -setcookie cookie -name slave@192.168.36.84 -hosts "192.168.36.84" -loader inet
节点开启之后就可以正常使用boot_server
上的模块了
3. 代码分析
3.1 boot_server
erl_boot_server.erl
init(Slaves) ->
% 4368 UDP端口打开
{ok, U} = gen_udp:open(?EBOOT_PORT, []),
{ok, L} = gen_tcp:listen(0, [binary,{packet,4}]),
{ok, Port} = inet:port(L),
{ok, UPort} = inet:port(U),
Ref = make_ref(),
% Pid 执行 boot_loop 函数
Pid = proc_lib:spawn_link(?MODULE, boot_init, [Ref]),
ok = gen_tcp:controlling_process(L, Pid),
Pid ! {Ref, L},
%% We trap exit inorder to restart boot_init and udp_port
process_flag(trap_exit, true),
{ok, #state{priority = 0,
version = erlang:system_info(version),
udp_sock = U,
udp_port = UPort,
listen_sock = L,
listen_port = Port,
slaves = ordsets:from_list(Slaves),
bootp = Pid}}.
#主要的请求
boot_loop(Socket, PS) ->
receive
{tcp, Socket, Data} ->
PS2 = handle_command(Socket, PS, Data),
boot_loop(Socket, PS2);
{tcp_closed, Socket} ->
true
end.
handle_command(S, PS, Msg) ->
case catch binary_to_term(Msg) of
{get,File} ->
......
{'EXIT',Reason} ->
send_result(S, {error,Reason}),
PS;
_Other ->
send_result(S, {error,unknown_command}),
PS
end.
## 第一次收 erl_priv_loader的请求
handle_info({udp, U, IP, Port, Data}, S0) ->
Token = ?EBOOT_REQUEST ++ S0#state.version,
Valid = member_address(IP, ordsets:to_list(S0#state.slaves)),
% 判断是否在白名单中(slaves)
case {Valid, Data,Token} of
{true,Token,Token} ->
% 将本地打开的tcp的端口返回给slave,方便下次请求的时候直接通过tcp请求
case gen_udp:send(U,IP,Port,[?EBOOT_REPLY,S0#state.priority,
int16(S0#state.listen_port),
S0#state.version])
.....
{noreply,S0};
{false,_,_} ->
......
{true,_,_} ->
.....
end;
3.2 erl_prim_loader.erl
% erl_prim_loader.erl
% 发现master 的主要逻辑
find_master(...) -> .....
% 最后得到连上master 的socket 放在state中
find_master(U, Retry, AddrL, ReqDelay, SReSleep, Ignore, Tries, LReSleep) ->
case find_loop(U, Retry, AddrL, ReqDelay, SReSleep, Ignore, Tries, LReSleep) of
[] ->
find_master(U, Retry, AddrL, ReqDelay, SReSleep, Ignore, Tries, LReSleep);
Servers ->
% [{Priotity,IP,Port}..]
?dbg(servers, Servers),
case connect_master(Servers) of
{ok, Socket} ->
.....
{ok, Socket};
.....
end
end..
send_all(U, [IP | AL], Cmd) ->
?dbg(sendto, {U, IP, ?EBOOT_PORT, Cmd}),
% 向 MasterIP:4368 UDP发送一个请求
prim_inet:sendto(U, IP, ?EBOOT_PORT, Cmd),
send_all(U, AL, Cmd);
send_all(_U, [], _) -> ok.
% 主循环,处理例如get_file等请求
loop(St0, Parent, Paths) ->
receive
{Pid, {set_path, NewPaths}} when is_pid(Pid) ->
Pid ! {self(), ok},
loop(St0, Parent, to_strs(NewPaths));
{Pid, Req} when is_pid(Pid) ->
case handle_request(Req, Paths, St0) of
ignore ->
ok;
{Resp, #state{} = St1} ->
Pid ! {self(), Resp},
loop(St1, Parent, Paths);
{_, State2, _} ->
exit({bad_state, Req, State2})
end;
......
after St0#state.timeout ->
St1 = handle_timeout(St0, Parent),
loop(St1, Parent, Paths)
end.
% 处理请求的入口
handle_request(Req, Paths, St0) ->
case Req of
{get_path, _} ->
{{ok, Paths}, St0};
{get_file, File} ->
handle_get_file(St0, Paths, File);
.....
_ ->
ignore
end.
对此整个链路分析完成
4. 优缺点分析
4.1 优点
- 可以从远程加载模块和文件
- 为了那些硬盘紧缺的机器提供了一种加载方式
- 为运维提供一种
erlang shell
的运维思路
4.2 缺点
-
master
节点和slave
节点不能有防火墙,当然这个在内网中很容易实现(为什么不通过cookie?) -
master
,slave
使用的erts
版本要保持一致
5. 总结
本文简要的讲述了如何从远程加载模块文件,分析了实现方式以及优缺点。
虽然在生产环境中,我们从来没有尝试过,但是还是推荐一下。