聊聊ab、wrk、JMeter、Locust這些壓測工具的併發模型差別

原文連接  :https://mp.weixin.qq.com/s?__biz=MzA4NTYwOTE3MQ==&mid=2452533021&idx=1&sn=c4d4ae2b60aed77f6ab820ca4ba6cfe7&chksm=880f50a1bf78d9b751bb420c6f8d4727a5aaa57b862855fe6dd69c9ba7573b961dc4de236a52&token=1367783642&lang=zh_CN

 

 

選擇壓測工具是在選什麼?

壓力測試是測試工程師日常工作中一項比較“有技術含量”的工作,很多人都對這項工作充滿了好奇。除了少數特殊場景得靠自己開發壓測腳本外,大部分壓測工作是可以選用成熟的壓測工具來進行的。壓測工具有非常多,有開源的、有商業化的,我下面羅列一些常見的:

工具項目地址
ApacheBench https://httpd.apache.org/docs/2.4/programs/ab.html
wrk https://github.com/wg/wrk
Apache JMeter https://jmeter.apache.org/
Locust https://locust.io/
K6 https://k6.io/
Artillery https://artillery.io/

除了LoadRunner這種商業壓測工具之外,大部分測試人員在壓測工具的選型時最重要的一點:是否熟悉。這種熟悉往往是出於過往的工作經歷、身邊同事的推薦、網上教程的多寡、腳本語言等因素。比如我在很多年前開始用Locust時,就是因爲我個人擅長Python開發語言,即便在當時幾乎沒有中文教程。

但我在使用Locust一段時間之後,大約在2015年中,我意識到Locust作爲一款壓測工具,其能夠產生的壓力好像遠遠遜色於JMeter之類,於是開始關注壓測工具背後的併發模型,去理解不同壓測工具運行邏輯,嘗試去解釋我看到的性能差異。

同步、異步、阻塞、非阻塞

要講併發模型,我們繞不開以下四個名詞:

  • 同步(Synchronous)
  • 異步(Asynchronous)
  • 阻塞 (Blocking)
  • 非阻塞(Nonblocking)

而且我還要特地指出:目前你能通過搜索引擎找到的、能準確解釋這四個概念的中文資料,是極少的。

我這邊不會班門弄斧地來解釋這四個詞的差別,只是提一些大部分資料中忽視的點:

  • 要區分同步、異步,必須講清楚其所處的層,比如框架、用戶空間、內核、IO模型
  • 同步調用發起後,沒有得到結果不返回,那麼毫無疑問就是被阻塞
  • 異步調用發起後直接返回,毫無疑問,這個進程沒有被阻塞

Operating System Concepts [9th Edition]該書中描述對進程間通信進行了一些描述

 

 

 

也就是說,站在進程通信緯度上來看,阻塞、非阻塞與同步、異步是同義詞,但是需要區分發送方、接收方:

  • 阻塞發送
  • 非阻塞發送
  • 阻塞接受
  • 非阻塞接受

上述不同類型的發送方法和不同類型的接收方法可以自由組合

另外,我們還知道Linux有五種I/O模型:

  1. 阻塞式IO(Blocking I/O)

  2. 非阻塞式IO(Nonblocking I/O)

  3. IO複用(I/O multiplexing)

  • select

  • poll

  • epoll

  • 信號驅動式IO(Signal Driver I/O)

  • 異步IO(Asynchronous I/O)

    • AIO

    以上1-4其實都是同步IO,只有第五種模型纔是異步IO

    瞭解以上這些概念後,我們再來講講文章標題中提到的這些壓測工具背後的併發模型

    基於多線程併發的ab、JMeter

    ab、JMeter分別是用C、Java開發的、基於多線程併發模型的壓測工具,也是目前最流行的開源壓測工具,兩者的工作原理類似,如下圖:

     

     

     

    多線程併發

    • 不管ab還是JMeter,其所謂的虛擬用戶(vuser)就是對應一個線程
    • 在單個線程中,每個請求(query)都是同步調用的,下一個請求要等待前一個請求完成才能進行
    • 一個請求(query)分成三部分:
      • send - 施壓端發送開始,直到承壓端接收完成
      • wait - 承壓端接收完成開始,直至業務處理結束
      • recv - 承壓端返回數據,直至施壓端接收完成
    • 同一線程中連續的兩個請求之間存在等待時間這種概念,即圖中的空白處

    在多線程併發模型下,是不是可以通過不斷增加線程數量生產出更大的壓力?

    答案是否定的。

    事實上一個進程在一個時間點只能執行一個線程,而所謂的併發是指在進程裏不斷切換線程實現了看上去的多個任務的併發,但是線程上下文切換有很高的成本,過多的線程數反而會造成性能的嚴重下滑。

    BIO

    從應用角度來看,基於多線程的併發模型,往往需要設置最大併發數參數,而如果壓測場景需要不斷往上加壓,那這類工具其實挺難應付的。

    wrk爲什麼比ab快?

    wrk是一款很類似ab的壓測工具,同樣是使用C語言開發,不過更加的『現代化』:

    wrk is a modern HTTP benchmarking tool capable of generating significant load when run on a single multi-core CPU. It combines a multithreaded design with scalable event notification systems such as epoll and kqueue.

    我們通過wrk的執行參數來來解釋其併發模型:

    • connections: Connections to keep open
    • threads: Number of threads to use

    wrk的connections接近ab的concurrency的概念,見:https://github.com/wg/wrk/issues/205

    但是在ab中,concurrency即線程數,而wrk卻有單獨的threads參數。

     

     

     

     

     

     

     

    wrk concurrency model

    wrk爲了提升吞吐能力,使用基於epoll的IO複用模型,可以參考下圖(圖中實際爲select),同時爲了減少線程的上下文切換,官方建議thread的數量等同CPU核數,即每一個processor下只運行一個線程,這樣來徹底了擺脫了線程切換的消耗。

    注意了,在這種模型下,施壓端發起請求是阻塞的,它需要等待多個請求一起發送,但是在接收時卻是非阻塞的。

     

     

     

    基於這種併發模型的wrk一定會比ab、jmeter這類強嗎?

    答案是不一定。I/O複用不是銀彈,它並不一定適用所有的壓測場景。外網有wrk、jmeter的對比測試,可以看下這兩種壓測更適合哪些場景:

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    wrk沒有比JMeter更流行的原因可能是缺少GUI,同時其腳本使用lua語言,又比較小衆。

    Locust、Artillery身後的libuv

    Locust是用Python開發的分佈式壓測工具,近年來在國內比較流行。Locust並不是基於Python的多線程,而是coroutine(協程,gevent提供),gevent使用了libev或者libuv作爲eventloop。

    所謂的evetloop(事件循環)可以看下面這張圖來加深理解:

     

     

     

    eventloop

    這裏介紹另外個壓測工具Artillery,熟悉的人應該比較少,它是基於Node.js開發的。

    這裏爲什麼把它跟Locust歸到一起來介紹?瞭解Node.js的人想必已經知道原因了:底層的libuv提供了極強悍的異步IO能力。

     

     

     

     

     

     

    imglibuv

    其事件循環邏輯:

     

     

     

     

     

     

    eventloop

    想深入瞭解libuv可以參考官方文檔,這裏不多展開了:http://docs.libuv.org/en/v1.x/design.html

    不過就我實際使用經驗來看,Locust所能產生的併發匪夷所思的低,該問題有很多人向官方反饋過(包括我自己),我不確定到目前版本該問題是否已經根除。在你不確定的情況下,請謹慎使用Locust,包括其分佈式執行模式。

    橫向比較這些壓測工具

    在我寫這篇文章搜索資料之時,意外發現k6.io的blog上有目前已知壓測工具的綜合對比,非常完善,可以直接戳這裏:https://k6.io/blog/comparing-best-open-source-load-testing-tools

    對於這些開源的壓測工具,我的觀點:

    • JMeter能夠應付大部分壓測場景,即便多線程併發模型不是最高效的
    • 如果你的壓測場景中,需要不斷往上加壓,請嘗試基於異步API的壓測工具,多線程併發滿足不了需求
    • 如果你不滿足以上工具,請接着往下看

    one more thing - Ultron

    除上面介紹的這些併發模型之外,我相信很多人聽過Golang下的goroutine:

    • goroutine可以理解是用戶態線程,goroutine的切換沒有內核開銷
    • 內存佔用小,線程棧空間通常是 2M,goroutine 棧空間最小 2K
    • G-M-P調度模型

    golang爲併發而生,go項目在未經優化的情況下就有很高的併發能力,有人曾經調侃,十年經驗的cpp程序員用各種黑魔法寫出來的服務也未必有golang項目的高併發能力。( 一個段子而已,不要太當真)

    那goroutine這麼強,那有沒有基於goroutine的開源壓測工具?

    開始帶貨了

    我這裏推薦下ultron這個項目: https://github.com/qastub/ultron

    • 基於goroutine的高併發能力,在與wrk、jmeter等工具的對比測試中,併發能力超出jmeter,僅次於wrk
    • goroutine提供的是同步函數的語義,理解簡單
    • 對請求(或事務)進行了高度抽象,方便接入各種協議
    • 提供了Result、Report事件監聽接口,方便擴展,如內置了對Influxdb的支持,可以實時生成報告
    • 支持分佈式執行

    這篇文章寫得戰戰兢兢的,以我目前的知識儲備,很難把這麼大的一個話題講清楚,文中難免有所錯漏之處,請各位看官不吝指出。

    參考資料:

    • 怎樣理解阻塞非阻塞與同步異步的區別?

    • JMeter VS WRK

    • Comparing the best open source load testing tools since 2017!

    發表評論
    所有評論
    還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
    相關文章