
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里面就給出了使用配置文件的路子,只不過他是把這個配置放在專門的配置文件
如果這兩個參數調整了,最好干掉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
參考資料: