使用 Twisted Python 和 Treq 進行 HTTP 壓力測試

oschina發表於2013-05-04

  從事API相關的工作很有挑戰性,在高峰期保持系統的穩定及健壯性就是其中之一,這也是我們在Mailgun做很多壓力測試的原因。

  這麼久以來,我們已經嘗試了很多種方法,從簡單的ApacheBench到複雜些的自定義測試套。但是本貼講述的,是一種使用python進行“快速粗糙”卻非常靈活的壓力測試的方法。

  使用python寫HTTP客戶端的時候,我們都很喜歡用 Requests library。這也是我們向我們的API使用者們推薦的。Requests 很強大,但有一個缺點,它是一個模組化的每執行緒一個呼叫的東西,很難或者說不可能用它來快速的產生成千上萬級別的請求。

  Treq on Twisted簡介

  為解決這個問題我們引入了Treq (Github庫)。Treq是一個HTTP客戶端庫,受Requests影響,但是它執行在Twisted上,具有Twisted典型的強大能力:處理網路I/O時它是非同步且高度併發的方式。

  Treq並不僅僅限於壓力測試:它是寫高併發HTTP客戶端的好工具,比如網頁抓取。Treq很優雅、易於使用且強大。這是一個例子:


>>> from treq import get
 
>>> def done(response):
...     print response.code
...     reactor.stop()
 
>>> get("http://www.github.com").addCallback(done)
 
>>> from twisted.internet import reactor
>>> reactor.run()
200

  簡單的測試指令碼

  如下是一個使用Treq的簡單指令碼,用最大可能量的請求來對單一URL進行轟炸。


#!/usr/bin/env python
from twisted.internet import epollreactor
epollreactor.install()
 
from twisted.internet import reactor, task
from twisted.web.client import HTTPConnectionPool
import treq
import random
from datetime import datetime
 
req_generated = 0
req_made = 0
req_done = 0
 
cooperator = task.Cooperator()
 
pool = HTTPConnectionPool(reactor)
 
def counter():
    '''This function gets called once a second and prints the progress at one
    second intervals.
    '''
    print("Requests: {} generated; {} made; {} done".format(
            req_generated, req_made, req_done))
    # reset the counters and reschedule ourselves
    req_generated = req_made = req_done = 0
    reactor.callLater(1, counter)
 
def body_received(body):
    global req_done
    req_done += 1
 
def request_done(response):
    global req_made
    deferred = treq.json_content(response)
    req_made += 1
    deferred.addCallback(body_received)
    deferred.addErrback(lambda x: None)  # ignore errors
    return deferred
 
def request():
    deferred = treq.post('http://api.host/v2/loadtest/messages',
                         auth=('api', 'api-key'),
                         data={'from': 'Loadtest <test@example.com>',
                               'to': 'to@example.org',
                               'subject': "test"},
                         pool=pool)
    deferred.addCallback(request_done)
    return deferred
 
def requests_generator():
    global req_generated
    while True:
        deferred = request()
        req_generated += 1
        # do not yield deferred here so cooperator won't pause until
        # response is received
        yield None
 
if __name__ == '__main__':
    # make cooperator work on spawning requests
    cooperator.cooperate(requests_generator())
 
    # run the counter that will be reporting sending speed once a second
    reactor.callLater(1, counter)
 
    # run the reactor
    reactor.run()

  輸出結果:


2013-04-25 09:30 Requests: 327 generated; 153 sent; 153 received
2013-04-25 09:30 Requests: 306 generated; 156 sent; 156 received
2013-04-25 09:30 Requests: 318 generated; 184 sent; 154 received

“Generated”類的數字代表被Twisted反應器準備好但是還沒有傳送的請求。這個指令碼為了簡潔性忽略了所有錯誤處理。為它新增超時狀態的資訊就留給讀者作為一個練習。

  這個指令碼可以當做是一個起始點,你可以通過擴充改進它來自定義特定應用下的處理邏輯。建議你在改進的時候用 collections.Counter 來替代醜陋的全域性變數。這個指令碼執行在單執行緒上,想通過一臺機器壓榨出最大量的請求的話,你可以用類似 mulitprocessing 的技術手段。

  願你樂在壓力測試!

相關文章