- 浏览: 36118 次
- 性别:
- 来自: 秦皇岛
最近访客 更多访客>>
最新评论
-
melin:
每次添加jar,如果某个文件夹下面文件比较多,就慢的的要命、
Netbeans 6.5 完美字体设置 -
cnboss:
乱码乱得是一踏糊涂。
只要一改成courier new字体, ...
Netbeans 6.5 完美字体设置 -
cryolite:
端口8000
用Mochiweb打造百万级Comet应用,第二部分 -
cryolite:
那个恐怖的脚本错了几个字符:MOCHIPID=`pgrep - ...
用Mochiweb打造百万级Comet应用,第二部分 -
phenom:
dejavu sans mono显示英文不错的,mocano也 ...
Netbeans 6.5 完美字体设置
提示:如有转载请注明作者 小游戏 及出处
原文:A Million-user Comet Application with Mochiweb, Part 2
参考资料:Comet--基于 HTTP 长连接、无须在浏览器端安装插件的“服务器推”技术为“Comet”
MochiWeb--建立轻量级HTTP服务器的Erlang库
在第一部分 , 我们构建了一个每10秒向客户端发送一条消息的mochiweb comet应用(没什么用处)。我们微调了一下linux内核,做了一个能够建立大量网络连接以测试应用性能和所耗内存的工具 。我们发现每个连接花费大约45K内存。
本系列的第二部分讲的主要是把应用变得更加有用,更加节省内存:
- 用一个login/logout/send API实现一个消息路由器
- 更新mochiweb应用使之能够从路由器接收消息
- 建立一个分布式erlang系统,这样我们可以在不同的节点和主机上运行路由器
- 写一个能给路由器发送大量无用信息的工具
- 超过24小时的内存用量图,优化mochiweb应用以节约内存
这就意味着我们需要把消息发送逻辑从mochiweb应用中剥离出来。利用第一部分的压力测试工具,我们可以建立一个更接近产品级别的基准测试。
实现消息路由器
路由器的API只有3个函数:
login(Id, Pid)
为Id注册一个接收消息的进程(Pid
)logout(Pid)
停止接受消息send(Id, Msg)向已登录客户端(Id)发送消息(Msg)
注意,从设计上来说,多个不同的用户登陆到同一个进程是有可能的。
这个实例路由器模块用了2个ets表存储Pids和Ids的双向映射(pid2id和id2pid在下面的#state记录定义中)。
router.erl:
-
-module ( router) .
-
-behaviour ( gen_server) .
-
-
-export ( [ start_link /0 ] ) .
-
-export ( [ init/1 , handle_call/3 , handle_cast/2 , handle_info/2 ,
-
terminate/2 , code_change/3 ] ) .
-
-
-export ( [ send/2 , login/2 , logout/1 ] ) .
-
-
-define( SERVER , global:whereis_name ( ?MODULE ) ) .
-
-
% will hold bidirectional mapping between id <–> pid
-
-record( state, { pid2id, id2pid} ) .
-
-
start_link ( ) ->
-
gen_server :start_link ( { global, ?MODULE } , ?MODULE , [ ] , [ ] ) .
-
-
% sends Msg to anyone logged in as Id
-
send( Id , Msg ) ->
-
gen_server :call ( ?SERVER , { send, Id , Msg } ) .
-
-
login( Id , Pid ) when is_pid( Pid ) ->
-
gen_server :call ( ?SERVER , { login, Id , Pid } ) .
-
-
logout( Pid ) when is_pid( Pid ) ->
-
gen_server :call ( ?SERVER , { logout, Pid } ) .
-
-
%%
-
-
init( [ ] ) ->
-
% set this so we can catch death of logged in pids:
-
process_flag( trap_exit, true) ,
-
% use ets for routing tables
-
{ ok, #state{
-
pid2id = ets:new ( ?MODULE , [ bag] ) ,
-
id2pid = ets:new ( ?MODULE , [ bag] )
-
}
-
} .
-
-
handle_call( { login, Id , Pid } , _From , State ) when is_pid( Pid ) ->
-
ets :insert ( State #state.pid2id, { Pid , Id } ) ,
-
ets:insert ( State #state.id2pid, { Id , Pid } ) ,
-
link( Pid ) , % tell us if they exit, so we can log them out
-
io:format ( "~w logged in as ~w\n " ,[ Pid , Id ] ) ,
-
{ reply, ok, State } ;
-
-
handle_call( { logout, Pid } , _From , State ) when is_pid( Pid ) ->
-
unlink ( Pid ) ,
-
PidRows = ets:lookup ( State #state.pid2id, Pid ) ,
-
case PidRows of
-
[ ] ->
-
ok ;
-
_ ->
-
IdRows = [ { I ,P } || { P ,I } <- PidRows ] , % invert tuples
-
% delete all pid->id entries
-
ets:delete ( State #state.pid2id, Pid ) ,
-
% and all id->pid
-
[ ets:delete_object ( State #state.id2pid, Obj ) || Obj <- IdRows ]
-
end ,
-
io:format ( "pid ~w logged out\n " ,[ Pid ] ) ,
-
{ reply, ok, State } ;
-
-
handle_call( { send, Id , Msg } , _From , State ) ->
-
% get pids who are logged in as this Id
-
Pids = [ P || { _Id , P } <- ets:lookup ( State #state.id2pid, Id ) ] ,
-
% send Msg to them all
-
M = { router_msg, Msg } ,
-
[ Pid ! M || Pid <- Pids ] ,
-
{ reply, ok, State } .
-
-
% handle death and cleanup of logged in processes
-
handle_info( Info , State ) ->
-
case Info of
-
{ ‘EXIT’ , Pid , _Why } ->
-
% force logout:
-
handle_call( { logout, Pid } , blah, State ) ;
-
Wtf ->
-
io :format ( "Caught unhandled message: ~w\n " , [ Wtf ] )
-
end ,
-
{ noreply, State } .
-
-
handle_cast( _Msg , State ) ->
-
{ noreply, State } .
-
terminate( _Reason , _State ) ->
-
ok .
-
code_change( _OldVsn , State , _Extra ) ->
-
{ ok, State } .
更新mochiweb应用
让我们假设用户是由基于连入mochieweb的URl中的Id号所描述的,我们用那个id向消息路由器注册。 取代阻塞10秒后发送消息,mochiweb的loop循环将组塞在从路由器接收消息上,路由器给mochiweb进程发送消息后mochiweb进程就会向客户端发送Http数据块:
- 客户端从http://localhost:8000/test/123连接mochiweb
- Mochiweb应用为id为123的用户的那个连接向消息路由器注册进程(pid)
- 假如你用id为123向路由器发送一条消息,他将转发到正确的mochiweb进程,继而消息会出现在那个用户的浏览器上
这是mochiconntest_web.erl的更新版本:
-
-module ( mochiconntest_web) .
-
-
-export ( [ start/1 , stop/0 , loop/2 ] ) .
-
-
%% External API
-
-
start( Options ) ->
-
{ DocRoot , Options1 } = get_option( docroot, Options ) ,
-
Loop = fun ( Req ) ->
-
?MODULE :loop ( Req , DocRoot )
-
end ,
-
% we’ll set our maximum to 1 million connections. (default: 2048)
-
mochiweb_http:start ( [ { max, 1000000 } , { name, ?MODULE } , { loop, Loop } | Options1 ] ) .
-
-
stop( ) ->
-
mochiweb_http :stop ( ?MODULE ) .
-
-
loop( Req , DocRoot ) ->
-
"/" ++ Path = Req :get ( path) ,
-
case Req :get ( method) of
-
Method when Method =:= ‘GET’ ; Method =:= ‘HEAD’ ->
-
case Path of
-
"test/" ++ Id ->
-
Response = Req :ok ( { "text/html; charset=utf-8" ,
-
[ { "Server" ,"Mochiweb-Test" } ] ,
-
chunked} ) ,
-
% login using an integer rather than a string
-
{ IdInt , _} = string:to_integer ( Id ) ,
-
router:login ( IdInt , self( ) ) ,
-
feed( Response , IdInt , 1 ) ;
-
_ ->
-
Req :not_found ( )
-
end ;
-
‘POST’ ->
-
case Path of
-
_ ->
-
Req :not_found ( )
-
end ;
-
_ ->
-
Req :respond ( { 501 , [ ] , [ ] } )
-
end .
-
-
feed( Response , Id , N ) ->
-
receive
-
{ router_msg, Msg } ->
-
Html = io_lib:format ( "Recvd msg #~w: ‘~s’" , [ N , Msg ] ) ,
-
Response :write_chunk ( Html )
-
end ,
-
feed( Response , Id , N +1 ) .
-
-
%% Internal API
-
-
get_option( Option , Options ) ->
-
{ proplists:get_value ( Option , Options ) , proplists:delete ( Option , Options ) } .
动起来!
现在让我们让它活起来 - 我们用两个erlang shell, 一个用于mochiweb,一个用于路由器。 编辑start-dev.sh
, 用于启动mochiweb, 下面的额外参数是用于erl的:
-sname n1
命名节点‘n1′+K true
使kernel-poll有效。 当处理当量的连接时看起来不是那么的不知所措+P 134217727
缺省的你能调度的最大进程数为 32768. 想像一下我们每个连接就用一个进程(我不知道不那样做的更好原因) 我建议设置这个参数为最大的可能值。 根据 “man erl”,134,217,727 是最大的
现在运行
make && ./start-dev.sh
你将看到一个像(n1@localhost)1>的提示符
-你的mochiweb应用已经那个运行起来了,erlang节点也有了名字。
现在运行另外的erlang shell,就像这样:
erl -sname n2
现在两个erlang实例彼此不知道对方,更正它:
(n2@localhost)1> nodes().
[]
(n2@localhost)2> net_adm:ping(n1@localhost).
pong
(n2@localhost)3> nodes().
[n1@localhost]
现在从这个shell上编译启动路由器:
(n2@localhost)4> c(router).
{ok,router}
(n2@localhost)5> router:start_link().
{ok,<0.38.0>}
现在更好玩点, 从浏览器中执行 http://localhost:8000/test/123
(或者从终端执行lynx --source "http://localhost:8000/test/123"
)。 检查运行路由器的shell,你将看到已经有一个用户登陆了。
你现在可以向路由器发送消息并且在浏览器上看到她们。现在只是发送字符串,因为我们在feed函数中用~s
来格式化io_lib:fomart中的参数,原子将使其崩溃:
借用你运行路由器的shell:
(n2@localhost)6> router:send(123, "Hello World").
(n2@localhost)7> router:send(123, "Why not open another browser window too?").
(n2@localhost)8> router:send(456, "This message will go into the void unless you are connected as /test/456 too").
检查你的浏览器,你已经得到了comet,呵呵
在分布式erlang系统中运行
感觉上路由器和mochiweb前端运行在不同的机器上。 假设你有一对备用机用来测试,你应该把erlangshell作为分布式节点启动,也就是说用 -name n1@host1.example.com
取代 -sname n1
(n2也一样)。确信他们可以看到彼此,就像上面似的用 net_adm:ping(...)
。
注意router.erl中的16行, 路由器进程的名字(’router’)被注册成全局的,因为我们在对gen_server的调用中用随后的宏去标志和定位路由器,它已经在分布式系统中很好的工作了:
-define(SERVER, global:whereis_name(?MODULE))。
在分布式系统中为进程注册全局名是elang为你做的很自然的事情之一。
生成大量信息
在实际环境中我们可能看到像用例模型似的长尾想象,有一些很活跃的用户和很多不活跃用户。但是在这个测试中我们将不分青红皂白的为随机用户生成无用的消息。
msggen.erl:
-
-module ( msggen) .
-
-export ( [ start/3 ] ) .
-
-
start( 0 , _, _) -> ok ;
-
start( Num , Interval , Max ) ->
-
Id = random:uniform ( Max ) ,
-
router:send ( Id , "Fake message Num = " ++ Num ) ,
-
receive after Interval -> start ( Num -1 , Interval , Max ) end .
这将向id在1到max之间的随机用户发送Num个消息,每次发送等待Interval
毫秒。
你可以看到这些东西假如你运行路由器和mochiweb应用后用浏览器连接http://localhost:8000/test/3之
后执行
erl -sname test (test@localhost)1> net_adm:ping(n1@localhost). pong (test@localhost)2> c(msggen). {ok,msggen} (test@localhost)3> msggen:start(20, 10, 5). ok
这将向随机id在1-5之间的用户发送20条消息,每条消息之间有10毫秒等待。 Id 3有机会收到一条或者四条消息。
我们可以均等的并行运行一些进程以模拟多个消息源。这里的例子是生成10个进程,每个进程发送20条消息到1-5号用户,每条消息之间间隔100毫秒:
[ spawn(fun() -> msggen:start(20, 100, 5), io:format("~w finished.\n", [self()]) end) || _ <- lists:seq(1,10) ].
[<0.97.0>,<0.98.0>,<0.99.0>,<0.100.0>,<0.101.0>,<0.102.0>,
<0.103.0>,<0.104.0>,<0.105.0>,<0.106.0>]
<0.101.0> finished.
<0.105.0> finished.
<0.106.0> finished.
<0.104.0> finished.
<0.102.0> finished.
<0.98.0> finished.
<0.99.0> finished.
<0.100.0> finished.
<0.103.0> finished.
<0.97.0> finished.
再次感受C10K测试
现在我们需要运行另外一个有更大伸缩行的测试;客户端链接到mochiweb应用,mochiweb把他们注册到路由器。我们能生成更多的虚假消息来考验路由器,路由器将把这些消息发送到任何注册的客户端。让我们再次运行在Part 1 使用的10,000个并发用户的测试 ,但是这次我们将在传输大量消息之前保持所有的客户连接一段时间。
假设你按照在 Part 1 部分提到的操作调整了你的内核,增加了最大文件限制,这很简单。你已经运行了mochiweb应用和路由器,让我们查看下流量情况。
在没有任何客户连接的情况下, 每个pochiweb beam进程用了大约40MB内存(常驻):
$ ps -o rss= -p `pgrep -f 'sname n1'`
40156
带‘sname n1'的greps命令是为了得到我们的mochiweb erlang进程ID的,然后用带有格式化选项的ps命令打印常驻内存大小 (KB)
我组合出了这讨厌的每60秒用一行打印时间戳, 当前内存用量 (常驻 KB), 当前建立的连接数的语句 - 在另外一个运行mochiweb的机子的终端中放任他运行:
$ MOCHIPID=`pgrep -f 'name n1'`; while [ 1 ] ; do
NUMCON=`netstat -n | awk ‘/ESTABLISHED/ &&
$4==”127.0.0.1:8000″‘ | wc -l`; MEM=`ps -o rss= -p $MOCHIPID`; echo -e
“`date`\t`date +%s`\t$MEM\t$NUMCON”; sleep 60; done | tee -a
mochimem.log
假如有谁知道为一个进程长时间动态剥离出内存用量更好的方法,请留个言。
现在在一个新的erl shell中运行第一部分使用的floodtest工具:
erl> floodtest:start("/tmp/mochi-urls.txt", 10).
这将每秒建立100个新的连接,直到10,000个客户端都已经连接了。
你将看到非常快的达到10K个连接:
erl> floodtest:start("/tmp/mochi-urls.txt", 10).
Stats: {825,0,0}
Stats: {1629,0,0}
Stats: {2397,0,0}
Stats: {3218,0,0}
Stats: {4057,0,0}
Stats: {4837,0,0}
Stats: {5565,0,0}
Stats: {6295,0,0}
Stats: {7022,0,0}
Stats: {7727,0,0}
Stats: {8415,0,0}
Stats: {9116,0,0}
Stats: {9792,0,0}
Stats: {10000,0,0}
...
检查这讨厌的一行输出的内存使用情况
Mon Oct 20 16:57:24 BST 2008 1224518244 40388 1
Mon Oct 20 16:58:25 BST 2008 1224518305 41120 263
Mon Oct 20 16:59:27 BST 2008 1224518367 65252 5267
Mon Oct 20 17:00:32 BST 2008 1224518432 89008 9836
Mon Oct 20 17:01:37 BST 2008 1224518497 90748 10001
Mon Oct 20 17:02:41 BST 2008 1224518561 90964 10001
Mon Oct 20 17:03:46 BST 2008 1224518626 90964 10001
Mon Oct 20 17:04:51 BST 2008 1224518691 90964 10001
他已经达到了10K个并发连接(加上我用firefox打开的这个),mochiweb常驻内存的大小也在90MB左右(90964KB)。
现在来弄些消息吧
erl> [ spawn(fun() -> msggen:start(1000000, 100, 10000) end) || _ <- lists:seq(1,100) ].
[<0.65.0>,<0.66.0>,<0.67.0>,<0.68.0>,<0.69.0>,<0.70.0>,
<0.71.0>,<0.72.0>,<0.73.0>,<0.74.0>,<0.75.0>,<0.76.0>,
<0.77.0>,<0.78.0>,<0.79.0>,<0.80.0>,<0.81.0>,<0.82.0>,
<0.83.0>,<0.84.0>,<0.85.0>,<0.86.0>,<0.87.0>,<0.88.0>,
<0.89.0>,<0.90.0>,<0.91.0>,<0.92.0>,<0.93.0>|...]
也就是100个进程各自以每秒10条消息的速度向随机的id为1到10000的客户端发送1百万条消息。那就意味着路由器每秒钟看到1000条消息,平均我们10K个客户端每10秒得到一条信息。
检查floodtest shell的输出,你将看到客户端正在接收http数据块(记住,格式是{NumConnected, NumClosed, NumChunksRecvd}):
...
Stats: {10000,0,5912}
Stats: {10000,0,15496}
Stats: {10000,0,25145}
Stats: {10000,0,34755}
Stats: {10000,0,44342}
...
一百万条消息以每个进程每秒10条速度将用27小时完成。这仅仅是10分钟后的没存用量:
Mon Oct 20 16:57:24 BST 2008 1224518244 40388 1
Mon Oct 20 16:58:25 BST 2008 1224518305 41120 263
Mon Oct 20 16:59:27 BST 2008 1224518367 65252 5267
Mon Oct 20 17:00:32 BST 2008 1224518432 89008 9836
Mon Oct 20 17:01:37 BST 2008 1224518497 90748 10001
Mon Oct 20 17:02:41 BST 2008 1224518561 90964 10001
Mon Oct 20 17:03:46 BST 2008 1224518626 90964 10001
Mon Oct 20 17:04:51 BST 2008 1224518691 90964 10001
Mon Oct 20 17:05:55 BST 2008 1224518755 90980 10001
Mon Oct 20 17:07:00 BST 2008 1224518820 91120 10001
Mon Oct 20 17:08:05 BST 2008 1224518885 98664 10001
Mon Oct 20 17:09:10 BST 2008 1224518950 106752 10001
Mon Oct 20 17:10:15 BST 2008 1224519015 114044 10001
Mon Oct 20 17:11:20 BST 2008 1224519080 119468 10001
Mon Oct 20 17:12:25 BST 2008 1224519145 125360 10001
当10K个客户端已经连接后,你能看到内存已经由40MB涨到了90M,运行更长一段时间涨到了125M。
值得指出的是floodtest shell 与cpu相关的,消息产生shell 用了2%cpu,路由器和mochiweb应用则少于1%。(也就是只有仿真大量客户端用的cpu比较多 - 服务器本身则很少)。这有助于用多台机子或多核cpu进行测试。
运行24小时后的结果
我运行这个24小时, mochiweb进程的内存用量信息被写到mochimem.log文件中。这是10000个连接客户端,每秒1000条消息发送给随机的客户端。
下面的bash/awk语句是为了把mochimem.log信息转成图例:
(echo -e "set terminal png size 500,300\nset xlabel \"Minutes
Elapsed\"\nset ylabel \"Mem (KB)\"\nset title \"Mem usage with 10k
active connections, 1000 msg/sec\"\nplot \"-\" using 1:2 with lines
notitle" ; awk 'BEGIN{FS="\t";} NR%10==0 {if(!t){t=$2} mins=($2-t)/60;
printf("%d %d\n",mins,$3)}' mochimem.log ; echo -e "end" ) | gnuplot
> mochimem.png
这个图展示内存用量 (10k 活跃连接, 1000条消息/秒) 24小时持续在250M。有两个大点的下掉, 一个在测试开始一个在结束, 这是当在mochiweb进程中处于好奇运行这个:
erl> [erlang:garbage_collect(P) || P <- erlang:processes()].
他迫使所有的进程进行垃圾回收,这收回大约100MB的内存-下面我们研究一些不用手动强迫进行垃圾回收的方法以节约内存。
在mochiweb减少内存的方法
看起来mochiweb应用只是发送消息然后立即跌掉她们,内存用量不应该随消息发送数的增长而增长。
对于Erlang内存管理我是个新手,但是我继续假设能够频繁的进行垃圾回收,这将允许我们剩下大量内存, 最终让我们能用比较少的系统内存服务更多用户。 我们可能利用更多点的cpu占用率, 但是是可以接收的。
深挖 erlang docs 有这么几个选项:
erlang:system_flag(fullsweep_after, Number)
Number是一个标志在没有全扫描的情况下多少次垃圾回收可以做的一个非负常数。这个值适用于新进程;已经运行的进程不受影响。
在低内存的系统中(特别没有虚拟内存),设置这个值为0可以帮助节约内存
另一个设置次值可选的方法是通过(操作系统)环境变量ERL_FULLSWEEP_AFTER。
听起来挺有意思,但是他仅仅适用于新进程而且将对虚拟机中的所有进程产生作用,只是除了我们的mochiweb进程,呵呵
接下来
erlang:system_flag(min_heap_size, MinHeapSize)
为进程设置缺省的堆大小。以字为单位。新的min_heap_size仅仅影响当min_heap_size改变后生成的进程。通过spawn_opt/N or process_flag/2 min_heap_size可以为单独进行设置
可能有用,但是我更愿意确保我们的mochiweb进程有一个比缺省值大点的内存堆。我更喜欢尽可能避免为了加spawn选项而对mochieweb源代码打补丁
下面的吸引了我的眼球
erlang:hibernate(Module, Function, Args)
把正在调用的进程处于等待状态,它的内存分配就会尽可能的少,假如进程不想在短时间内接收任何数据了那么这是非常有用的。
进程在有消息发送过来时被唤醒, 跟着调用栈被清空,带有由Args给定参数的Module:Function将得到控制权, 意味着这个进程当函数返回时将被终止。 这样erlang:hibernate/3将永远也返回不到调用他的地方
假如进程在消息队列里有任何消息,进程也会以上面的方式被立即唤醒。
用更专业的术语来说,erlang:hibernate/3 做了下面几点。他丢弃进程的调用栈之后进行垃圾回收。在回收后,所有活跃数据都在一个连续的堆中。这个堆然后把空间压缩到刚好能容纳的了活跃数据的尺寸 (即使这个尺寸比进程的最小堆的还小).
假如进程活跃数据的大小小于最小堆尺寸,第一次垃圾回收也会在进程被唤醒后发生,这样确保堆尺寸会变到不小于最小堆尺寸。
注意,清空调用栈意味着任何异常处理都被移除并在休眠后重新插入。一个影响是进程要用proc_lib启动(间接的, gen_server也可以), 用proc_lib:hibernate/3代替主要是确保异常处理在进程被唤醒后能够继续工作。
听起来很合理 - 让我们发送完每个消息后试着休眠,看到底发生了什么
编辑
mochiconntest_web.erl
,改变如下:
- 让
feed(Response, Id, N)函数的
最后一行调用hibernate,而不是调用他自己 - 登进路由器后立马调用hibernate,而不是调用
feed并阻塞在receive上
- 记住导出
feed/3
,这样hibernate在唤醒时可以回调回这个函数
用hibernation更新后的
mochiconntest_web.erl
:
-
-module ( mochiconntest_web) .
-
-
-export ( [ start/1 , stop/0 , loop/2 , feed/3 ] ) .
-
-
%% External API
-
-
start( Options ) ->
-
{ DocRoot , Options1 } = get_option( docroot, Options ) ,
-
Loop = fun ( Req ) ->
-
?MODULE :loop ( Req , DocRoot )
-
end ,
-
% we’ll set our maximum to 1 million connections. (default: 2048)
-
mochiweb_http:start ( [ { max, 1000000 } , { name, ?MODULE } , { loop, Loop } | Options1 ] ) .
-
-
stop( ) ->
-
mochiweb_http :stop ( ?MODULE ) .
-
-
loop( Req , DocRoot ) ->
-
"/" ++ Path = Req :get ( path) ,
-
case Req :get ( method) of
-
Method when Method =:= ‘GET’ ; Method =:= ‘HEAD’ ->
-
case Path of
-
"test/" ++ IdStr ->
-
Response = Req :ok ( { "text/html; charset=utf-8" ,
-
[ { "Server" ,"Mochiweb-Test" } ] ,
-
chunked} ) ,
-
{ Id , _} = string:to_integer ( IdStr ) ,
-
router:login ( Id , self( ) ) ,
-
% Hibernate this process until it receives a message:
-
proc_lib:hibernate ( ?MODULE , feed, [ Response , Id , 1 ] ) ;
-
_ ->
-
-
-
Req :not_found ( )
-
end ;
-
‘POST’ ->
-
case Path of
-
_ ->
-
Req :not_found ( )
-
end ;
-
_ ->
-
Req :respond ( { 501 , [ ] , [ ] } )
-
end .
-
-
feed( Response , Id , N ) ->
-
receive
-
{ router_msg, Msg } ->
-
Html = io_lib:format ( "Recvd msg #~w: ‘~w’<br/>" , [ N , Msg ] ) ,
-
Response :write_chunk ( Html )
-
end ,
-
% Hibernate this process until it receives a message:
-
proc_lib:hibernate ( ?MODULE , feed, [ Response , Id , N +1 ] ) .
-
-
-
%% Internal API
-
-
get_option( Option , Options ) ->
-
{ proplists:get_value ( Option , Options ) , proplists:delete ( Option , Options ) } .
我做了这些改变,运行make重新构建mochiweb,然后重做同样的c10k测试 (1000条消息/秒 24小时).
运行24小时后的结果 w/ proc_lib:hibernate()
恰如其分的,用了hibernate,10K个连接的mochiweb应用的内存用量维持在78MB这个水平,
要比我们在第一部分
看到的450MB好的多。CPU占用率也没明显增加。
总结
我们基于mochiweb做了个comet应用,他让我们推送任意消息给由ID标志的客户端。在以每秒1000条消息的速度推送24小时后, 10,000个连接用户,我们发现它用了80MB内存,或者说每个用户8KB 。 我们同样也做了很漂亮的图。
相对于在第一部分
我们所看到的每个用户45KB,这是一个很大的改进。这些优化节省是归因于让我们的应用表现的更加贴近实际, 为mochiweb进程在每条消息之间用 hibernate。
下一步
后续, 我将调整它到一百万个连接客户端。我将部署这个测试应用到拥有充沛内存的多核64位服务器上 。这将展示有什么不同,如果有的话也可以运行在64位虚拟机上。为了模拟一百万客户连接我将详细介绍一些额外的技巧和调整 。
这个应用将发展成一系列公共子系统,在那订阅被关联于用户ID 并被存储于这个应用, 而不是当用户连接时由他们提供。我们将调入一个典型的社会网络数据集: friends。这将允许一个用户用一个用户ID登陆并且自动接收任何有他朋友生成的消息。
评论
MOCHIPID=`pgrep -f 'name n1'`; while [ 1 ] ; do NUMCON=`netstat -n | awk '/ESTABLISHED/ && $4=="127.0.0.1:8086"' | wc -l`; MEM=`ps -o rss= -p $MOCHIPID`; echo -e “`date`\\t`date +%s`\\t$MEM $NUMCON“; sleep 60; done | tee -a mochimem.log
发表评论
-
用Mochiweb打造百万级Comet应用,第三部分(续2)
2008-12-09 14:02 2342提示:如有转载请注明作者 小游戏 及出处 原文:A Mi ... -
用Mochiweb打造百万级Comet应用,第三部分(续)
2008-12-04 14:05 2692提示:如有转载请注明作者 小游戏 及出处 原文:A Mi ... -
用Mochiweb打造百万级Comet应用,第三部分
2008-11-21 14:56 2809提示:如有转载请注明作者 小游戏 及出处 原文:A Mi ... -
用Mochiweb打造百万级Comet应用,第一部分
2008-11-14 12:25 6084提示:如有转载请注明作者 小游戏 及出处 原文:A Mi ...
相关推荐
Mochiweb是一个轻量级、高效的Web服务器和HTTP库,用Erlang编程语言编写。这个实例将帮助我们理解Mochiweb是如何工作的,并如何使用它来构建一个简单的Web服务器。Erlang是一种并发性极强、容错性高的语言,特别适合...
2. **Web 服务器进程模型**:Mochiweb 使用 Erlang 的进程模型来处理每个连接,这使得它非常适合处理高并发场景。每个连接都被分配到一个独立的进程,保证了请求之间的隔离,同时也易于实现并发处理。 3. **HTTP ...
2. **WebSocket接口**:利用Mochiweb的WebSocket支持,为聊天室提供实时通信的能力。用户通过WebSocket连接到服务器,发送和接收消息。 3. **消息存储与广播**:在Erlang进程中,可能会有一个或多个进程用于存储...
MochiWeb 的最新版本可在MochiWeb 的邮件列表位于 设置 MochiWeb 环境需要 Erlang OTP,可在使用项目创建一个新的 mochiweb:make app PROJECT=project_name 要使用特定目录中的项目创建新的 mochiweb: make app ...
RPSSL-Rock-Paper-Scissors-Spock-Lizard是一款简单但着名的2人游戏。 通常,它是第一人称自己玩的,但这是网络版本。 Web版本使用,该是运行时间很长的HTTP请求,可用于将服务器立即将数据推送到客户端。 每个...
**Erlang 高级特性和应用** Erlang 是一种高级编程语言,以其在并发处理、分布式计算和高可靠性方面的出色性能而闻名。在国内外,Erlang 已经被广泛应用于各种场景,如广告平台、社交网络、云计算、网络游戏以及...
在国外,Erlang被广泛应用于Ejabberd即时通讯服务器、RabbitMQ消息队列、CouchDB文档数据库、Mochiweb轻量级Web服务器以及Disco分布式计算框架。 Erlang与传统的操作系统如Unix相比,具有独特的设计哲学。在Unix中...
在Erlang Web框架中,Mochiweb和Cowboy通常被用于构建RESTful API、实时Web应用或者作为其他复杂系统的一部分。这些框架充分利用Erlang的actor模型,实现了进程间的异步通信,从而在处理高并发场景时能保持良好的...
MochiWeb是一个Erlang库... 要使用项目创建新的mochiweb,请执行以下操作:使应用程序PROJECT = project_name 要使用特定目录中的项目创建新的mochiweb:make app PROJECT = project_name PREFIX = $ HOME / projects /
【JavaScript + Delphi + ErLang讲座内容(4)】是一个专题讲座的第四部分,主要探讨了如何将三种技术——JavaScript、Delphi和Erlang——整合应用。这个压缩包包含了多个资源,帮助学习者理解这三者的交互和实际应用...
在此前提下,架构的设计可以分为三个主要部分:服务堆栈、服务拓扑和开源项目应用。 服务堆栈包括了WebAPI、MobileAPI、WebServer、InstantSearch、SystemFeed、SystemNotice、MQserver、CalfServer、OnlineServer...
Webmachine是一个应用程序层,它在mochiweb提供的出色的按位和HTTP语法管理的基础上增加了HTTP语义意识,并提供了一种简单明了的方式将其连接到应用程序的行为。 可获得更多信息。 您还可以阅读有关Webmachine的...
- Mochiweb: 一种轻量级的Erlang Web服务器,可通过SVN检出并放置于 `$ERL_LIB` 目录下自动加入至编译路径。 - GeoIP数据库: 通过脚本从MaxMind获取并解压,存储于`priv`目录下。 - egeoip: 从Google Code获取,...
2. **多框架兼容**:支持各种框架,例如Spring、Grails、Express、Rails、Lift、MochiWeb等。 3. **多服务集成**:能够与多种数据服务和其他服务集成,如MySQL、PostgreSQL、MongoDB、Redis、RabbitMQ等。 4. **多云...
Webmachine 是一个应用层,为 mochiweb 提供 HTTP 语义的特性,定义一个简单而清晰的连接应用的方式。 标签:Webmachine Web框架
2. **Erlang实现Websocket**:在Erlang中,可以使用如`cowboy`或`mochiweb`这样的Web框架来处理Websocket连接。它们提供了方便的中间件,使得在Erlang进程中直接处理Websocket连接成为可能。例如,`cowboy`中的`...
heroku-genfsmAn experimental Erlang app which deployed on HerokuSome Deploy Detail在 Heroku 上部署 Webmachine + Mochiweb + ErlyDTL 组合的 Erlang Web 应用
该框架基于Mochiweb(一款用Erlang编写的Web服务器)构建,旨在帮助开发者轻松构建遵循HTTP语义的服务,同时避免了在业务逻辑中直接处理HTTP相关的复杂性。 ##### 原则 - **默认行为**:Webmachine实现了一些默认...
使用 rebar 工具开发 Erlang 工程项目和发布 Erlang 工程项目学习 本文主要介绍了使用 rebar 工具开发 Erlang 工程项目和发布 Erlang 工程项目的方法。rebar 是一个 Erlang 构建工具,可以方便的编译测试 Erlang ...