[译]使用Mochiweb构建百万级Comet程序,第一篇

  [译]使用Mochiweb构建百万级Comet程序,第一篇
使用Mochiweb构建百万级Comet程序,第一篇
http://ro4tub.blogspot.com/2008/10/mochiwebcomet.html
译者: oscar

译者注:作者Richard Jones是Last.fm的创始人之一。原文简单易懂,且最后的留言也颇有参考价值。




在这系列,我将向大家详细描述,我所知道的mochiweb支持那么多的连接的原因。教你们使用mochiweb构建comet程序,每个mochiweb连接都注册在给不同用户分发消息的路由器上。
最后,我会给出一个可以运行的,且能支持百万并发连接的程序。关键之处是,我们需要知道支持那多并发连接需要占用多少内存。

本作:
  • 实现一个简单的comet mochiweb程序,每10秒向客户端发送一条消息。
  • 设置linux内核参数,使其能处理更多的TCP连接。
  • 实现一个压力测试工具(flood-testing tool),不断建立连接(C10K 测试)。
  • 检查每条连接占用的内存。

在续作中,我将介绍如何实现一个实时消息路由系统、降低内存使用的技巧和进一步的测试(10万、100万并发连接)。

我假定,你会使用linux命令行,并且了解Erlang。


编译Mochiweb测试程序

简而言之:
  1. 安装、编译Mochiweb
  2. 运行: /your-mochiweb-path/scripts/new_mochiweb.erl mochiconntest
  3. cd mochiconntest 然后编辑src/mochiconntest_web.erl

这段代码(mochiconntest_web.erl)只是接受连接,并使用块传输(chunked transfer)发送一条初始的欢迎消息,每10秒给每个客户端发送一条。

  1. - module (mochiconntest_web ).
  2. - export ( [start/ 1, stop/ 0, loop/ 2 ] ).
  3. %% External API
  4. start ( Options ) ->
  5. { DocRoot, Options1 } = get_option (docroot, Options ),
  6. Loop = fun ( Req ) ->
  7. ? MODULE: loop ( Req, DocRoot )
  8. end,
  9. % we’ll set our maximum to 1 million connections. (default: 2048)
  10. mochiweb_http: start ( [ {max, 1000000 }, {name, ? MODULE }, {loop, Loop } | Options1 ] ).

  11. stop ( ) ->
  12. mochiweb_http: stop (? MODULE ).

  13. loop ( Req, DocRoot ) ->
  14. "/" ++ Path = Req: get (path ),
  15. case Req: get (method ) of
  16. Method when Method =:= ‘GET’; Method =:= ‘HEAD’ ->
  17. case Path of
  18. "test/" ++ Id ->
  19. Response = Req: ok ( { "text/html; charset=utf-8",
  20. [ { "Server", "Mochiweb-Test" } ],
  21. chunked } ),
  22. Response: write_chunk ( "Mochiconntest welcomes you! Your Id: " ++ Id ++ "/n" ),
  23. %% router:login(list_to_atom(Id), self()),
  24. feed ( Response, Id, 1 );
  25. _ ->
  26. Req: not_found ( )
  27. end;
  28. ‘POST’ ->
  29. case Path of
  30. _ ->
  31. Req: not_found ( )
  32. end;
  33. _ ->
  34. Req: respond ( { 501, [ ], [ ] } )
  35. end.

  36. feed ( Response, Path, N ) ->
  37. receive
  38. %{router_msg, Msg} ->
  39. % Html = io_lib:format("Recvd msg #~w: ‘~s’
    ", [N, Msg]),
  40. % Response:write_chunk(Html);
  41. after 10000 ->
  42. Msg = io_lib: format ( "Chunk ~w for id ~s/n", [ N, Path ] ),
  43. Response: write_chunk ( Msg )
  44. end,
  45. feed ( Response, Path, N +1 ).

  46. %% Internal API
  47. get_option ( Option, Options ) ->
  48. {proplists: get_value ( Option, Options ), proplists: delete ( Option, Options ) }.



启动你的mochiweb程序

make && ./start-dev.sh

默认情况下,mochiweb在所有网卡的8000端口上侦听。如果你在桌面环境下工作,那么你可以打开任何浏览
器进行测试,输入http://localhost:8000/test/foo。

而命令行测试如下:
$ lynx --source "http://localhost:8000/test/foo"
Mochiconntest welcomes you! Your Id: foo

Chunk 1 for id foo

Chunk 2 for id foo

Chunk 3 for id foo

^C

好的,让我们继续吧。


设置Linux内核参数,以支持更多连接

为节省你的时间,在进行大规模测试之前,我们先调节内核的tcp设置。否则,你的测试将会失败,你会看到许
多『out of socket memory』的消息(最后将是nf_conntrack: table
full, dropping packet.)

下面是最终的sysctl设置(你的配置可能不一样,但应该差不多):

# General gigabit tuning:
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
net.ipv4.tcp_syncookies = 1
# this gives the kernel more memory for tcp
# which you need with many (100k+) open socket connections
net.ipv4.tcp_mem = 50576 64768 98152
net.core.netdev_max_backlog = 2500
# I was also masquerading the port comet was on, you might not need this
net.ipv4.netfilter.ip_conntrack_max = 1048576

在/etc/sysctl.conf中配置,然后运行-p。不需要重启电脑,现在你的内核可以处理大量的连接了。


创建大量连接

有多种实现方法。Tsung 是其中最为不错的,其他不错的还有ab、httperf、httpload等等。不过对于测试
comet程序,们都显得不那么理想化
。正好我也想找个借口尝试一下Erlang http客户端,所以我写了个简单
的测试程序,进行大量的连接。

在这里一个连接一个进程确实有点浪费。所以,我用一个进程从文件中加载URL,另一个进程建立连接、接收
数据(还有一个进程每10秒定时打印报告)。

服务器不处理接收到的数据,直接丢弃,但是会增加计数器,所以我们可以跟踪http块(HTTP chunks)的传输
量。

floodtest.erl

  1. - module (floodtest ).
  2. - export ( [start/ 2, timer/ 2, recv/ 1 ] ).

  3. start ( Filename, Wait ) ->
  4. inets: start ( ),
  5. spawn (? MODULE, timer, [ 10000, self ( ) ] ),
  6. This = self ( ),
  7. spawn (fun ( )-> loadurls ( Filename, fun ( U )-> This ! {loadurl, U } end, Wait ) end ),
  8. recv ( { 0, 0, 0 } ).

  9. recv ( Stats ) ->
  10. { Active, Closed, Chunks } = Stats,
  11. receive
  12. {stats } -> io: format ( "Stats: ~w/n", [ Stats ] )
  13. after 0 -> noop
  14. end,
  15. receive
  16. {http, {_ Ref,stream_start,_ X } } -> recv ( { Active +1, Closed, Chunks } );
  17. {http, {_ Ref,stream,_ X } } -> recv ( { Active, Closed, Chunks +1 } );
  18. {http, {_ Ref,stream_end,_ X } } -> recv ( { Active -1, Closed +1, Chunks } );
  19. {http, {_ Ref, {error, Why } } } ->
  20. io: format ( "Closed: ~w/n", [ Why ] ),
  21. recv ( { Active -1, Closed +1, Chunks } );
  22. {loadurl, Url } ->
  23. http: request (get, { Url, [ ] }, [ ], [ {sync, false }, {stream, self }, {version, 1.1 }, {body_format, binary } ] ),
  24. recv ( Stats )
  25. end.

  26. timer ( T, Who ) ->
  27. receive
  28. after T ->
  29. Who ! {stats }
  30. end,
  31. timer ( T, Who ).

  32. % Read lines from a file with a specified delay between lines:
  33. for_each_line_in_file ( Name, Proc, Mode, Accum0 ) ->
  34. {ok, Device } = file: open ( Name, Mode ),
  35. for_each_line ( Device, Proc, Accum0 ).

  36. for_each_line ( Device, Proc, Accum ) ->
  37. case io: get_line ( Device, "" ) of
  38. eof -> file: close ( Device ), Accum;
  39. Line -> NewAccum = Proc ( Line, Accum ),
  40. for_each_line ( Device, Proc, NewAccum )
  41. end.

  42. loadurls ( Filename, Callback, Wait ) ->
  43. for_each_line_in_file ( Filename,
  44. fun ( Line, List ) ->
  45. Callback (string: strip ( Line, right, $/n ) ),
  46. receive
  47. after Wait ->
  48. noop
  49. end,
  50. List
  51. end,
  52. [read ], [ ] ).

我 们建立的每个连接都需要一个端口,即文件描述符(file descriptor)。默认情况下,文件描述符的上限为1024。为了避免『too many open files』问题,你需要修改ulimit,可以在/etc/security/limits.conf中
修改,但是需要重启。现在你可以sudo,修改当前的shell:

$ sudo bash
# ulimit -n 999999
# erl

你也需要增加端口范围:
# echo "1024 65535" > /proc/sys/net/ipv4/ip_local_port_range

为floodtest程序批量生成URL:
( for i in `seq 1 10000`; do echo "http://localhost:8000/$i" ; done ) >
/tmp/mochi-urls.txt

现在你可以编译、运行floodtest.erl:

erl> c(floodtest).
erl> floodtest:start("/tmp/mochi-urls.txt", 100).

它将每秒建立10条新的连接(例如,每100毫秒一条连接)。

它将以{Active, Closed, Chunks}格式输出状态信息,其中Active是并发连接的数目,Closed是由于某种
原因断开的连接,Chunks是mochiweb以块方式传输的块数目。Closed应该为0,Chunks应该大于Active,因
为每个活跃的连接将接收多个块(每10秒1个)。

1万个活跃连接占用450MB的内存,也就是说每个连接45KB。CPU占用率几乎没有。


现阶段总结

每个连接占用45KB内存,似乎有点高。用C + libevent库,我想我可以做到每连接占用4.5KB(只是个猜想,
谁有这方面的经历,请讲讲)。如果从代码量和实现时间比较Erlang和C,我想多一点内存消耗是可以理解的。

在之后的文章中,我将实现一个消息路由(取消mochiconntest_web.erl的25行和41-43行的注释),讨论一些降低内存使用量的方法。

我也会和大家一起分享10万和100万连接的结果。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值