聶永的博客

          記錄工作/學習的點點滴滴。

          100萬并發連接服務器筆記之Erlang完成1M并發連接目標

          前言

          使用Erlang語言也寫一個測試和前面大同小異的測試,在100萬個并發連接用戶情況下,就是想觀察一下極顯情況下的表現。
          這個測試使用了優秀的Erlang界的明星框架cowboy,加單易用的接口,避免了我們對HTTP棧再次進行閉門造車。

          測試Erlang服務器

          運行在VMWare Workstation 9中,64位Centos 6.4系統,分配14.9G內存左右,雙核4個線程,服務器安裝Erlang/OTP R16B,最新版本支持異步代碼熱加載,很贊。

          下載安裝

          本系統已經提前安裝JDK,只需要安裝Erlang好了。

          安裝依賴

          
          
          yum install build-essential m4
          yum install openssl
          yum install openssl-devel
          yum install unixODBC
          yum install unixODBC-devel
          yum -y install openssl make gcc gcc-c++ kernel-devel m4 ncurses-devel openssl-devel
          yum install xsltproc fop

          源代碼安裝

          #wget https://elearning.erlang-solutions.com/binaries/sources/otp_src_R16B.tar.gz
          #tar xvf otp_src_R16B.tar.gz
          cd otp_src_R16B
          ./configure --prefix=/usr/local/erlang --enable-hipe --enable-threads --enable-smp-support --enable-kernel-poll
          make
          make install
          

          添加到環境變量中(/etc/profile)

          export ERL_HOME=/usr/local/erlang export PATH=$ERL_HOME/bin:$PATH
          

          保存生效

          source /etc/profile
          

          測試進程創建

          這里拷貝《Erlang程序設計》一書提供的processes.erl源碼,稍作修改。

          創建一百萬個進程,看看大概花費多少時間。

          [yongboy@base erlang]$ erl +P 10240000
          Erlang R16B (erts-5.10.1) [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false]
          Eshell V5.10.1 (abort with ^G)
          1> processes:max(200000).
          Maxmium allowed process is 16777216 Process spawn time=1.1 (2.68) microseconds ok
          2> processes:max(200000).
          Maxmium allowed process is 16777216 Process spawn time=1.7 (2.33) microseconds ok
          3> processes:max(200000).
          Maxmium allowed process is 16777216 Process spawn time=1.55 (2.12) microseconds ok
          4> processes:max(1000000).
          Maxmium allowed process is 16777216 Process spawn time=2.97 (3.967) microseconds ok
          5> processes:max(1000000).
          Maxmium allowed process is 16777216 Process spawn time=2.4 (2.729) microseconds ok
          6> processes:max(1000000).
          Maxmium allowed process is 16777216 Process spawn time=2.19 (2.735) microseconds ok
          7> processes:max(10000000).
          Maxmium allowed process is 16777216 Process spawn time=3.328 (4.2777) microseconds ok
          8> processes:max(10000000).
          Maxmium allowed process is 16777216 Process spawn time=3.144 (3.1361) microseconds ok
          9> processes:max(10000000).
          Maxmium allowed process is 16777216 Process spawn time=3.394 (3.2051) microseconds ok
          

          恩,創建1000萬個進程,每一個進程花費3.4微秒(μs)的CPU時間,相當于4.3微秒(μs)的消耗時間(亦即消耗的真實時間),在一定量的區間內,其值變化,可以看做是一個常量。

          初始化小問題

          Cowboy初始化需要事項

          我們做的簡單程序,使用了非常受歡迎的cowboy框架,其啟動函數:

          cowboy:start_http(my_http_listener, 100,
          [{port, 8000}],
          [{env, [{dispatch, Dispatch}]}]
          ),
          

          Cowboy默認支持1024個連接,服務器端輸出:

          online user 1122 online user 1123 

          停滯于此,后續的連接只能排隊等候了。

          這里設置支持無限個連接好了。

          {max_connections, infinity}
          

          話說,Cowboy為Erlang世界的明星產品,絕對值得一試!

          Erlang默認創建進程限制

          Erlang在我的機器上默認允許創建的線程也是有限的:

          erlang:system_info(process_limit).
          262144
          

          才26萬個,不夠用。在啟動腳本(start.sh)處添加允許創建的最大線程支持:

          #!/bin/sh
          erl +K true +P 10240000 -sname testserver -pa ebin -pa deps/*/ebin -s htmlfilesimple\
          -eval "io:format(\"Server start with port 8000 Success!~n\")."

          腳本啟動后現在在erl shell中測試一下:

          erlang:system_info(process_limit).
          16777216
          

          數量完全夠用了。

          開啟erlang的epoll屬性

          +K true | false 是否開啟kernel poll,就是epoll;
          

          不開啟,測試過程中,在內存完好情況下,經常會有連接失敗情況。

          使用cowboy_req:compact降低內存占用

          一旦你從request對象中獲取到足夠的信息,以后不再獲取其附加屬性時,調用compact/1函數可去除無用屬性,起到節省內存作用。
          程序里面調用如下:

          init(_Any, Req, State) -> NowCount = count_server:welcome(),
          io:format("online user ~p :))~n", [NowCount]),
          output_first(Req),
          Req2 = cowboy_req:compact(Req),
          {loop, Req2, State, hibernate}.
          

          在本例中精測壓縮內存效果不明顯,因為 ,測試端輸出的HTTP頭部壓根就沒有幾個。

          Cowboy無法處理沒有header的HTTP請求

          這里需要牢記,也不能算是BUG,前面的client2.c源碼,就未曾設置HTTP Header元數據,需要做些簡單修改,修改之后的測試端程序文件名為client5.c, 可以到這里 下載client.c

          Cowboy處理長連接

          Cowboy很貼心的提供了cowboy_loop_handler behaviour。在init/3函數中,可以進入休眠狀態,節省內存,消息到達時,被喚醒,值得一贊! 其定義如下: 注意hibernatetimeout參數,按照實際需求返回即可。

          htmlfile_handler示范代碼如下:

          100萬并發連接達成

          測試過程跌跌撞撞的,雖然這中間因為內存問題拋出若干的異常,但也達到100W連接的數量

          online user 1022324 :))
          online user 1022325 :))
          online user 1022326 :))
          online user 1022327 :))
          online user 1022328 :))
          online user 1022329 :))
          online user 1022330 :))
          online user 1022331 :))
          online user 1022332 :))
          online user 1022333 :))
          online user 1022334 :))
          online user 1022335 :))
          online user 1022336 :))
          online user 1022337 :))
          online user 1022338 :))
          

          可以看到狀態信息

          算一下: 14987952K = 14636M
          14987952/1022338 = 14.7K/Connection

          未啟動時的內存情況:

           total used free shared buffers cached
          Mem: 14806 245 14561 0 12 60
          -/+ buffers/cache: 172 14634
          Swap: 3999 0 3999
          

          啟動后的內存占用情況:

           total used free shared buffers cached
          Mem: 14806 435 14370 0 12 60
          -/+ buffers/cache: 363 14443
          Swap: 3999 0 3999
          

          用戶量達到1022338數量后的內存一覽:

           total used free shared buffers cached
          Mem: 14806 14641 165 0 1 5
          -/+ buffers/cache: 14634 172
          Swap: 3999 1068 2931
          

          可以看到,當前內存不夠用了,需要虛擬內存配合了。

          查看一下當前進程的內存占用

          ps -o rss= -p `pgrep -f 'sname testserver'`
          4869520
          

          這樣算起來,系統為每一個進程持有 4869520/1022338 = 4.8K 內存。 這個值只是計算物理內存,實際上連虛擬內存都占用了,估計在4.8K-6.8K之間吧。

          和C語言相比,內存占用相當大,我虛擬機器分配的15G內存,也僅僅處理達到100萬的連接,已經接近極限時,會發現陸陸續續的有連接失敗。

          不得不說的代碼熱加載

          運行時系統的代碼熱加載功能,在這個實例中,通過vi修改了htmlfile_handler.erl文件,主要修改內容如下:

          io:format("online user ~p :))~n", [NowCount]),
          ......
          io:format("offline user ~p :(( ~n", [NowCount]).
          

          執行make,編譯

          [root@base htmlfilesimple]# make
          ==> ranch (get-deps)
          ==> cowboy (get-deps)
          ==> htmlfilesimple (get-deps)
          ==> ranch (compile)
          ==> cowboy (compile)
          ==> htmlfilesimple (compile)
          src/htmlfile_handler.erl:5: Warning: record status is unused
          src/htmlfile_handler.erl:29: Warning: variable 'Reason' is unused
          Compiled src/htmlfile_handler.erl
          

          很好,非常智能的rebar,自動只編譯了htmlfile_handler.erl一個文件,然后通知Erlang的運行環境進行代碼熱替換吧。

          (testserver@base)4> code:load_file(htmlfile_handler). 

          查看日志輸出控制臺,可以看到已經生效,同時也保存著到狀態數據等。

          非常利于運行時調試,即不傷害在線狀態數據,又能即時修改,贊!但生產環境下,一般都是版本切換,OTP的版本切換,測試或馬上修改bug時,著實有些復雜。

          小結

          和C相比,處理相同的事情(100萬并發連接),及其簡單,但Erlang會需要更多的內存,廉價的內存可以滿足,只是我的搭建在Vmware中的虛擬機器已經達到了它所要求的極限。
          完整的源代碼,可點擊這里下載

          posted on 2013-04-28 17:17 nieyong 閱讀(16888) 評論(1)  編輯  收藏 所屬分類: C1M

          評論

          # re: 100萬并發連接服務器筆記之Erlang完成1M并發連接目標 2014-02-20 10:17 西嶺風清

          真巧,去年我用C++開發了一個推送服務器,4月份測完上線使用。

          測試的時候測到了110w并發(2.6.2以下最多能到1024k,在這個之上的kernel單機有辦法改到超過1024k句柄),沒有出現博主出現的掉線情況,內存消耗貌似要小一點,一個連接平均均攤8-10k左右。

          不過C++開發起來真累,維護起來也很麻煩,我看到此文后,深深陷入到造HTTP輪子的自責當中。

          我今年一開始想把它變成JNI+Java方式,碰巧看到博主的做法,到時候試試,謝謝博主。  回復  更多評論   

          公告

          所有文章皆為原創,若轉載請標明出處,謝謝~

          新浪微博,歡迎關注:

          導航

          <2013年4月>
          31123456
          78910111213
          14151617181920
          21222324252627
          2829301234
          567891011

          統計

          常用鏈接

          留言簿(58)

          隨筆分類(130)

          隨筆檔案(151)

          個人收藏

          最新隨筆

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 玉树县| 新营市| 夹江县| 五峰| 普安县| 霍林郭勒市| 沐川县| 大厂| 凤台县| 宣汉县| 临泽县| 同江市| 会同县| 镇安县| 泸西县| 静安区| 锡林郭勒盟| 邻水| 嘉义市| 拉萨市| 松潘县| 彝良县| 濮阳市| 如东县| 富川| 湄潭县| 城口县| 政和县| 屏山县| 贵港市| 寻乌县| 澳门| 东辽县| 景德镇市| 方山县| 洪湖市| 嘉义市| 吕梁市| 崇明县| 吉安市| 新乡县|