Erlang中频繁发送远程消息要注意的问题
http://avindev.iteye.com/blog/76373
注:这篇文章可能会有争议,欢迎提出意见
在Erlang中,如果要实现两个远程节点之间的通信,就需要通过网络来实现,对于消息发送,是使用TCP。如果要在两个节点间频繁发送消息,比如每秒几百上千条,那样就要注意了。
无论是网游服务器开发的书籍,或是经验老道的工程师,都会告诉你,在发送数据包时,尽可能把小的消息组合为一个比较大的包来发送,毕竟一个TCP包的头也很大,首先是浪费带宽,其次调用底层发送的指令也是有开销的。有工程师告诉我,一般每秒大概是2W次左右。
简单测试一下,先是代码
一个接收消息并马上抛弃的Server:
- start() ->
- register(nullserver, self()),
- loop().
- loop() ->
- receive
- Any ->
- loop() %drop message and loop
- end.
一个在循环中向它发送消息的Client:
- start() ->
- start_send(100).
- start_send(0) ->
- ok;
- start_send(N) ->
- {nullserver, 'foo@192.168.0.3'} ! hi,
- start_send(N-1).
然后打开截包工具,运行server和client,截取到接近200个包的发送和接收记录,其中,大部分是这样的数据:
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 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 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: 实现消息的批量发送,接收和转发
- -module(message_agent).
- -export([listen/0, proxy/2, block_exit/1]).
- -export([loop_receive/0]).
- -define(MAX_BATCH_MESSAGE_SIZE, 50).
- listen() ->
- io:format("Message agent server start listen~n"),
- spawn(fun() -> register('MsgServerAgent', self()), loop_receive() end),
- ok.
- loop_receive() ->
- receive
- {forward_message, PName, Messages} ->
- forward_messages(PName, Messages),
- loop_receive();
- Any ->
- message_agent:loop_receive()
- end.
- forward_messages(PName, []) ->
- ok;
- forward_messages(PName, [H|T]) ->
- %io:format("Forward message ~w to process ~w~n", [H, PName]),
- catch PName ! H,
- forward_messages(PName, T).
- proxy(Node, PName) ->
- spawn_link(fun() -> handle_message_forward(Node, PName, []) end).
- block_exit(Agent) ->
- Agent ! {block_wait, self()},
- receive
- {unblock} ->
- ok
- end.
- handle_message_forward(Node, PName, Messages) ->
- receive
- {block_wait, Pid} ->
- catch send_batch(Node, PName, lists:reverse(Messages)),
- Pid ! {unblock};
- Any ->
- NewMessages = [Any|Messages],
- case length(NewMessages)>=?MAX_BATCH_MESSAGE_SIZE of
- true ->
- send_batch(Node, PName, lists:reverse(NewMessages)),
- handle_message_forward(Node, PName, []);
- false ->
- handle_message_forward(Node, PName, NewMessages)
- end
- after
- 0 ->
- case length(Messages)>0 of
- true ->
- catch send_batch(Node, PName, lists:reverse(Messages));
- false ->
- ok
- end,
- handle_message_forward(Node, PName, [])
- end.
- send_batch(Node, PName, Messages) ->
- %io:format("Send batch message, size ~p~n", [length(Messages)]),
- {'MsgServerAgent', Node} ! {forward_message, PName, Messages}.
使用方式很简单,在接收Message的一端调用 message_agent:listen() 启动监听代理,客户端使用 register(agent, message_agent:proxy(?NODE, 'MsgServer')) 的方式启动代理进程,消息发送给这个代理进程就可以了。下面是我写的简单例子:
- -module(message_server).
- -export([start/0]).
- -define(TIMEOUT_MS, 1000).
- start() ->
- io:format("Message server start~n"),
- register('MsgServer', self()),
- message_agent:listen(),
- loop_receive(0).
- loop_receive(Count) ->
- receive
- Any ->
- %io:format("Receive msg ~w~n", [Any]),
- loop_receive(Count+1)
- after
- ?TIMEOUT_MS ->
- if
- Count>0 ->
- io:format("Previous receive msg count: ~p~n", [Count]),
- loop_receive(0);
- true ->
- loop_receive(0)
- end
- end.
- -module(message_client).
- -define(NODE, 'msgsrv@192.168.0.3').
- -define(COUNT, 20000).
- -export([start/0]).
- start() ->
- statistics(wall_clock),
- register(agent, message_agent:proxy(?NODE, 'MsgServer')),
- send_loop(?COUNT).
- send_loop(0) ->
- message_agent:block_exit(agent),
- {_, Interval} = statistics(wall_clock),
- io:format("Finished ~p sends in ~p ms, exiting...~n", [?COUNT, Interval]);
- send_loop(Count) ->
- agent ! {self(), lalala},
- send_loop(Count-1).
这里要注意的是,消息发送端和接收端都是由一个单独的进程来处理消息。在Erlang的默认堆实现,是私有堆,本地进程间的消息发送是需要拷贝的,在数据量大的时候,该进程堆的垃圾回收会相当频繁。
Erlang中频繁发送远程消息要注意的问题的更多相关文章
- 通过HTTP协议发送远程消息
通过HTTP协议发送远程消息 MSMQ一般情况是通过tcp协议进行通讯,但如果遇到端口被禁用或防火墙,则通过HTTP协议发送消息是一个有效的解决办法. 通过HTTP协议发送消息到远程服务器 publi ...
- 微信小程序中发送模版消息注意事项
在微信小程序中发送模版消息 参考微信公众平台Api文档地址:https://mp.weixin.qq.com/debug/wxadoc/dev/api/notice.html#模版消息管理 此参考地址 ...
- 在C#程序中模拟发送键盘按键消息
using System.Runtime.InteropServices; 引入键盘事件函数 [DllImport("user32.dll")]public static exte ...
- 解决c#所有单线程单元(STA)线程都应使用泵式等待基元(如 CoWaitForMultipleHandles),并在运行时间很长的操作过程中定期发送消息。 转载
最近做一个后来程序,启动了事务后有一段操作业务,当运行一段时间后,出现这个异常 CLR 无法从 COM 上下文 0x1b1c38 转换为 COM 上下文 0x1b1da8,这种状态已持续 60 秒.拥 ...
- 【原创】在 ASP.NET Core 3.1 中使用 Senparc.Weixin.Work 企业微信 SDK —— 发送文本消息
下面在 Web 空应用里展示一个简单的例子来实现发送文本消息. 本文目录: 创建 Web 空应用 命令行方式创建 添加SDK引用 命令行方式 进入项目目录 添加包引用 配置和使用SDK 添加appse ...
- 【原创】在 .NET Core 3.1 中使用 Senparc.Weixin.Work 企业微信 SDK —— 发送文本消息
下面在控制台应用里展示一个简单的例子来实现发送文本消息. 本文目录: 创建控制台应用 添加SDK引用 命令行方式 进入项目目录 添加包引用 配置和使用SDK 添加appsettings.json文件 ...
- Canal Server发送binlog消息到Kafka消息队列中
Canal Server发送binlog消息到Kafka消息队列中 一.背景 二.需要修改的地方 1.canal.properties 配置文件修改 1.修改canal.serverMode的值 2. ...
- [Erlang 0106] Erlang实现Apple Push Notifications消息推送
我们的IOS移动应用要实现消息推送,告诉用户有多少条消息未读,类似下图的效果(笑果),特把APNS和Erlang相关解决方案笔记于此备忘. 上面图片中是Apple Notif ...
- iOS开发笔记8:Remote Notification远程消息推送处理
远程消息推送处理场景有三种:分别是app还没有运行.app在前台运行以及app在后台运行,下面介绍相关流程及三种场景下处理步骤 1.流程 (1)注册通知 首先是在注册远程消息推送,需要注意的是iOS8 ...
随机推荐
- elasticsearch cluster 概述
在源码概述中我们分析过,elasticsearch源码从功能上可以分为分布式功能和数据功能,接下来这几篇会就分布式功能展开.这里首先会对cluster作简单概述,然后对cluster所涉及的主要功能详 ...
- 1.1 Introduction中 Producers官网剖析(博主推荐)
不多说,直接上干货! 一切来源于官网 http://kafka.apache.org/documentation/ Producers 生产者(Producers) Producers publish ...
- JQuery的index()函数
1.index(),这里的索引从0开始计数. jQueryObject.index( [ object ] ):1.1 如果没有指定参数object,则返回当前元素在其所有同辈元素中的索引位置.1.2 ...
- win10系统64位安装git后右键运行git bash here生成一个mintty.exe.stackdump文件后闪退解决方案
在其他win10电脑上复制了一个null.sys文件,替换C:\Windows\System32\drivers\null.sys,搞定.
- 原 HttpClient 4.3超时设置
https://my.oschina.net/u/577453/blog/173724 http://blog.csdn.net/zh521zh/article/details/51994140
- Mahjong tree (hdu 5379 dfs)
Mahjong tree Time Limit: 6000/3000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others) Tot ...
- 洛谷——P1011 车站
https://www.luogu.org/problem/show?pid=1011#sub 题目描述 火车从始发站(称为第1站)开出,在始发站上车的人数为a,然后到达第2站,在第2站有人上.下车, ...
- P2P网贷第三方托管模式存在5大缺陷,托管机构才是最大赢家
1.注册开户需要2次,用户体验很差劲儿. 理财人和借款人,首先在平台注册,然后还要在第三方托管账户注册. 很多类似的地方,用户体验非常差劲. 比如,密码4个. 平台:登录密码.交易密码 ...
- java导出word直接下载
导出word工具类 package util; import java.io.IOException; import java.io.Writer; import java.util.Map; imp ...
- Ubuntu UEFI 模式下安装基本原则
https://help.ubuntu.com/community/UEFI Introduction The Extensible Firmware Interface (EFI) or its v ...