用Mochiweb打造百万级Comet应用(一)

原文:A Million-user Comet Application with Mochiweb, Part 1

参考资料:Comet--基于 HTTP 长连接、无须在浏览器端安装插件的“服务器推”技术为“Comet”

MochiWeb--建立轻量级HTTP服务器的Erlang库

在这个系列中,我将详述我所发现的mochiweb是怎样支持那么巨大的网络连接的,为大家展示怎样用mochiweb构建一个comet应用,这个应用中每个mochiweb连接都被注册到负责为不同用户派送消息的路由器上。最后我们建立一个能够承受百万并发连接的可工作的应用,更重要的我们将知道这样的应用需要多少内存才能使它跑起来。

本部分内容如下:

建立一个基本的comet应用, 它每10秒钟给客户端发送一条消息调整linux内核参数,使它能够处理大量的TCP连接写一个能够建立大量网络连接的压力测试工具 (也就是 C10k测试)检查每个连接到底需要多少内存.

本系列续作将包括怎样建立一个真正的信息路由系统,降低内存使用的技巧,100K和1m并发连接的测试。

基础是你需要知道一些linux命令行操作和一点Erlang知识,否则看不懂别怪我呀,呵呵

写一个Mochiweb测试程序

概括如下:

安装编译Mochiweb运行: /your-mochiweb-path/scripts/new_mochiweb.erl mochiconntestcd mochiconntest 之后编辑 src/mochiconntest_web.erl

这部分代码(mochiconntest_web.erl)只是接收连接并且每十秒用块传输方式给客户端发送一个初始的欢迎信息。

mochiconntest_web.erl

-module( mochiconntest_web) .-export([ start/1 , stop/0 , loop/2]) .%% 外部APIstart(Options) ->    {DocRoot , Options1} = get_option( docroot, Options) ,    Loop = fun (Req) ->                   ?MODULE :loop(Req , DocRoot)           end ,    % 设置最大连接数为一百万,缺省2048    mochiweb_http:start([{ max, 1000000} , { name, ?MODULE} , { loop, Loop} | Options1]) . stop() ->    mochiweb_http :stop( ?MODULE) . loop(Req , DocRoot) ->    "/" ++ Path = Req :get( path) ,    caseReq :get( method)of        Method when Method =:= ‘GET’ ; Method =:= ‘HEAD’ ->            casePathof                "test/" ++ Id ->                    Response = Req :ok({"text/html; charset=utf-8" ,                                      [{"Server" ,"Mochiweb-Test"}] ,                                      chunked}) ,                    Response :write_chunk("Mochiconntest welcomes you! Your Id: " ++ Id ++ "\n ") ,                    %% router:login(list_to_atom(Id), self()),                    feed(Response , Id , 1) ;                _ ->                    Req :not_found()            end ;        ‘POST’ ->            casePathof                _ ->                    Req :not_found()            end ;        _ ->            Req :respond({501 , [] , []})    end . feed(Response , Path , N) ->    receive        %{router_msg, Msg} ->        %    Html = io_lib:format("Recvd msg #~w: ‘~s’<br/>", [N, Msg]),        %    Response:write_chunk(Html);    after10000 ->        Msg = io_lib:format("Chunk ~w for id ~s\n " , [N , Path]) ,        Response :write_chunk(Msg)    end ,    feed(Response , Path , N+1) . %%内部APIget_option(Option , Options) ->    { 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<br/>Chunk 1 for id foo<br/> Chunk 2 for id foo<br/>Chunk 3 for id foo<br/>^C

是的,它可以工作。 现在,让我们使劲整它,呵呵。

调整linux内核参数,使它能够处理大量的TCP连接

为节省时间我们需要在进行大量并发连接测试之前调整内核的tcp设置参数,否则你的测试将会失败,你将看到大量的Out of socket memory 信息(假如在伪造将得到, nf_conntrack: table full, dropping packet. )

下面的是我用到的sysctl设置 - 你的配置可能不一样,但是大致就是这些:

# General gigabit tuning: net.core.rmem_max = 16777216net.core.wmem_max = 16777216 net.ipv4.tcp_rmem = 4096 87380 16777216net.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中然后运行sysctl -p 使其生效。不需要重启,现在你的内核能够处理大量的连接了,yay。

建立大量连接

有很多方法可以用. Tsung 就十分好, 也有很多其他比较好的工具如ab, httperf, httpload等等可以生成大量的无用请求。 但是它们中任何一款都不适合测试comet应用, 正好我也想找个借口测试一下Erlang的http客户端, 因此我写了一个基本的测试程序用以发起大量的连接。
只是因为你可以但并不意味着你就这样做.. 一个连接就用一个进程确实有点浪费。我用一个进程从文件中调入一批url链接,另一个进程建立连接并接收数据 (当定时器的进程每10秒打印一份报告)。所有从服务器接收来的数据都被丢弃,但是它增加计数,这样我们能够跟踪到底有多少http数据块被传输了。

floodtest.erl

-module( floodtest) .-export([ start/2 , timer/2 , recv/1]) . start(Filename , Wait) ->    inets :start() ,    spawn( ?MODULE , timer, [10000 , self()]) ,    This = self() ,    spawn( fun() -> loadurls(Filename , fun(U) -> This ! { loadurl, U}end , Wait)end) ,    recv({0 ,0 ,0}) . recv(Stats) ->    {Active , Closed , Chunks} = Stats ,    receive        { stats} -> io :format("Stats: ~w\n " ,[Stats])        after0 -> noop    end ,    receive        { http,{ _Ref ,stream_start,_X}} ->  recv({Active+1 ,Closed ,Chunks}) ;        { http,{ _Ref ,stream,_X}} ->          recv({Active , Closed , Chunks+1}) ;        { http,{ _Ref ,stream_end,_X}} ->  recv({Active-1 , Closed+1 , Chunks}) ;        { http,{ _Ref ,{ error,Why}}} ->            io :format("Closed: ~w\n " ,[Why]) ,            recv({Active-1 , Closed+1 , Chunks}) ;        { loadurl, Url} ->            http :request( get, {Url , []} , [] , [{ sync, false} , { stream, self} , { version, 1.1} , { body_format, binary}]) ,                recv(Stats)    end . timer(T , Who) ->    receive    afterT ->        Who ! { stats}    end ,    timer(T , Who) . % Read lines from a file with a specified delay between lines:for_each_line_in_file(Name , Proc , Mode , Accum0) ->    { ok, Device} = file:open(Name , Mode) ,    for_each_line(Device , Proc , Accum0) . for_each_line(Device , Proc , Accum) ->    case io:get_line(Device , "")of        eof  -> file :close(Device) , Accum ;        Line -> NewAccum = Proc(Line , Accum) ,                    for_each_line(Device , Proc , NewAccum)    end . loadurls(Filename , Callback , Wait) ->    for_each_line_in_file(Filename ,        fun(Line , List) ->            Callback( string:strip(Line , right, $\n)) ,            receive            afterWait ->                noop            end ,            List        end ,        [ read] , []) .每个连接我们都要用一个临时的端口,每个端口也是一个文件描述符, 缺省情况下这被限制为1024。为了避免Too many open files问题出现,你需要为你当前shell更改这个限制 ,可以通过修改/etc/security/limits.conf ,但是这需要注销再登陆。目前你只需要用sudo修改当前shell就可以了(假如你不想运行在root状态下,调用ulimit后请su回非权限用户): udo bash # ulimit -n 999999# erl

你也可以把临时端口的范围区间增到最大:
# echo "1024 65535" > /proc/sys/net/ipv4/ip_local_port_range

为压力测试程序生成一个url列表文件
( for i in `seq 1 10000`; do echo "http://localhost:8000/test/$i" ; done ) > /tmp/mochi-urls.txt

现在在erlang提示符下你可以编译调用floodtest.erl 了:
erl> c(floodtest).
erl> floodtest:start("/tmp/mochi-urls.txt", 100).

这将每秒钟建立十个连接 (也就是每个连接100毫秒).

它将以{Active, Closed, Chunks}的形式输出状态信息 ,Active表示已建立连接数, Closed表示因每种原因被终止的连接数,Chunks是mochiweb以块传输模式处理的数据块数。 Closed应该为0,Chunks应该大于Active,因为每个活跃连接接收多个数据块 (10秒一个)。

10,000个活跃连接的mochiweb进程的固定大小是450MB-也就是每个连接45KB。CPU占用率就好像预想中的一样微乎其微.

总结

第一次尝试是可以理解的。每个连接45KB内存看起来有些高 - 用libevent再做些调整我可以把它做到将近4.5KB每个连接 (只是猜猜, 谁有这方面的经验请留个回复). 如果就代码量和时间效率上对erlang和c做下考量,我想多花点内存还是有情可原的。

后续中,我将建立一个消息路由器 (我们可以把mochiconntest_web.erl中的 25行和41-43行的注释取消 )也探讨一下减少内存用量的方法。我也会分享当100k和1M个连接时的测试结果。

时间: 2024-08-02 21:50:27

用Mochiweb打造百万级Comet应用(一)的相关文章

用Mochiweb打造百万级Comet应用(二)

原文:A Million-user Comet Application with Mochiweb, Part 2 参考资料:Comet--基于 HTTP 长连接.无须在浏览器端安装插件的“服务器推”技术为“Comet” MochiWeb--建立轻量级HTTP服务器的Erlang库 在第一部分 , 我们构建了一个每10秒向客户端发送一条消息的mochiweb comet应用(没什么用处).我们微调了一下linux内核,做了一个能够建立大量网络连接以测试应用性能和所耗内存的工具 .我们发现每个连接

HERE 使用 AWS EF 和 JFrog Artifactory 打造百万级工件 CI&amp;CD

本篇文章是根据 AWS 发布在 Youtube 上的视频资料翻译并整理而来,介绍的是 AWS re:Invent 2017大会上分享的 HERE Technology 使用 AWS EF 和 JFrog Artifactory 打造百万级工件 CI&CD 系统,支持数千活跃开发者的案例. 本次主讲人有三位,分别是 Suresh Prem(HERE principal system engineer),Yoav Landman(JFrog CTO.Co-Founder)和 Yong Kim(AWS

迅雷链技术沙龙第一站:百万级TPS是怎样炼成的

9月15日下午,由迅雷集团主办的链创未来?迅雷链技术沙龙在北京举行,作为此系列技术沙龙的首期活动,本期邀请了来自迅雷链开放平台产品负责人.研发负责人.研发工程师.HGBC等企业的技术大咖,为区块链爱好者和开发者分享智能合约开发与DAPP实践经验等干货满满的区块链技术知识,上百名开发者大牛在现场自由交流,就区块链技术进行了深入探讨. 张慧勇:迅雷链同构多链框架解析,揭开迅雷链神秘面纱 图:迅雷链开放平台研发负责人 张慧勇 迅雷链开放平台研发负责人张慧勇率先揭开迅雷链的神秘面纱,为现场的开发者解析迅

百万级高并发WebRTC流媒体服务器设计与开发教程云

百万级高并发WebRTC流媒体服务器设计与开发 资源获取链接:点击获取完整教程 百万级高并发WebRTC流媒体服务器设计与开发 5G时代音视频为王,随着实时音视频应用的爆发,来自Google 的WebRTC成为了人们关注的焦点,但很多人却不知道如何使用WebRTC实现多人实时互动,本课就将围绕与浏览器互通.级联.可扩展等6大痛点手把手带你学习大负载.高并发.高性能 WebRTC 流媒体服务器的设计与开发,揭秘万人互动直播背后的深层奥秘,打造可负载百万用户量的企业级的流媒体服务器 播答题的核心需求

高防CDN防御百万级DDoS攻击

企业了解DDoS的攻击方式,他们就必须决定如何应付这种攻击,这是现在几乎不可避免的状况.第一个方法是与一些DDoS防御供应商合作,如集群盾.WAFCDN.高防盾.高防云盾等,这是应对最严重攻击的一个可行方法.这些公司专门研究如何防御和应付可能的恶意流量.然而,如果一个组织没有足够资源购买第三方产品和服务,那么聪明的安全管理员也会采取下面这些步骤,尽量减小DDoS攻击的危害.      首先,安全管理员应该先了解他们组织的互联网连接.正如前提所提到的,一般组织的平均连接带宽为10Gbps,所以管理

MySQL 百万级分页优化(Mysql千万级快速分页)

以下分享一点我的经验 一般刚开始学SQL的时候,会这样写 : SELECT * FROM table ORDER BY id LIMIT 1000, 10; 但在数据达到百万级的时候,这样写会慢死 : SELECT * FROM table ORDER BY id LIMIT 1000000, 10; 也许耗费几十秒 网上很多优化的方法是这样的: SELECT * FROM table WHERE id >= (SELECT id FROM table LIMIT 1000000, 1) LIM

【netty】Netty系列之Netty百万级推送服务设计要点

1. 背景 1.1. 话题来源 最近很多从事移动互联网和物联网开发的同学给我发邮件或者微博私信我,咨询推送服务相关的问题.问题五花八门,在帮助大家答疑解惑的过程中,我也对问题进行了总结,大概可以归纳为如下几类: Netty是否可以做推送服务器? 如果使用Netty开发推送服务,一个服务器最多可以支撑多少个客户端? 使用Netty开发推送服务遇到的各种技术问题. 由于咨询者众多,关注点也比较集中,我希望通过本文的案例分析和对推送服务设计要点的总结,帮助大家在实际工作中少走弯路. 1.2. 推送服务

使用redis缓存加索引处理数据库百万级并发

使用redis缓存加索引处理数据库百万级并发 前言:事先说明:在实际应用中这种做法设计需要各位读者自己设计,本文只提供一种思想.准备工作:安装后本地数redis服务器,使用mysql数据库,事先插入1000万条数据,可以参考我之前的文章插入数据,这里不再细说.我大概的做法是这样的,编码使用多线程访问我的数据库,在访问数据库前先访问redis缓存没有的话在去查询数据库,需要注意的是redis最大连接数最好设置为300,不然会出现很多报错. 贴一下代码吧 1 2 3 4 5 6 7 8 9 10 1

百万级运维心得一:Mongodb和Redis数据不能放在同一个服务器

百万级运维经验一:Mongodb和Redis数据不能放在同一个服务器 一开始时,为了省服务器,把Mongodb和Redis放在一个服务器上.网站每到高峰期都特别卡,还经常出现502.找了很久的原因,发现硬盘的写数据很大,IOPS也很高,排查了很多原因都没找到.然后再仔细研究监控,发现写硬盘的操作很有规律,每隔几分钟就有一次频繁的写硬盘,联想到Redis同步数据到硬盘的间隔就是几分钟,所以开始怀疑是Redis引起的.于是加了一台服务器,把Redis单独放在那里,发现网站瞬间快了,502问题也不再出