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

注:这篇文章可能会有争议,欢迎提出意见

在Erlang中,如果要实现两个远程节点之间的通信,就需要通过网络来实现,对于消息发送,是使用TCP。如果要在两个节点间频繁发送消息,比如每秒几百上千条,那样就要注意了。

无论是网游服务器开发的书籍,或是经验老道的工程师,都会告诉你,在发送数据包时,尽可能把小的消息组合为一个比较大的包来发送,毕竟一个TCP包的头也很大,首先是浪费带宽,其次调用底层发送的指令也是有开销的。有工程师告诉我,一般每秒大概是2W次左右。

简单测试一下,先是代码

一个接收消息并马上抛弃的Server:

  1. start() ->
  2. register(nullserver, self()),
  3. loop().
  4. loop() ->
  5. receive
  6. Any ->
  7. loop() %drop message and loop
  8. end.

一个在循环中向它发送消息的Client:

  1. start() ->
  2. start_send(100).
  3. start_send(0) ->
  4. ok;
  5. start_send(N) ->
  6. {nullserver, 'foo@192.168.0.3'} ! hi,
  7. 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: 实现消息的批量发送,接收和转发

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

使用方式很简单,在接收Message的一端调用 message_agent:listen() 启动监听代理,客户端使用 register(agent, message_agent:proxy(?NODE, 'MsgServer')) 的方式启动代理进程,消息发送给这个代理进程就可以了。下面是我写的简单例子:

  1. -module(message_server).
  2. -export([start/0]).
  3. -define(TIMEOUT_MS, 1000).
  4. start() ->
  5. io:format("Message server start~n"),
  6. register('MsgServer', self()),
  7. message_agent:listen(),
  8. loop_receive(0).
  9. loop_receive(Count) ->
  10. receive
  11. Any ->
  12. %io:format("Receive msg ~w~n", [Any]),
  13. loop_receive(Count+1)
  14. after
  15. ?TIMEOUT_MS ->
  16. if
  17. Count>0 ->
  18. io:format("Previous receive msg count: ~p~n", [Count]),
  19. loop_receive(0);
  20. true ->
  21. loop_receive(0)
  22. end
  23. end.
  1. -module(message_client).
  2. -define(NODE, 'msgsrv@192.168.0.3').
  3. -define(COUNT, 20000).
  4. -export([start/0]).
  5. start() ->
  6. statistics(wall_clock),
  7. register(agent, message_agent:proxy(?NODE, 'MsgServer')),
  8. send_loop(?COUNT).
  9. send_loop(0) ->
  10. message_agent:block_exit(agent),
  11. {_, Interval} = statistics(wall_clock),
  12. io:format("Finished ~p sends in ~p ms, exiting...~n", [?COUNT, Interval]);
  13. send_loop(Count) ->
  14. agent ! {self(), lalala},
  15. send_loop(Count-1).

这里要注意的是,消息发送端和接收端都是由一个单独的进程来处理消息。在Erlang的默认堆实现,是私有堆,本地进程间的消息发送是需要拷贝的,在数据量大的时候,该进程堆的垃圾回收会相当频繁。

Erlang中频繁发送远程消息要注意的问题的更多相关文章

  1. 通过HTTP协议发送远程消息

    通过HTTP协议发送远程消息 MSMQ一般情况是通过tcp协议进行通讯,但如果遇到端口被禁用或防火墙,则通过HTTP协议发送消息是一个有效的解决办法. 通过HTTP协议发送消息到远程服务器 publi ...

  2. 微信小程序中发送模版消息注意事项

    在微信小程序中发送模版消息 参考微信公众平台Api文档地址:https://mp.weixin.qq.com/debug/wxadoc/dev/api/notice.html#模版消息管理 此参考地址 ...

  3. 在C#程序中模拟发送键盘按键消息

    using System.Runtime.InteropServices; 引入键盘事件函数 [DllImport("user32.dll")]public static exte ...

  4. 解决c#所有单线程单元(STA)线程都应使用泵式等待基元(如 CoWaitForMultipleHandles),并在运行时间很长的操作过程中定期发送消息。 转载

    最近做一个后来程序,启动了事务后有一段操作业务,当运行一段时间后,出现这个异常 CLR 无法从 COM 上下文 0x1b1c38 转换为 COM 上下文 0x1b1da8,这种状态已持续 60 秒.拥 ...

  5. 【原创】在 ASP.NET Core 3.1 中使用 Senparc.Weixin.Work 企业微信 SDK —— 发送文本消息

    下面在 Web 空应用里展示一个简单的例子来实现发送文本消息. 本文目录: 创建 Web 空应用 命令行方式创建 添加SDK引用 命令行方式 进入项目目录 添加包引用 配置和使用SDK 添加appse ...

  6. 【原创】在 .NET Core 3.1 中使用 Senparc.Weixin.Work 企业微信 SDK —— 发送文本消息

    下面在控制台应用里展示一个简单的例子来实现发送文本消息. 本文目录: 创建控制台应用 添加SDK引用 命令行方式 进入项目目录 添加包引用 配置和使用SDK 添加appsettings.json文件 ...

  7. Canal Server发送binlog消息到Kafka消息队列中

    Canal Server发送binlog消息到Kafka消息队列中 一.背景 二.需要修改的地方 1.canal.properties 配置文件修改 1.修改canal.serverMode的值 2. ...

  8. [Erlang 0106] Erlang实现Apple Push Notifications消息推送

        我们的IOS移动应用要实现消息推送,告诉用户有多少条消息未读,类似下图的效果(笑果),特把APNS和Erlang相关解决方案笔记于此备忘.          上面图片中是Apple Notif ...

  9. iOS开发笔记8:Remote Notification远程消息推送处理

    远程消息推送处理场景有三种:分别是app还没有运行.app在前台运行以及app在后台运行,下面介绍相关流程及三种场景下处理步骤 1.流程 (1)注册通知 首先是在注册远程消息推送,需要注意的是iOS8 ...

随机推荐

  1. 对于学习apache软件基金会顶级项目源码的一点思路(转)

    ASF的开源项目,为软件行业贡献了太多好的产品和软件思维.学习ASF的项目源码能很大的提升自身的能力.程序运行在服务器上的流程:执行启动脚本(start.sh) -> 指向程序的主方法 -> ...

  2. js进阶 14-2 如何用ajax验证登陆状态(这里用load方法)

    js进阶 14-2 如何用ajax验证登陆状态(这里用load方法) 一.总结 一句话总结:$('#test').load('test.php?password=1234560'),这样就get方式提 ...

  3. amazeui学习笔记--css(常用组件13)--进度条Progress

    amazeui学习笔记--css(常用组件13)--进度条Progress 一.总结 1.进度条基本使用:进度条组件,.am-progress 为容器,.am-progress-bar 为进度显示信息 ...

  4. Altium Designer中Electrical Type的意思

    :之前Altium Designer设计图时发现: 它的引脚上有两个三角 双击打开引脚,打开配置: 于是从网上查了一下:http://blog.csdn.net/jbb0523/article/det ...

  5. POJ 2386 Lake Counting DFS水水

    http://poj.org/problem?id=2386 题目大意: 有一个大小为N*M的园子,雨后积起了水.八连通的积水被认为是连接在一起的.请求出院子里共有多少水洼? 思路: 水题~直接DFS ...

  6. postman和fiddler的基本使用

    本文转自:https://www.cnblogs.com/qq909283/p/6826578.html 写在前面:本文主要的章节规划: 1.什么是接口测试 另外,有的时候会直接调用别的公司的接口,比 ...

  7. boost 库编译选项

    boost大部分库仅仅须要包括头文件就可以使用,而有部分须要编译的.例如以下: E:\Qt\Qt3rdlib\boost_1_58_0>bjam --show-libraries The fol ...

  8. ElasticSearch 2.4

    ES是一个基于Lucene的分布式全文搜索服务器,和SQL Server的全文索引(Fulltext Index)有点类似,都是基于分词和分段的全文搜索引擎,具有分词,同义词,词干查询的功能,但是ES ...

  9. 大战C100K之-Linux内核调优篇--转载

    原文地址:http://joyexpr.com/2013/11/22/c100k-4-kernel-tuning/ 早期的系统,系统资源包括CPU.内存等都是非常有限的,系统为了保持公平,默认要限制进 ...

  10. 【BZOJ 2754】[SCOI2012]喵星球上的点名

    [链接]h在这里写链接 [题意]     n个人;     由姓和名组成.s1[i]和s2[i];     有m个询问串.     问你第j个询问串,是否为某个人的姓或者名的子串.     如果是的话 ...