聶永的博客

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

          Tsung筆記之壓測端資源限制篇

          前言

          這里匯集一下影響tsung client創建用戶數的各項因素。因為Tsung是IO密集型的應用,CPU占用一般不大,為了盡可能的生成更多的用戶,需要考慮內存相關事宜。

          IP & 端口的影響

          1. 系統端口限制

          Linux系統端口為short類型表示,數值上限為65535。假設分配壓測業務可用端口范圍為1024 - 65535,不考慮可能還運行著其它對外連接的服務,真正可用端口也就是64000左右(實際上,一般為了方便計算,一般直接設定為50000)。換言之,即在一臺機器上一個IP,可用同時對外建立64000網絡連接。

          若是N個可用IP,理論上 64000*N,實際上還需要滿足:

          • 充足內存支持
            • tcp接收/發送緩沖區不要設置太大,tsung默認分配32K(可以修改成16K,一般夠用了)
            • 一個粗略估算一個連接占用80K內存,那么10萬用戶,將占用約8G內存
          • 為多IP的壓測端分配適合的權重,以便承擔更多的終端連接

          另外還需要考慮端口的快速回收等,可以這樣做:

          sysctl -w net.ipv4.tcp_syncookies=1
          sysctl -w net.ipv4.tcp_tw_reuse=1
          sysctl -w net.ipv4.tcp_tw_recycle=1
          sysctl -w net.ipv4.tcp_fin_timeout=30
          sysctl -w net.ipv4.ip_local_port_range="1024 65535"
          
          sysctl -p
          

          若已經在 /etc/sysctl.conf 文件中有記錄,則需要手動修改

          作為附加,可設置端口重用:

          <option name="tcp_reuseaddr" value="true"/>
          

          注意,不要設置下面的可用端口范圍:

          <option name="ports_range" min="1025" max="65535"/>
          

          因為操作系統會自動跳過已經被占用本地端口,而Tsung只能夠被動通過錯誤進行可用端口+1繼續下一個連接,有些多余。

          2. IP和端口組合

          每一個client支持多個可用IP地址列表

          <client host="client_99" maxusers="120000" weight="2" cpu="8">
              <ip value="10.10.10.99"></ip>
              <ip value="10.10.10.11"></ip>
          </client>
          

          tsung client從節點開始準備建立網絡連接會話時,需要從tsung_controller主節點獲取具體的會話信息,其中就包含了客戶端連接需要使用到來源{LocalIP, LocalPort}二元組。由tsung_controller主節點完成。

          get_user_param(Client,Config)->
              {ok, IP} = choose_client_ip(Client),
              {ok, Server} = choose_server(Config#config.servers, Config#config.total_server_weights),
              CPort = choose_port(IP, Config#config.ports_range),
              {{IP, CPort}, Server}.
          
          choose_client_ip(#client{ip = IPList, host=Host}) ->
              choose_rr(IPList, Host, {0,0,0,0}).
          
          ......
          
          choose_client_ip(#client{ip = IPList, host=Host}) ->
              choose_rr(IPList, Host, {0,0,0,0}).
          
          choose_rr(List, Key, _) ->
              I = case get({rr,Key}) of
                    undefined -> 1 ; % first use of this key, init index to 1
                    Val when is_integer(Val) ->
                      (Val rem length(List))+1 % round robin
              end,
              put({rr, Key},I),
              {ok, lists:nth(I, List)}.
          
          %% 默認不設置 ports_range 會直接返回0
          %% 不建議設置 <option name="ports_range" min="1025" max="65535"/>
          %% 因為這樣存在端口沖突問題,除非確實不存被占用情況
          choose_port(_,_, undefined) ->
              {[],0};
          choose_port(Client,undefined, Range) ->
              choose_port(Client,dict:new(), Range);
          choose_port(ClientIp,Ports, {Min, Max}) ->
              case dict:find(ClientIp,Ports) of
                  {ok, Val} when Val =< Max ->
                      NewPorts=dict:update_counter(ClientIp,1,Ports),
                      {NewPorts,Val};
                  _ -> % Max Reached or new entry
                      NewPorts=dict:store(ClientIp,Min+1,Ports),
                      {NewPorts,Min}
              end.
          

          從節點建立到壓測服務器連接時,就需要指定從主節點獲取到的本機IP地址和端口兩元組:

          Opts = protocol_options(Protocol, Proto_opts)  ++ [{ip, IP},{port,CPort}],
          ......
          gen_tcp:connect(Server, Port, Opts, ConnectTimeout).
          

          3. IP自動掃描特性

          若從機單個網卡綁定了多個IP,又懶于輸入,可以配置掃描特性:

          <ip scan="true" value="eth0"/>
          

          本質上使用shell方式獲取IP地址,并且支持CentOS 6/7。

              /sbin/ip -o -f inet addr show dev eth0
          

          因為掃描比較慢,Tsung 1.6.1推出了ip_range特性支持。

          Linux系統打開文件句柄限制

          系統打開文件句柄,直接決定了可以同時打開的網絡連接數量,這個需要設置大一些,否則,你可能會在tsung_controller@IP.log文件中看到error_connect_emfile類似文件句柄不夠使用的警告,建議此值要大于 > N * 64000。

          echo "* soft nofile 300000" >> /etc/security/limits.conf
          echo "* hard nofile 300000" >> /etc/security/limits.conf
          

          或者,在Tsung會話啟動腳本文件中明確添加上ulimit -n 300000

          內存的影響

          一個網絡Socket連接占用不多,但上萬個或數十萬等就不容小覷了,設置不當會導致內存直接成為屏障。

          1. TCP接收、發送緩存

          Tsung默認設置的網絡Socket發送接收緩沖區為16KB,一般夠用了。

          以TCP為例,某次我手誤為Tcp接收緩存賦值過大(599967字節),這樣每一個網絡了解至少占用了0.6M內存,直接導致在16G內存服務上網絡連接數到2萬多時,內存告急。

          <option name="tcp_snd_buffer" value="16384"></option>
          <option name="tcp_rcv_buffer" value="16384"></option>
          

          此值會覆蓋Linux系統設置接收、發送緩沖大小。

          粗略的默認值計算,一個網絡連接發送緩沖區 + 接收緩沖區,再加上進程處理連接堆棧占用,約40多K內存,為即計算方便,設定建立一個網絡連接消費50K內存。

          先不考慮其它因素,若我們想要從機模擬10W個用戶,那么當前可用內存至少要剩余:50K * 100000 / 1000K = 5000M = 5G內存。針對一般服務器來講,完全可滿足要求(剩下事情就是要有兩個可用IP了)。

          2. Erlang函數堆棧內存占用

          使用Erlang程序寫的應用服務器,進程要存儲堆棧調用信息,進程一多久會占用大量內存,想要服務更多網絡連接/任務,需要將不活動的進程設置為休眠狀態,以便節省內存,Tsung的壓測會話信息若包含thinktime時間,也要考慮啟用hibernate休眠機制。

          <option name="hibernate" value="5"></option>
          

          值單位秒,默認thinktime超過10秒后自動啟動,這里修改為5秒。

          XML文件設置需要注意部分

          1. 日志等級要調高一些

          tsung使用error_logger記錄日志,其只適用于真正的異常情況,若當一般業務調試類型日志量過多時,不但耗費了大量內存,網絡/磁盤寫入速度跟不上生產速度時,會導致進程堵塞,嚴重會拖累整個應用僵死,因此需要在tsung.xml文件中設置日志等級要高一些,至少默認的notice就很合適。

          2. 不要啟用dump

          dump是一個耗時的行為,因此默認為false,除非很少的壓測用戶用于調試。

          3. 動態屬性太多,會導致請求超時

          <option name="file_server" id="userdb" value="/your_path/100w_users.csv"/>
          
          ...
          
          <setdynvars sourcetype="file" fileid="userdb" delimiter=";" order="iter">
              <var name="userid" />
              <var name="nickname" />
          </setdynvars>
          
          ...
          
          <request subst="true">
              <yourprotocol type="hello" uid="%%_userid%%" ack="local">
                  Hello, I'm %%_nickname%%
              </yourprotocol>
          </request>
          

          設定一個有狀態的場景,用戶ID儲存在文件中,每一次會話請求都要從獲取到用戶ID,壓測用戶一旦達到百萬級別并且用戶每秒產生速率過大(比如每秒1000個用戶),會經常遇到超時錯誤:

          =ERROR REPORT==== 25-Jul-2016::15:14:11 ===
          ** Reason for termination =
          ** {timeout,{gen_server,call,
                                  [{global,ts_file_server},{get_next_line,userdb}]}}
          

          這是因為,當tsung client遇到setdynvars指令時,會直接請求主機ts_file_server模塊,當一時間請求量巨大,可能會造成單一模塊處理緩慢,出現超時問題。

          怎么辦:

          1. 降低用戶每秒產生速率,比如300秒用戶生成
          2. 不用從文件中存儲用戶id等信息,采用別的方式

          如何限流/限速

          某些時候,要避免tsung client壓測端影響所在服務器網絡帶寬IO太擁擠,需要限制流量,其采用令牌桶算法。

          <option name="rate_limit" value="1024"></option>
          
          • 值為KB單位每秒
          • 目前僅對傳入流量生效

          閥值計算方式:

          {RateConf,SizeThresh} = case RateLimit of
                                      Token=#token_bucket{} ->
                                          Thresh=lists:min([?size_mon_thresh,Token#token_bucket.burst]),
                                          {Token#token_bucket{last_packet_date=StartTime}, Thresh};
                                      undefined ->
                                          {undefined, ?size_mon_thresh}
                     end,
          

          接收傳入流量數據,需要計算:

          handle_info2({gen_ts_transport, _Socket, Data}, wait_ack, State=#state_rcv{rate_limit=TokenParam}) when is_binary(Data)->
              ?DebugF("data received: size=~p ~n",[size(Data)]),
              NewTokenParam = case TokenParam of
                                  undefined ->
                                      undefined;
                                  #token_bucket{rate=R,burst=Burst,current_size=S0, last_packet_date=T0} ->
                                      {S1,_Wait}=token_bucket(R,Burst,S0,T0,size(Data),?NOW,true),
                                      TokenParam#token_bucket{current_size=S1, last_packet_date=?NOW}
                              end,
              {NewState, Opts} = handle_data_msg(Data, State),
              NewSocket = (NewState#state_rcv.protocol):set_opts(NewState#state_rcv.socket,
                                                                 [{active, once} | Opts]),
              case NewState#state_rcv.ack_done of
                  true ->
                      handle_next_action(NewState#state_rcv{socket=NewSocket,rate_limit=NewTokenParam,
                                                            ack_done=false});
                  false ->
                      TimeOut = case (NewState#state_rcv.request)#ts_request.ack of
                          global ->
                              (NewState#state_rcv.proto_opts)#proto_opts.global_ack_timeout;
                          _ ->
                              (NewState#state_rcv.proto_opts)#proto_opts.idle_timeout
                      end,
                      {next_state, wait_ack, NewState#state_rcv{socket=NewSocket,rate_limit=NewTokenParam}, TimeOut}
              end;
          

          下面則是具體的令牌桶算法:

          %% @spec token_bucket(R::integer(),Burst::integer(),S0::integer(),T0::tuple(),P1::integer(),
          %%                    Now::tuple(),Sleep::boolean()) -> {S1::integer(),Wait::integer()}
          
          %% @doc Implement a token bucket to rate limit the traffic: If the
          %%      bucket is full, we wait (if asked) until we can fill the
          %%      bucket with the incoming data
          %%      R = limit rate in Bytes/millisec, Burst = max burst size in Bytes
          %%      T0 arrival date of last packet,
          %%      P1 size in bytes of the packet just received
          %%      S1: new size of the bucket
          %%      Wait: Time to wait
          %% @end
          token_bucket(R,Burst,S0,T0,P1,Now,Sleep) ->
              S1 = lists:min([S0+R*round(ts_utils:elapsed(T0, Now)),Burst]),
              case P1 < S1 of
                  true -> % no need to wait
                      {S1-P1,0};
                  false -> % the bucket is full, must wait
                      Wait=(P1-S1) div R,
                      case Sleep of
                          true ->
                              timer:sleep(Wait),
                              {0,Wait};
                          false->
                              {0,Wait}
                      end
              end.
          

          小結

          以上簡單梳理一下影響tsung從機創建用戶的各項因素,實際環境其實相當復雜,需要一一對癥下藥才行。

          posted on 2016-07-26 08:47 nieyong 閱讀(4200) 評論(0)  編輯  收藏 所屬分類: 壓測

          公告

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

          新浪微博,歡迎關注:

          導航

          <2016年7月>
          262728293012
          3456789
          10111213141516
          17181920212223
          24252627282930
          31123456

          統計

          常用鏈接

          留言簿(58)

          隨筆分類(130)

          隨筆檔案(151)

          個人收藏

          最新隨筆

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 吴忠市| 景德镇市| 山丹县| 墨竹工卡县| 舒兰市| 贵南县| 上虞市| 清远市| 焉耆| 达孜县| 石台县| 汶上县| 上林县| 六枝特区| 邯郸县| 正宁县| 民和| 松潘县| 嘉义县| 延边| 塔城市| 姚安县| 郓城县| 宕昌县| 和平县| 安达市| 合江县| 广昌县| 孝义市| 乐业县| 江西省| 陆良县| 阳原县| 屯昌县| 都安| 五寨县| 衡南县| 页游| 柳林县| 黄浦区| 义乌市|