Erlang中頻繁傳送遠端訊息要注意的問題

weixin_30924079發表於2020-04-04

http://avindev.iteye.com/blog/76373

注:這篇文章可能會有爭議,歡迎提出意見 

在Erlang中,如果要實現兩個遠端節點之間的通訊,就需要通過網路來實現,對於訊息傳送,是使用TCP。如果要在兩個節點間頻繁傳送訊息,比如每秒幾百上千條,那樣就要注意了。 

無論是網遊伺服器開發的書籍,或是經驗老道的工程師,都會告訴你,在傳送資料包時,儘可能把小的訊息組合為一個比較大的包來傳送,畢竟一個TCP包的頭也很大,首先是浪費頻寬,其次呼叫底層傳送的指令也是有開銷的。有工程師告訴我,一般每秒大概是2W次左右。 

簡單測試一下,先是程式碼 

一個接收訊息並馬上拋棄的Server: 

Java程式碼  收藏程式碼
  1. start() ->  
  2.     register(nullserver, self()),  
  3.     loop().  
  4.   
  5. loop() ->  
  6.     receive  
  7.     Any ->  
  8.         loop() %drop message and loop  
  9.     end.  



一個在迴圈中向它傳送訊息的Client: 

Java程式碼  收藏程式碼
  1. start() ->  
  2.     start_send(100).  
  3.   
  4. start_send(0) ->  
  5.     ok;  
  6. start_send(N) ->  
  7.     {nullserver, 'foo@192.168.0.3'} ! hi,  
  8.     start_send(N-1).  



然後開啟截包工具,執行server和client,擷取到接近200個包的傳送和接收記錄,其中,大部分是這樣的資料: 

引用
00 14 78 B9 14 BC 00 11-11 9F 91 1A 08 00 45 00 
00 45 EE 77 40 00 80 06-80 E4 C0 A8 00 CC DB E8 
ED F9 13 58 C1 C6 AA 4E-59 F2 38 CF 22 2D 50 18 
FF 19 B9 EE 00 00 00 00-00 19 70 83 68 04 61 06 
67 43 CC 00 00 00 01 00-00 00 00 02 43 05 43 BD 
83 43 BF                                     



引用
00 14 78 B9 14 BC 00 11-11 9F 91 1A 08 00 45 00 
00 45 EE 78 40 00 80 06-80 E3 C0 A8 00 CC DB E8 
ED F9 13 58 C1 C6 AA 4E-5A 0F 38 CF 22 2D 50 18 
FF 19 B9 D1 00 00 00 00-00 19 70 83 68 04 61 06 
67 43 CC 00 00 00 01 00-00 00 00 02 43 05 43 BD 
83 43 BF                                      




實際上,只有從 00 00-00 19 這裡開始,才是TCP包的內容,前面都是底層協議的資料,就是這樣的資料包傳送了100次,浪費是巨大的。而且,在訊息傳送後,還收到同樣數目類似 

引用
00 11 11 9F 91 1A 00 14-78 B9 14 BC 08 00 45 00 
00 28 8C FC 40 00 32 06-30 7D DB E8 ED F9 C0 A8 
00 CC C1 C6 13 58 38 CF-22 2D AA 4E 59 F2 50 10 
19 20 D7 01 00 00 00 00-00 00 00 00 

          

這樣的響應包,也浪費著頻寬。 


從目前我所閱讀過的文件來看,暫時沒有有關如何快取這些訊息定期一併傳送的引數設定。那麼有什麼解決辦法,我自己有兩種。 

一種是將要傳送的一批Message打包到一個list傳送,接收方從list中取出所有message並處理。 

另一種是通過一個Proxy,傳送方不通過 {Name, Node} ! Message 這種方式來傳送,而是通過一個本地的Proxy Process,代理會將所有傳送到某個節點的訊息累積起來,定時批量傳送過去;接收方也有一個Listening Process,它接收批量的Message,遍歷後傳送給本地的相應程式。 

這裡是我初步寫出來的實現,不太漂亮,僅供參考~ 

message_agent.erl: 實現訊息的批量傳送,接收和轉發 

Java程式碼  收藏程式碼
  1. -module(message_agent).  
  2. -export([listen/0, proxy/2, block_exit/1]).  
  3. -export([loop_receive/0]).  
  4. -define(MAX_BATCH_MESSAGE_SIZE, 50).  
  5.   
  6. listen() ->  
  7.     io:format("Message agent server start listen~n"),  
  8.     spawn(fun() -> register('MsgServerAgent', self()), loop_receive() end),  
  9.     ok.  
  10.   
  11. loop_receive() ->  
  12.     receive  
  13.     {forward_message, PName, Messages} ->  
  14.         forward_messages(PName, Messages),  
  15.             loop_receive();  
  16.     Any ->  
  17.         message_agent:loop_receive()  
  18.     end.  
  19.   
  20. forward_messages(PName, []) ->  
  21.     ok;  
  22. forward_messages(PName, [H|T]) ->  
  23.     %io:format("Forward message ~w to process ~w~n", [H, PName]),  
  24.     catch PName ! H,  
  25.     forward_messages(PName, T).  
  26.   
  27.   
  28. proxy(Node, PName) ->  
  29.     spawn_link(fun() -> handle_message_forward(Node, PName, []) end).  
  30.   
  31. block_exit(Agent) ->  
  32.     Agent ! {block_wait, self()},  
  33.     receive  
  34.     {unblock} ->  
  35.         ok  
  36.     end.  
  37.   
  38. handle_message_forward(Node, PName, Messages) ->  
  39.     receive  
  40.     {block_wait, Pid} ->  
  41.         catch send_batch(Node, PName, lists:reverse(Messages)),  
  42.         Pid ! {unblock};  
  43.     Any ->  
  44.         NewMessages = [Any|Messages],  
  45.         case length(NewMessages)>=?MAX_BATCH_MESSAGE_SIZE of  
  46.         true ->  
  47.             send_batch(Node, PName, lists:reverse(NewMessages)),  
  48.             handle_message_forward(Node, PName, []);  
  49.         false ->  
  50.             handle_message_forward(Node, PName, NewMessages)  
  51.         end  
  52.     after  
  53.     0 ->  
  54.         case length(Messages)>0 of  
  55.         true ->  
  56.             catch send_batch(Node, PName, lists:reverse(Messages));  
  57.         false ->  
  58.             ok  
  59.         end,  
  60.         handle_message_forward(Node, PName, [])  
  61.     end.  
  62.   
  63. send_batch(Node, PName, Messages) ->  
  64.     %io:format("Send batch message, size ~p~n", [length(Messages)]),  
  65.     {'MsgServerAgent', Node} ! {forward_message, PName, Messages}.  




使用方式很簡單,在接收Message的一端呼叫 message_agent:listen() 啟動監聽代理,客戶端使用 register(agent, message_agent:proxy(?NODE, 'MsgServer')) 的方式啟動代理程式,訊息傳送給這個代理程式就可以了。下面是我寫的簡單例子: 

Java程式碼  收藏程式碼
  1. -module(message_server).  
  2. -export([start/0]).  
  3. -define(TIMEOUT_MS, 1000).  
  4.   
  5. start() ->  
  6.     io:format("Message server start~n"),  
  7.     register('MsgServer', self()),  
  8.     message_agent:listen(),  
  9.     loop_receive(0).  
  10.   
  11. loop_receive(Count) ->  
  12.     receive  
  13.     Any ->  
  14.         %io:format("Receive msg ~w~n", [Any]),  
  15.             loop_receive(Count+1)  
  16.     after  
  17.     ?TIMEOUT_MS ->  
  18.         if   
  19.         Count>0 ->  
  20.             io:format("Previous receive msg count: ~p~n", [Count]),  
  21.             loop_receive(0);  
  22.         true ->  
  23.             loop_receive(0)  
  24.         end  
  25.     end.  



Java程式碼  收藏程式碼
  1. -module(message_client).  
  2. -define(NODE, 'msgsrv@192.168.0.3').  
  3. -define(COUNT, 20000).  
  4. -export([start/0]).  
  5.   
  6. start() ->  
  7.     statistics(wall_clock),  
  8.     register(agent, message_agent:proxy(?NODE, 'MsgServer')),  
  9.     send_loop(?COUNT).  
  10.   
  11. send_loop(0) ->  
  12.     message_agent:block_exit(agent),  
  13.     {_, Interval} = statistics(wall_clock),  
  14.     io:format("Finished ~p sends in ~p ms, exiting...~n", [?COUNT, Interval]);  
  15. send_loop(Count) ->  
  16.     agent ! {self(), lalala},  
  17.     send_loop(Count-1).  



這裡要注意的是,訊息傳送端和接收端都是由一個單獨的程式來處理訊息。在Erlang的預設堆實現,是私有堆,本地程式間的訊息傳送是需要拷貝的,在資料量大的時候,該程式堆的垃圾回收會相當頻繁。

轉載於:https://www.cnblogs.com/fvsfvs123/p/4255374.html

相關文章