用Mochiweb打造百万级Comet应用,第一部分
http://www.iteye.com/topic/267028
原文: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 mochiconntest
cd mochiconntest
之后编辑src/mochiconntest_web.erl
这部分代码(mochiconntest_web.erl)只是接收连接并且每十秒用块传输方式给客户端发送一个初始的欢迎信息。
mochiconntest_web.erl
- -module ( mochiconntest_web) .
- -export ( [ start/1 , stop/0 , loop/2 ] ) .
- %% 外部API
- start( 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) ,
- 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} ) ,
- 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’ ->
- case Path of
- _ ->
- 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);
- after 10000 ->
- Msg = io_lib:format ( "Chunk ~w for id ~s\n " , [ N , Path ] ) ,
- Response :write_chunk ( Msg )
- end ,
- feed( Response , Path , N +1 ) .
- %%内部API
- get_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 = 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中然后运行
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 ] )
- after 0 -> 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
- after T ->
- 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
- after Wait ->
- 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个连接时的测试结果。
用Mochiweb打造百万级Comet应用,第一部分的更多相关文章
- 如何生成每秒百万级别的 HTTP 请求?
第一篇:<如何生成每秒百万级别的 HTTP 请求?> 第二篇:<为最佳性能调优 Nginx> 第三篇:<用 LVS 搭建一个负载均衡集群> 本文是构建能够每秒处理 ...
- MySQL 百万级分页优化
MySQL 百万级分页优化 http://www.jb51.net/article/31868.htm 一般刚开始学SQL的时候,会这样写 : , ; 但在数据达到百万级的时候,这样写会慢死 : , ...
- sqlserver 处理百万级以上的数据处理与优化
一处理百万级以上的数据提高查询速度的方法: 1.应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描. 2.对查询进行优化,应尽量避免全表扫描,首先应 ...
- MySQL百万级、千万级数据多表关联SQL语句调优
本文不涉及复杂的底层数据结构,通过explain解释SQL,并根据可能出现的情况,来做具体的优化,使百万级.千万级数据表关联查询第一页结果能在2秒内完成(真实业务告警系统优化结果).希望读者能够理解S ...
- sql百万级查询优化(转)
< 数据库技术内幕 > 处理百万级以上的数据提高查询速度的方法: 1.应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描. 2.对查询进 ...
- MySQL 百万级分页优化(Mysql千万级快速分页)(转)
http://www.jb51.net/article/31868.htm 以下分享一点我的经验 一般刚开始学SQL的时候,会这样写 复制代码 代码如下: SELECT * FROM table OR ...
- FW 每秒百万级别的 HTTP 请求 sung: 重型的(heavy-duty)、分布式的、多协议测试工具
本文是构建能够每秒处理 3 百万请求的高性能 Web 集群系列文章的第一篇.它记录了我使用负载生成器工具的一些经历,希望它能帮助每一个像我一样不得不使用这些工具的人节省时间. 负载生成器是一些生成用于 ...
- 数据库SQL优化大总结之 百万级数据库优化方案(转载)
网上关于SQL优化的教程很多,但是比较杂乱.近日有空整理了一下,写出来跟大家分享一下,其中有错误和不足的地方,还请大家纠正补充. 这篇文章我花费了大量的时间查找资料.修改.排版,希望大家阅读之后,感觉 ...
- 百万级数据 MySQL处理(转)
转自 http://www.cnblogs.com/win7xt/p/3156334.html 使用MySQL处理百万级以上数据时,不得不知道的几个常识 最近一段时间参与的项目要操作百万级数据量的 ...
随机推荐
- WebForm使用JQuery实现DropDownList无刷新联动
目录(?)[-] 1 JS代码 2 页面相关控件用的是平台封装的控件普通DropDownList也可以 3 后台C代码 注意事项 原来用的微软封装的Ajax控件UpdatePannel和Scri ...
- Python编写Appium测试用例(1)
有段时间没有使用python编写测试用例了,很长时间以来,感觉appium这个测试工具确实不错,今天又重新拿起来,分享一下自己学习的一些用例,欢迎大家一起交流.学习! 1.登录客户端 #coding= ...
- 前端切图|点击按钮div变色
<!DOCTYPE html> <html> <head> <title>点击按钮div变色.html</title> <meta c ...
- Invalid property 'annotatedClasses' of bean class
Invalid property 'annotatedClasses' of bean class 在整合Hibernate和Spring时出现,Invalid property 'annotated ...
- 【 2017 Multi-University Training Contest - Team 9 && hdu 6162】Ch’s gift
[链接]h在这里写链接 [题意] 给你一棵树,每个节点上都有一个权值. 然后给你m个询问,每个询问(x,y,a,b); 表示询问x->y这条路径上权值在[a,b]范围内的节点的权值和. [题解] ...
- 洛谷—— P1765 手机_NOI导刊2010普及(10)
https://www.luogu.org/problem/show?pid=1765#sub 题目描述 一般的手机的键盘是这样的: 1 2 abc 3 def 4 ghi 5 jkl 6 mno 7 ...
- 百度糯米iOSclient登录BUG
环境 设备:iphone5s 网络:WIFI App版本号: 操作步骤 1.进入登录界面 2.输入手机号 3.点击[获取验证码],等待接收验证码后 4.点击[X]退出登录界面 5.反复1-2-3,提示 ...
- html实现返回上一页的几种方法(javaScript:history.go(-1);)
html实现返回上一页的几种方法(javaScript:history.go(-1);) 一.总结: 1.javaScript:history.go(-1); 二.方法 1.通过超链接返回到上一页 & ...
- 对Linux下常用头文件总结
asm.current.h 定义全局项current ,其指向结构体struct task_struct linux/sched.h 定义结构体task_struct ,只要包含此头文件 ...
- autohotkey excel getfullname (ComObjActive)