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 4369 with data:
name ns_1 at port 21101
name babysitter_of_ns_1 at port 21100
name ligaoren at port 51056
新启动一个节点之后,再看下epmd的情况:
[root@nimbus data2]# erl -name test@nimbus -setcookie 1234
[root@nimbus ~]# epmd -names
epmd: up and running on port 4369 with data:
name test at port 35441
name ns_1 at port 21101
name babysitter_of_ns_1 at port 21100
name ligaoren at port 51056
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进程.
如何让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 4370 inet_dist_listen_max 4375
erl -sname node1 -kernel inet_dist_listen_min 4370 inet_dist_listen_max 4375
erl -sname node2 -kernel inet_dist_listen_min 4370 inet_dist_listen_max 4375
erl -sname node3 -kernel inet_dist_listen_min 4370 inet_dist_listen_max 4375
erl -sname node4 -kernel inet_dist_listen_min 4370 inet_dist_listen_max 4375
erl -sname node5 -kernel inet_dist_listen_min 4370 inet_dist_listen_max 4375
在启动上面节点的时候,我们显示指定了kernel的 inet_dist_listen_min inet_dist_listen_max值,也就是节点可侦听端口的最小值,最大值.上面节点启动成功之后,我们通过epmd -names查看一下端口注册情况
epmd: up and running on port 4369 with data:
name node5 at port 4375
name node4 at port 4374
name node3 at port 4373
name node2 at port 4372
name node1 at port 4371
name abc at port 4370
这时,我们尝试再创建一个节点试一下
erl -sname node6 -kernel inet_dist_listen_min 4370 inet_dist_listen_max 4375
失败了,错误信息节录如下:
{error_logger,{{2014,7,3},{20,51,4}},"Protocol: ~tp: register/listen error: ~tp~
n",["inet_tcp",eaddrinuse]}
{error_logger,{{2014,7,3},{20,51,4}},crash_report,[[{initial_call,{net_kernel,in
it,['Argument__1']}},{pid,<0.20.0>},{registered_name,[]},{error_info,{exit,{erro
r,badarg},[{gen_server,init_it,6,[{file,"gen_server.erl"},{line,320}]},{proc_lib
,init_p_do_apply,3,[{file,"proc_lib.erl"},{line,239}]}]}},{ancestors,[net_sup,ke
rnel_sup,<0.10.0>]},{messages,[]},{links,[<0.17.0>]},{dictionary,[{longnames,fal
se}]},{trap_exit,true},{status,running},{heap_size,610},{stack_size,27},{reducti
ons,1861}],[]]}
是的,启动失败的原因是在epmd注册失败,没有可用的动态端口可以分配给新节点了,所以报出的是地址正在使用的错误.
注:上面参数修改如果是在代码中完成,如下
application:set_env(kernel, inet_dist_listen_min, 9100).
application:set_env(kernel, inet_dist_listen_max, 9105).
这个在 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, 6000},
{inet_dist_listen_max, 7999}
]}
LYSE里面就给出了使用配置文件的路子,只不过他是把这个配置放在专门的配置文件
http://learnyousomeerlang.com/distribunomicon
如果这两个参数调整了,最好干掉epmd,重新启动,之所以这样是因为epmd在所有节点关闭之后还会存在,所以必须重启以便新参数生效.
如何让epmd使用指定端口
默认情况下epmd使用的TCP端口是4369
ERL_EPMD_ADDRESS=127.0.0.1 ERL_EPMD_PORT=8384 epmd -daemon
交互模式下要链接指定的端口可以使用port选项
epmd -port 8384 -names
调试状态看细节
如果启动epmd -d 启动调试,可以看到输出信息;下面的过程,我逐一启动了abc,xyz,test三个节点;然后关闭掉xyz,test节点,从下面的输出信息,可以看到节点注册和注销注册的情况.
[root@Slave4 ~]#
[root@Slave4 ~]# epmd -d
epmd: Thu Jul 3 15:56:15 2014: epmd running - daemon = 0
epmd: Thu Jul 3 15:56:25 2014: ** got ALIVE2_REQ
epmd: Thu Jul 3 15:56:25 2014: registering 'abc:2', port 35383
epmd: Thu Jul 3 15:56:25 2014: type 77 proto 0 highvsn 5 lowvsn 5
epmd: Thu Jul 3 15:56:25 2014: ** sent ALIVE2_RESP for "abc"
epmd: Thu Jul 3 15:56:43 2014: ** got ALIVE2_REQ
epmd: Thu Jul 3 15:56:43 2014: registering 'xyz:2', port 42802
epmd: Thu Jul 3 15:56:43 2014: type 77 proto 0 highvsn 5 lowvsn 5
epmd: Thu Jul 3 15:56:43 2014: ** sent ALIVE2_RESP for "xyz"
epmd: Thu Jul 3 15:57:22 2014: ** got ALIVE2_REQ
epmd: Thu Jul 3 15:57:22 2014: node name already occupied abc
epmd: Thu Jul 3 15:57:22 2014: ** sent ALIVE2_RESP for "abc"
epmd: Thu Jul 3 15:57:22 2014: trying to unregister node with unknown file descriptor 6
epmd: Thu Jul 3 15:57:51 2014: ** got ALIVE2_REQ
epmd: Thu Jul 3 15:57:51 2014: registering 'test:1', port 32781
epmd: Thu Jul 3 15:57:51 2014: type 77 proto 0 highvsn 5 lowvsn 5
epmd: Thu Jul 3 15:57:51 2014: ** sent ALIVE2_RESP for "test"
epmd: Thu Jul 3 15:58:23 2014: ** got PORT2_REQ
epmd: Thu Jul 3 15:58:23 2014: ** sent PORT2_RESP (ok) for "test"
epmd: Thu Jul 3 16:05:26 2014: unregistering 'xyz:2', port 42802
epmd: Thu Jul 3 16:05:35 2014: unregistering 'test:1', port 32781
是不是比较迷惑里面的ALIVE2_REQ之类的是什么意思?这就要认真对照Erlang Distribution Protocol了,对照下面的图,如果有兴趣可以研究下协议,地址: http://www.erlang.org/doc/apps/erts/erl_dist_protocol.html
最后,这里有一个Golang的项目 Eclus-EPMD replacement in Go 有兴趣的可以看下,项目地址: https://github.com/goerlang/eclus