论坛首页 综合技术论坛

ibrowse可用性测试

浏览 5213 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2009-11-18   最后修改:2009-11-18
之前发帖抱怨过erlang inet http client的性能很糟糕并且有严重bug
litaocheng同学曾建议使用ibrowse,正好目前有项目需要一个http client,
遂下载查看源码学习之(erlang周边项目基本可以放弃只看doc就可以使用的想法)

ibrowse启动首先会读取priv下的配置文件,默认为ibrowse.conf
格式:
{dest, Hostname, Portnumber, MaxSessions, MaxPipelineSize, Options}.

程序是通过file:consult()这个读取的,所以应该知道什么格式了

其实程序中会保存这个配置到一个ets表,
{{max_sessions, Host, Port}, MaxSess},
{{max_pipeline_size, Host, Port}, MaxPipe},
{{options, Host, Port}, Options},

基本就是一个key/value结构,每个host+port都会有上面3项配置,稍候解释上面参数

ibrowse的进程模型基本是这样:

每个host+port都有唯一一个 gen_server进程进行管理,就是ibrowse_lb.erl这个
然后每个ibrowse_lb进程负责对该host+port的请求进行,连接管理工作,
具体的连接请求处理等由ibrowse_http_client.erl进行处理,
也就是说一个ibrowse_http_client就是对应一个物理tcp connection,然后由ibrowse_lb来进行连接管理

ibrowse_lb会根据我们配置的 每个host+port对应的maxsession来决定是否创建新的连接,也就是决定是否启动一个
新的ibrowse_http_client实例,

如果maxsession已经达到最大值,这是ibrowse_lb会根据maxpipline来把请求发给某个ibrowse_http_client实例进行排队处理
当然,选择是根据哪个实例的请求排队量最小,这个也是用一个order_set的ets表保存的特殊数据结构

大部分client基本都是这个原理,好看看测试效果如何,先贴下测试代码
-module(ib_test).

%%
%% Include files
%%

%%
%% Exported Functions
%%
-export([start/2,dotest/3]).

%%
%% API Functions
%%
start(Processes,Loops)->
	ibrowse:start(),
	ets:new(ib,[named_table,set]),
	[spawn(?MODULE,dotest,[self(),X,Loops])||X<-lists:seq(1,Processes)],
	recv(Processes,Loops),
	ets:delete(ib).

recv(Processes,Loops)->
	receive
		{fin,ProcessNum,Elapse}->
			ets:insert(ib,{ProcessNum,Elapse}),
			case ets:info(ib,size) of
				Processes->
					%%got all the resp
				    print_result(Processes,Loops);
				_->
					recv(Processes,Loops)
			end;
		Err->
			io:format("recv unknowen msg~n"),
			recv(Processes,Loops)
	end.

print_result(Processes,Loops)->
	io:format("=================ibrowse http client performance report=============~n"),
	TotalElapse=
	lists:foldr(
	 fun({ProcessNum,Elapse},Acc)->
		io:format("process #~p send ~p request spent ~p ms~n",[ProcessNum,Loops,Elapse]),		
	 	Acc + Elapse
	 end,	 
	 0,
	 ets:tab2list(ib)
	),
	AvgElapse=TotalElapse div Processes,
	io:format("total spent=~p ms,avg spent=~p ms~n",[TotalElapse,AvgElapse]),
	io:format("=====================================================================~n").
	
dotest(From,ProcessNum,Loops)->
	Start=timestamp_in_millinsec(),
	[ibrowse:send_req("http://xxx.xxx.xxx.xxx/refresh",[],get)||X<-lists:seq(1,Loops)],
	Elapse=timestamp_in_millinsec()-Start,
	From!{fin,ProcessNum,Elapse}.
	
timestamp_in_millinsec()->
	{MegaSec,Sec,MicoSec}=erlang:now(),
	MegaSec * 1000000000+Sec*1000+MicoSec div 1000.


运行结果:
ib_test:start(10,1000). 
=================ibrowse http client performance report=============
process #5 send 1000 request spent 2947 ms
process #3 send 1000 request spent 2946 ms
process #2 send 1000 request spent 2946 ms
process #8 send 1000 request spent 2947 ms
process #10 send 1000 request spent 2945 ms
process #9 send 1000 request spent 2947 ms
process #1 send 1000 request spent 2947 ms
process #4 send 1000 request spent 2946 ms
process #6 send 1000 request spent 2948 ms
process #7 send 1000 request spent 2946 ms
total spent=29465 ms,avg spent=2946 ms
=====================================================================


ib_test:start(10000,10). 
=================ibrowse http client performance report=============
...
...
process #20 send 10 request spent 954 ms
process #1418 send 10 request spent 1173 ms
process #5189 send 10 request spent 1165 ms
process #5859 send 10 request spent 1207 ms
process #9278 send 10 request spent 1173 ms
process #9826 send 10 request spent 1187 ms
total spent=11491457 ms,avg spent=1149 ms
=====================================================================


基本发现开大量进程(10000)进行测试,这时调高默认的max_session(默认10)反而降低了性能

这个基本根据应用场景可以自己进行调整到合适的值

结论:这东西比inet好太多了,inet开几百个进程测试自己就crash了,而ibrowse开一万进程每个进程
抓取10次页面,平均每个花掉1s左右时间,应该是可以接受的,适当调优参数应该还可以提高

ps: 我的测试页面在单个进程,单个请求花费的时间在1ms内,是一个静态页

另外提一下,这个ibrowse在读取完配置文件后,其他进程在获取配置文件时并不是发消息的哦,
而是直接从public 的ets表里取的,这个在erlang大会上,yufeng老大特别提到,是不是体现了
小消息大计算的思想呢,接收消息端运算的成本如果远远小于消息发送的成本,那么就不要发消息

   发表时间:2009-11-23   最后修改:2009-11-23
使用中发现另外一个问题,貌似有这种情况发生

大并发条件下,对同一个host+port的请求有可能在对应一个ibrowse_http_client进程里排队等待处理,也就是mailbox里的消息排队处理,

这时假如某次请求服务器端关闭了该条tcp连接,那么client端erlang的tcp driver会通过注册在系统上的事件获知连接关闭事件,然后给client端erlang进程发消息,这条消息本身也可能在进程mailbox里排队,还未处理,所以我的请求仍然可能发消息到这个进程,结果这些请求都会直接返回错误,ibrowse对这种情况是直接返回错误滴

为了印证这种情况,修改了测试代码如下:
-module(ib_test).

%%
%% Include files
%%

%%
%% Exported Functions
%%
-export([start/2,dotest/3]).

%%
%% API Functions
%%
start(Processes,Loops)->
	ibrowse:start(),
	ets:new(ib,[named_table,set]),
	[spawn(?MODULE,dotest,[self(),X,Loops])||X<-lists:seq(1,Processes)],
	recv(Processes,Loops),
	ets:delete(ib).

recv(Processes,Loops)->
	receive
		{fin,ProcessNum,SuccessNum,Elapse}->
			ets:insert(ib,{ProcessNum,SuccessNum,Elapse}),
			case ets:info(ib,size) of
				Processes->
					%%got all the resp
				    print_result(Processes,Loops);
				_->
					recv(Processes,Loops)
			end;
		Err->
			io:format("recv unknowen msg~n"),
			recv(Processes,Loops)
	end.

print_result(Processes,Loops)->
	io:format("=================ibrowse http client performance report=============~n"),
	{TotalElapse,TotalSucc}=
	lists:foldr(
	 fun({ProcessNum,SuccessNum,Elapse},{ElapseAcc,SuccAcc})->
		io:format("process #~p send ~p request,SuccessNum=~p,spent ~p ms~n",[ProcessNum,Loops,SuccessNum,Elapse]),			 	
		{ElapseAcc + Elapse,SuccAcc+SuccessNum}
	 end,	 
	 {0,0},
	 ets:tab2list(ib)
	),
	AvgElapsePerProc=TotalElapse div Processes,	
	AvgElapsePerReq=AvgElapsePerProc div Loops,	
	TotalReq=Processes * Loops,
	io:format("------------------~n"),
	io:format("avg spent per process=~p ms~n",[AvgElapsePerProc]),
	io:format("avg spent per req=~p ms~n",[AvgElapsePerReq]),
	io:format("total req num=~p,success req num=~p~n",[TotalReq,TotalSucc]),
	io:format("=====================================================================~n").
	
dotest(From,ProcessNum,Loops)->
	Start=timestamp_in_millinsec(),
	SuccessNum=
	lists:foldl(
	  fun(X,Acc)->
		Resp=ibrowse:send_req("http://xxx.xxx.xxx.xxx/refresh",[],get),	
	  	case Resp of
			{ok,"200",_Headers,"none-none"}->
				Acc+1;
			_->
				Acc
	    end
	  end, 
	  0,
	  lists:seq(1,Loops)
    ),		
	Elapse=timestamp_in_millinsec()-Start,
	From!{fin,ProcessNum,SuccessNum,Elapse}.
	
timestamp_in_millinsec()->
	{MegaSec,Sec,MicoSec}=erlang:now(),
	MegaSec * 1000000000+Sec*1000+MicoSec div 1000.



测试5000并发结果(我修改了max_sessions配置为100):
ib_test:start(5000,1).

====================================================================
...
process #3584 send 1 request,SuccessNum=1,spent 117 ms
process #3330 send 1 request,SuccessNum=1,spent 140 ms
process #4121 send 1 request,SuccessNum=1,spent 132 ms
process #4798 send 1 request,SuccessNum=1,spent 108 ms
------------------
avg spent per process=91 ms
avg spent per req=91 ms
total req num=5000,success req num=3863
=====================================================================


可以看到,其中有一小半是不成功的,这个ibrowse是返回一个错误的,我没打出来而已

默认的5个max_sessions大约只能处理200左右的并发量

结论:在增大默认max_sessions的情况下ibrowse还是能处理一定的较大并发量的,如果并发量很大,
最好在程序里对请求结果做下判断

另外不建议把max_pipeline设置的太大,大量请求都会在进程里排队处理,遇到连接关闭就比较麻烦


0 请登录后投票
   发表时间:2009-11-25  
ibrowser对Cookie的保存做到好吗? http_client虽然不是非常快 但是特性还是很足的 而且otp官方对inets的库 最近改动很大, 俺是不建议用第三方的。
0 请登录后投票
   发表时间:2009-11-26  
mryufeng 写道
ibrowser对Cookie的保存做到好吗? http_client虽然不是非常快 但是特性还是很足的 而且otp官方对inets的库 最近改动很大, 俺是不建议用第三方的。


恩,cookie那个不太清楚呀,其实我们就是http作为erlang与外部通信的接口,因为外部程序基本都是java的,这样改动量最小
0 请登录后投票
   发表时间:2009-11-27  
我是觉得 httpc挺好的 只是大家挖掘不够而已。。。。
0 请登录后投票
   发表时间:2009-11-27  
mryufeng 写道
我是觉得 httpc挺好的 只是大家挖掘不够而已。。。。


老大说好,那一定是不错的,我回头看一下,另外可以试验下刚刚发布的R13B03那个
0 请登录后投票
   发表时间:2010-03-09  
httpc的那个bug已经修复 你回头再做下性能测试 比较下...
0 请登录后投票
   发表时间:2010-03-09  
mryufeng 写道
httpc的那个bug已经修复 你回头再做下性能测试 比较下...


好的,我回头再测试下

老大你终于又回来了
0 请登录后投票
   发表时间:2010-03-09  
bachmozart 写道
mryufeng 写道
httpc的那个bug已经修复 你回头再做下性能测试 比较下...


好的,我回头再测试下

老大你终于又回来了

呵呵 最近写东西少了...
0 请登录后投票
论坛首页 综合技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics