轉載:http://www.tianqidev.com/
之前發帖抱怨過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基本都是這個原理,好看看測試效果如何,先貼下測試代碼
| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061 |
-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. |
運行結果:
| 123456789101112131415161718192021222324252627 |
ib_test:start(10,1000).=================ibrowse http client performance report=============process #5 send 1000 request spent 2947 msprocess #3 send 1000 request spent 2946 msprocess #2 send 1000 request spent 2946 msprocess #8 send 1000 request spent 2947 msprocess #10 send 1000 request spent 2945 msprocess #9 send 1000 request spent 2947 msprocess #1 send 1000 request spent 2947 msprocess #4 send 1000 request spent 2946 msprocess #6 send 1000 request spent 2948 msprocess #7 send 1000 request spent 2946 mstotal spent=29465 ms,avg spent=2946 ms===================================================================== ib_test:start(10000,10).=================ibrowse http client performance report=============......process #20 send 10 request spent 954 msprocess #1418 send 10 request spent 1173 msprocess #5189 send 10 request spent 1165 msprocess #5859 send 10 request spent 1207 msprocess #9278 send 10 request spent 1173 msprocess #9826 send 10 request spent 1187 mstotal spent=11491457 ms,avg spent=1149 ms===================================================================== |
基本發現開大量進程(10000)進行測試,這時調高預設的max_session(預設10)反而降低了效能
這個基本根據應用情境可以自己進行調整到合適的值
結論:這東西比inet好太多了,inet開幾百個進程測試自己就crash了,而ibrowse開一萬進程每個進程
抓取10次頁面,平均每個花掉1s左右時間,應該是可以接受的,適當調優參數應該還可以提高
ps: 我的測試頁面在單個進程,單個請求花費的時間在1ms內,是一個靜態頁
另外提一下,這個ibrowse在讀取完設定檔後,其他進程在擷取設定檔時並不是發訊息的哦,
而是直接從public 的ets表裡取的,這個在erlang大會上,yufeng老大特別提到,是不是體現了
小訊息大計算的思想呢,接收訊息端運算的成本如果遠遠小於訊息發送的成本,那麼就不要發訊息