Locust性能評測及優化詳解

  • 2019 年 10 月 4 日
  • 筆記

Locust性能評測及優化詳解

這篇文章是用來補前一篇文章挖的坑,在解析了Locust的整體流程之後,還是要回歸落地,看看它到底好不好用,能不能用。

性能評測

在《性能測試工具Locust源碼淺析》中,我們進行了一個主流程的分析。本次我們將對Locust進行實際的評測,在具體的評測之前,為了評測結果盡量的準確,我們需要做如下的規約:

•伺服器端沒有性能瓶頸(假設有無限能力)•系統環境沒有限制設定(網路連接數無限制,TIME_WAIT回收及時)•外部環境沒有額外消耗(網路監控軟體、限流軟體沒有啟動)•網路頻寬沒有瓶頸•不同待評測工具在同一台機器上進行評測(中間預留足夠的資源回收時間)

環境準備

1、壓測環境準備

•機器配置:4核8G•作業系統:CentOS(盡量選擇Linux系統)•網路環境:千兆區域網•文件句柄數限制設定:65536•socket連接回收時間:30ms

2、服務環境準備

•服務端服務:nginx 8 worker掛載一個靜態文件(hello world)•機器配置:4核8G•作業系統:CentOS(盡量選擇Linux系統)•網路環境:千兆區域網•文件句柄數限制設定:65536•socket連接設置:net.ipv4.tcp_tw_reuse=1,net.ipv4.tcp_timestamps=1,net.ipv4.tcp_tw_recycle=1,net.ipv4.tcp_fin_timeout = 30

3、壓測工具準備

•Locust•Jmeter•ab•http_load

壓測開始

在同一套環境分別使用不同的工具來進行相同場景的請求,這裡只發送一個請求hello world的靜態文件。不同測試之間停留10分鐘以上間隔,以保證2台機器各自資源的回收。

•CPU、記憶體•Load Avg(系統隊列長度)•socket連接數•Window Size(TCP窗口)

Locust

針對Locust先使用單實例進行壓測,腳本中設置min_wait和max_wait均為0;由於Locust使用的是requests.session來發起請求,所以默認支援http的keep-alive;在單實例執行完成後,使用4實例來進行相同場景的壓測。

具體的壓測腳本如下:

from locust import HttpLocust, TaskSet, task    class WebsiteTasks(TaskSet):      @task      def index(self):          self.client.get("/")    class WebsiteUser(HttpLocust):      task_set = WebsiteTasks      host = "http://10.168.xx.xx"      min_wait = 0      max_wait = 0

啟動Locust的命令如下:

# 單實例  locust -f performance.py --no-web -c 2 -r 2 -t 5s  # 分散式  locust -f performance.py --master  locust -f performance.py --slave  # 訪問http://127.0.0.1:8089 啟動壓測

不同並發和實例的壓測結果如下:

註:分散式場景下,locust停止默認client貌似有bug,web端停止不了。

Jmeter

對於Jmeter工具,首先設置JVM堆大小為固定2G,不設置思考時間,默認勾選keep-alive。分別使用不同的並發數進行場景壓測,最終評測出最優並發用戶數和最大QPS。

Jmeter的HTTP請求設置如下:

啟動Jmeter的命令如下:

sh jmeter -n -t ../xxx.jmx -l /data/xxxx.jtl

不同並發數下的壓測結果如下:

ab

ab是apache伺服器中的一個壓測工具,如果你不想安裝整個apache,那麼你可以直接安裝httpd-tools即可。ab可以通過-k參數開啟keep-alive模式,同時可以指定並發數和請求總數。

ab的啟動命令及參數如下:

./ab -n 6000000 -c 150 http://10.168.xx.xx/index/index.html

ab不同並發數下的壓測結果如下:

為什麼ab做了這麼多次測試呢?因為本來沒有想過能壓到這麼高的並發。另外會發現使用keep-alive性能會提升很高。

http_load

http_load工具需要下載後在本地編譯,由於http_load不支援keep-alive設置,所以只能指定並發數和請求總數。具體的壓測命令如下:

./http_load -p 100 -f 6000000 http://10.168.xx.xx/index/index.html

http_load不同並發數下的壓測結果如下:

因為http_load不支援設置keep-alive,所以它的數據和ab不使用keep-alive時差不多。

壓測說明

由於壓測場景比較單一,所以數據只能代表在該場景下,各工具在壓測能力上的不同體現。如果換作另外的場景,可能工具之間的性能表現會有所變化。但總體來講應該不會有太多的可變性。

各工具的壓測能力,基本上與其實現的語言執行效率成正比。C > JAVA > Python。另外,在使用keep-alive的情況下,確實會提高通訊性能。

判定壓測工具最大並發能力,在確保手工測試時間與基準時間接近的情況下,依據QPS曲線來判定。如果壓測的同時手工測試時間明顯大於基準時間,則表示伺服器先出現了性能問題。

很多工具的響應時間統計顯示為0,所以單純從工具端獲取響應時間是不準的。需要在壓測同時人工訪問並計時,結合伺服器端的QPS、響應時間等綜合來得出。

性能優化

通過上面簡單的對幾個工具的評測,從這組數據的體現來講,Locust是最弱的,Jmeter和網路上的評測結果接近。但是因為Locust屬於Python系列,所以還是抱著希望來看看Locust是否還有優化的潛力。

Locust優化項

為了嘗試給Locust進行性能提升,收集並思考從如下幾種方式來進行嘗試:

•思考時間設置為0(默認為1秒,上述已設置)•使用keep-alive模式(默認為keep-alive,待確認是否生效)•替換為urllib3基礎庫(requests是基於urllib3進行的封裝)•替換為使用socket庫發送請求•替換為go實現的客戶端發送請求

測試Locust默認是否為keep-alive

為了檢測是否使用了keep-alive,可以通過wireshark來進行抓包,並查看不同請求是否復用了一個TCP連接;如果是則為keep-alive,否則就不是keep-alive模式。

從結果可以看出,requests.session確實默認是支援keep-alive的。所以如果使用locust的默認client,這塊是不需要優化的了。

替換為urllib3實現client

因為requests底層使用的是urllib3庫,所以這裡我們也嘗試直接使用urllib3作為locust的client,看在性能上是否有提升。client程式碼如下:

import time  import urllib3    from locust import Locust, events  from locust.exception import LocustError  # from requests import Response    class Response:      def __init__(self, url):          self.url = url          self.reason = 'OK'          self.status_code = 200          self.data = None    class FastHttpSession:      def __init__(self, base_url=None):          self.base_url = base_url          # self.http = urllib3.PoolManager()          self.http = urllib3.HTTPConnectionPool(base_url)        def get(self, path):          full_path = f'{self.base_url}{path}'          return self.url_request(full_path)        def url_request(self, url, name="hello world"):          rep = Response(url)          start_time = time.time()            try:              # r = self.http.request('GET', url)              r = self.http.urlopen('GET', url)              total_time = int((time.time() - start_time) * 1000)              events.request_success.fire(request_type="urllib3", name=name, response_time=total_time, response_length=0)          except Exception as e:              total_time = int((time.time() - start_time) * 1000)              events.request_failure.fire(request_type="urllib3", name=name, response_time=total_time, exception=e)            rep.status_code = r.status          rep.reason = r.reason          rep.data = r.data          return rep    class FastHttpLocust(Locust):      client = None        def __init__(self):          super(FastHttpLocust, self).__init__()          if self.host is None:              raise LocustError(                  "You must specify the base host. Either in the host attribute in the Locust class, or on the command line using the --host option.")            self.client = FastHttpSession(base_url=self.host)

從urllib3請求時錄製的TCP通訊可以看出,它默認也是使用了keep-alive模式。

具體壓測執行結果如下:

從壓測結果可以看出,使用urllib3並發能力增加了將近一倍;不過相比較於其它語言的實現,還是有一定的差距。

替換為socket實現client

本來準備繼續使用socket來實現client,但是TCP協議編程這塊有坑,沒有達到理想的效果,這個坑先留著日後再填!

替換為go實現client

在查找Locust優化方案的時候,發現已經有人實現了go語言的client。github地址:https://github.com/myzhan/boomer,安裝步驟也很簡單,按照項目說明即可很快完成。

使用go語言的client也很方便,只要把原來啟動slave的命令替換為啟動go程式即可。具體命令如下:

locust -f performance.py --master  ./http.out --url http://10.168.xx.xx/index/index.html

不同並發數下的壓測結果如下:

在boomer項目里,實現了2個版本的go客戶端;除了上面的那個,還有一個fast版本的,啟動命令如下:

locust -f performance.py --master  ./fasthttp.out --url http://10.168.xx.xx/index/index.html

不同並發數下的壓測結果如下:

注意:普通版本client和fast版本的對應go文件分別為examples/http/client.goexamples/fasthttp/client.go

總結

從當前評測的結果來看,python實現的客戶端在壓力生成上並沒有優勢;而像ab這樣的工具在場景支援上卻不夠豐富;如果希望2者兼得,那麼go版本的locust客戶端或許是個不錯的選擇!

locust官方github上有一個issue,相關人員對於locust施壓能力不足的解釋是:「locust主要解決場景開發效率問題,而不是解決生成壓力的問題,因為人效成本遠大於硬體的成本」。