Python服務端多進程壓測工具

來源:互聯網
上載者:User

Python服務端多進程壓測工具

本文描述一個Python實現的多進程壓測工具,這個壓測工具的特點如下:

  • 多進程

在大多數情況下,壓測一般適用於IO密集型情境(如提供者並等待返回),在這種情境下多線程多進程的區分並不明顯(詳情請參見GIL相關)。不過一旦出現詞表參數加密、返回內容校正等事情的話,多進程對發送效率的提升還是很明顯的。

  • 可以指定發送QPS

可以指定發壓的QPS,根據並行度和請求相應時間,可以估算出可發送QPS峰值。例如並行度是10,回應時間是100ms,那麼QPS峰值應該是(1s/100ms * 10)=100,此工具可以將QPS穩定的維持在小於峰值的一個量上。

  • 便於擴充

為什麼要DIY壓測工具了?一般的服務端壓測工具,例如http_load和jmeter,不是http協議的,就是需要通過代碼進行擴充。例如在壓測thrift介面的時候,即使通過jmeter擴充java程式也很麻煩。但是當涉及到情境化壓測,或者是奇怪的SDK,例如本文要壓測的介面是通過java代碼自動產生的python訊息類SDK,並且涉及到情境化的壓測,很難通過一般的服務端壓測工具搞定。

1、發壓代碼

解耦

下面是壓測代碼的實現,可以看到,我這裡使用abc包,做了一個抽象類別。

業務測試代碼,例如自動化case,只要繼承了這個抽象類別,就獲得壓測的能力,做到壓測和自動化測試的解耦。

這裡有兩個抽象方法

vocab() - 構造詞表

press() - 發壓邏輯

是被@abc.abstractmethod裝飾器裝飾,在子類中,是一定要被實現的。

run()方法是壓測執行的方法,實現子類的詞表方法和發壓邏輯之後,直接調用run()方法就可以壓測了。

固定QPS

固定QPS是通過管理進程實現的。可以看到有兩種進程:

一種是worker_process進程,調用了press()發壓邏輯函數,並且這個進程可以指定並發度concurrent,是實際的發壓進程,值得注意的是在worker_process中使用了time.sleep(),是為了控制發送速度。

另一種是manager_process進程,這個進程每隔一段時間計算實際的qps,並和設定的qps比較,然後調整worker_process中的sleep時間,例如實際qps小於設定qps,那麼就少睡一會兒。

這裡不得不提到的是,多進程如何共用變數?

這裡使用的是multiprocessing中的Manager包,這個包提供了多進程共用變數的能力,我這裡用到的是Namespace資料結構來儲存多進程的計數。在使用過程中我懷疑Manager Namespace是通過讀寫檔案的形式進行進程間共用變數的,這個我沒有深入的研究。

# -*- coding:utf-8 -*-
import abc
import time
from multiprocessing import Lock, Process, Manager


class Press(object):

    __metaclass__ = abc.ABCMeta

    def __init__(self, qps=100, concurrent=10):
        self.qps = qps
        self.concurrent = concurrent
        self.mutex = Lock()
        self.local = Manager().Namespace()
        self.local.count = 0
        self.local.sleep = 0.1
        self.manager_gap = 0.5
        self.precision = 0.1
        self.vocab_list = list()
        self.vocab()

    def manager_process(self):
        while True:
            with self.mutex:
                current_qps = self.local.count / self.manager_gap
                self.local.count = 0
                print self.local.sleep, current_qps

            if current_qps < self.qps:
                self.local.sleep = self.local.sleep * (1.0 - self.precision)
            else:
                self.local.sleep = self.local.sleep * (1.0 + self.precision)
            time.sleep(self.manager_gap)

    def worker_process(self):
        while True:
            with self.mutex:
                self.local.count += 1
            time.sleep(self.local.sleep)
            self.press()

    @abc.abstractmethod
    def vocab(self):
        return

    @abc.abstractmethod
    def press(self):
        return

    def run(self):
        processes = [Process(target=self.worker_process) for index in range(self.concurrent)]
        processes.append(Process(target=self.manager_process))
        for process in processes:
            process.start()
        for process in processes:
            process.join()

2、實際壓測

給出一個發壓的例子。分三步~

QueryVmPress繼承了Press類,獲得了發壓能力。

然後實現了vocab方法,構造了詞表。

實現了press方法,這裡是發壓邏輯,可以看到QueryVmScenario.press_vm(vocab),QueryVmScenario放的是自動化case。發壓只是調用了其中的一個介面。這個介面的編寫很複雜,也是為什麼要自己做一個壓測工具的原因。

# -*- coding:utf-8 -*-
import random
from query.query_vm_scenario import QueryVmScenario
from db.vm_dao import Dao as vm_dao
from db.account_dao import Dao as account_dao
from press import Press
from lib import common
from vocab import Vocab

class QueryVmVocab(Vocab):

    def __init__(self):
        Vocab.__init__(self)


class QueryVmPress(Press):

    def __init__(self, qps=100, concurrent=10):
        Press.__init__(self, qps, concurrent)

    def vocab(self):
        for account in account_dao.query_all_account(limit=10):
            account_name = account[1]
            account_password = account[2]
            res = common.login_by_account(account_name, account_password)
            for item in vm_dao.query_vm_by_account(account_name, limit=100):
                vm_uuid = item[1]
                vocab = QueryVmVocab()
                vocab.add('session_uuid', res.inventory.uuid)
                vocab.add('vm_uuid', vm_uuid)
                self.vocab_list.append(vocab)
        return self.vocab_list

    def press(self):
        vocab = self.vocab_list[random.randint(0, len(self.vocab_list)-1)]
        QueryVmScenario.press_vm(vocab)


if __name__ == '__main__':
    QueryVmPress(qps=100, concurrent=10).run()

QueryVmPress(qps=100, concurrent=10).run(),就按照100QPS進行壓測了。

0.1 20.0
0.09 40.0
0.081 60.0
0.0729 80.0
0.06561 60.0
0.059049 80.0
0.0531441 60.0
0.04782969 80.0
0.043046721 80.0
0.0387420489 80.0
0.03486784401 80.0
0.031381059609 100.0
0.0345191655699 80.0
0.0310672490129 88.0
0.0279605241116 92.0
0.0251644717005 100.0
0.0276809188705 80.0
0.0249128269835 100.0
0.0274041096818 100.0
0.03014452065 80.0
0.027130068585 100.0
0.0298430754435 80.0
0.0268587678991 100.0
0.029544644689 92.0

第一列是sleep時間,第二列是實際QPS,可以看到,qps會被動態穩定在設定的值上。

3、混壓

當要做多個介面混壓的時候,可以這樣做。

先寫好單壓的python類,在單壓的代碼裡,可以看到我實現了QueryVmVocab類,表名了詞表的類型,這個類整合自Vocab,Vocab就是一個字典的封裝。

混壓的時候,先將詞表匯總,並且shuffle,然後彈出詞表的時候,使用isinstance判斷詞表的類型,調用不同的發壓函數進行壓測。

vocab的實現

# -*- coding:utf-8 -*-
import abc

class Vocab(object):

    __metaclass__ = abc.ABCMeta

    def __init__(self):
        self.vocab = dict()

    def add(self, key, value):
        self.vocab[key] = value

    def get(self, key):
        return self.vocab.get(key)

    def remove(self, key):
        del self.vocab[key]

混壓的實現

# -*- coding:utf-8 -*-
import random

from press import Press
from query_eip_press import QueryEipPress, QueryEipVocab
from query_image_press import QueryImagePress, QueryImageVocab
from query_snapshot_press import QuerySnapshotPress, QuerySnapshotVocab
from query_vm_press import QueryVmPress, QueryVmVocab

from query.query_eip_scenario import QueryEipScenario
from query.query_image_scenario import QueryImageScenario
from query.query_snapshot_scenario import QuerySnapshotScenario
from query.query_vm_scenario import QueryVmScenario


class MixedPress(Press):

    def __init__(self, qps=100, concurrent=10):
        Press.__init__(self, qps, concurrent)

    def vocab(self):
        self.vocab_list.extend(QueryEipPress().vocab())
        self.vocab_list.extend(QueryImagePress().vocab())
        self.vocab_list.extend(QuerySnapshotPress().vocab())
        self.vocab_list.extend(QueryVmPress().vocab())

    def press(self):
        vocab = self.vocab_list[random.randint(0, len(self.vocab_list)-1)]
        if isinstance(vocab, QueryEipVocab):
            QueryEipScenario.press_eip(vocab)
        elif isinstance(vocab, QueryImageVocab):
            QueryImageScenario.press_image(vocab)
        elif isinstance(vocab, QuerySnapshotVocab):
            QuerySnapshotScenario.press_snapshot(vocab)
        elif isinstance(vocab, QueryVmVocab):
            QueryVmScenario.press_vm(vocab)


if __name__ == '__main__':
    MixedPress(200, 50).run()

後記

這隻是一個很小的功能實現,提供給大家參考。如果有不對的地方,希望得到大家指正。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.