[Erlang 0123] Erlang EPMD

时间:2022-02-19 20:39:04
[Erlang 0123] Erlang EPMD epmd进程和Erlang节点进程如影随形,在Rabbitmq集群,Ejabberd集群,Couchbase集群产品文档中都会有相当多的内容讲epmd,epmd是什么呢?
  epmd 是Erlang Port Mapper Daemon的缩写,全称足够明确表达它的功能了(相比之下,OTP就是一个难以从字面理解的名字);epmd完成Erlang节点和IP,端口的映射关系,比如在我的测试机上,
 
[root@nimbus data2]# epmd -names
epmd: up and running on port with data:
name ns_1 at port
name babysitter_of_ns_1 at port
name ligaoren at port
新启动一个节点之后,再看下epmd的情况:
 
[root@nimbus data2]# erl -name test@nimbus -setcookie
[root@nimbus ~]# epmd -names
epmd: up and running on port with data:
name test at port
name ns_1 at port
name babysitter_of_ns_1 at port
name ligaoren at port
epmd什么时候启动?
 
     文档里面说的是" if the node is to be distributed ",其实从实际操作的角度看,只要启动时候启动选项包含-name 或者-sname就会自动启动epmd;如果由于意外关闭了epmd进程,可以通过/usr/local/lib/erlang/erts-6.0/bin/epmd -daemon 启动epmd(注意版本不同路径也会不同).下面我们分别通过erl -sname tt 和 erl 启动两个节点,通过observer看下两种方式启动之后的应用程序结构,比较一下可以发现,前者多启动了net_kernel和erl_epmd进程.
 
 [Erlang 0123] Erlang EPMD[Erlang 0123] Erlang EPMD
 
如何让epmd只侦听指定的IP
 
要实现这个目标,有两种方式,1.使用环境变量
 
ERL_EPMD_ADDRESS=127.0.0.1 epmd -daemon
或者使用启动参数
 
epmd -address IPList 
 
或者
 
erl ... -kernel inet_dist_use_interface "{127,0,0,1}" 
 
如何指定Erlang节点互联的动态端口范围
 
  从上面的简单实验可以看到每个分布式节点启动之后,都会在epmd一个动态的端口用来节点间通信.在实际的环境中,我们不可能在防火墙里面把所有的端口都开放出来,那么怎么限制Erlang节点使用的端口范围呢?答案就是 inet_dist_listen_min inet_dist_listen_max 选项
 
erl -sname abc  -kernel inet_dist_listen_min  inet_dist_listen_max
erl -sname node1 -kernel inet_dist_listen_min inet_dist_listen_max
erl -sname node2 -kernel inet_dist_listen_min inet_dist_listen_max
erl -sname node3 -kernel inet_dist_listen_min inet_dist_listen_max
erl -sname node4 -kernel inet_dist_listen_min inet_dist_listen_max
erl -sname node5 -kernel inet_dist_listen_min inet_dist_listen_max
在启动上面节点的时候,我们显示指定了kernel的 inet_dist_listen_min inet_dist_listen_max值,也就是节点可侦听端口的最小值,最大值.上面节点启动成功之后,我们通过epmd -names查看一下端口注册情况
 
epmd: up and running on port  with data:
name node5 at port
name node4 at port
name node3 at port
name node2 at port
name node1 at port
name abc at port
这时,我们尝试再创建一个节点试一下
erl -sname node6  -kernel inet_dist_listen_min  inet_dist_listen_max 
失败了,错误信息节录如下:
 
{error_logger,{{,,},{,,}},"Protocol: ~tp: register/listen error: ~tp~
n",["inet_tcp",eaddrinuse]}
{error_logger,{{,,},{,,}},crash_report,[[{initial_call,{net_kernel,in
it,['Argument__1']}},{pid,<0.20.>},{registered_name,[]},{error_info,{exit,{erro
r,badarg},[{gen_server,init_it,,[{file,"gen_server.erl"},{line,}]},{proc_lib
,init_p_do_apply,,[{file,"proc_lib.erl"},{line,}]}]}},{ancestors,[net_sup,ke
rnel_sup,<0.10.>]},{messages,[]},{links,[<0.17.>]},{dictionary,[{longnames,fal
se}]},{trap_exit,true},{status,running},{heap_size,},{stack_size,},{reducti
ons,}],[]]}
 
是的,启动失败的原因是在epmd注册失败,没有可用的动态端口可以分配给新节点了,所以报出的是地址正在使用的错误.
 
注:上面参数修改如果是在代码中完成,如下
 application:set_env(kernel, inet_dist_listen_min, ).
application:set_env(kernel, inet_dist_listen_max, ).
  这个在 Erlang FAQ中有提到 http://www.erlang.org/faq/how_do_i.html 5.18  ...run distributed Erlang through a firewall?
 
如果是配置在Confige文件中,配置节为:
{ kernel, [
{inet_dist_listen_min, },
{inet_dist_listen_max, }
]}
 
LYSE里面就给出了使用配置文件的路子,只不过他是把这个配置放在专门的配置文件
如果这两个参数调整了,最好干掉epmd,重新启动,之所以这样是因为epmd在所有节点关闭之后还会存在,所以必须重启以便新参数生效.
 
如何让epmd使用指定端口
默认情况下epmd使用的TCP端口是4369
ERL_EPMD_ADDRESS=127.0.0.1 ERL_EPMD_PORT= epmd -daemon 
交互模式下要链接指定的端口可以使用port选项
 epmd  -port  -names

调试状态看细节

如果启动epmd -d 启动调试,可以看到输出信息;下面的过程,我逐一启动了abc,xyz,test三个节点;然后关闭掉xyz,test节点,从下面的输出信息,可以看到节点注册和注销注册的情况.
 
[root@Slave4 ~]#
[root@Slave4 ~]# epmd -d
epmd: Thu Jul :: : epmd running - daemon =
epmd: Thu Jul :: : ** got ALIVE2_REQ
epmd: Thu Jul :: : registering 'abc:2', port
epmd: Thu Jul :: : type proto highvsn lowvsn
epmd: Thu Jul :: : ** sent ALIVE2_RESP for "abc"
epmd: Thu Jul :: : ** got ALIVE2_REQ
epmd: Thu Jul :: : registering 'xyz:2', port
epmd: Thu Jul :: : type proto highvsn lowvsn
epmd: Thu Jul :: : ** sent ALIVE2_RESP for "xyz"
epmd: Thu Jul :: : ** got ALIVE2_REQ
epmd: Thu Jul :: : node name already occupied abc
epmd: Thu Jul :: : ** sent ALIVE2_RESP for "abc"
epmd: Thu Jul :: : trying to unregister node with unknown file descriptor
epmd: Thu Jul :: : ** got ALIVE2_REQ
epmd: Thu Jul :: : registering 'test:1', port
epmd: Thu Jul :: : type proto highvsn lowvsn
epmd: Thu Jul :: : ** sent ALIVE2_RESP for "test"
epmd: Thu Jul :: : ** got PORT2_REQ
epmd: Thu Jul :: : ** sent PORT2_RESP (ok) for "test"
epmd: Thu Jul :: : unregistering 'xyz:2', port
epmd: Thu Jul :: : unregistering 'test:1', port
 是不是比较迷惑里面的ALIVE2_REQ之类的是什么意思?这就要认真对照Erlang Distribution Protocol了,对照下面的图,如果有兴趣可以研究下协议,地址: http://www.erlang.org/doc/apps/erts/erl_dist_protocol.html  
 
[Erlang 0123] Erlang EPMD
 
 
最后,这里有一个Golang的项目 Eclus-EPMD replacement in Go 有兴趣的可以看下,项目地址: https://github.com/goerlang/eclus  
 
参考资料: