本文描述一个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()

后记

这只是一个很小的功能实现,提供给大家参考。如果有不对的地方,希望得到大家指正。

python服务端多进程压测工具的更多相关文章

  1. 问题追查:QA压测工具http长连接总是被服务端close情况

    1. 背景 最近QA对线上单模块进行压测(非全链路压测),http客户端 与 thrift服务端的tcp链接总在一段时间被close. 查看服务端日志显示 i/o timeout. 最后的结果是: q ...

  2. python压测工具Locust

    python压测工具Locust Locust介绍 Locust作为基于Python语言的性能测试框架. 其优点在于他的并发量可以实现单机10倍于LoadRunner和Jmeter工具.他的工作原理为 ...

  3. 常用的HTTP服务压测工具介绍

    常用的HTTP服务压测工具介绍 在项目正式上线之前,我们通常需要通过压测来评估当前系统能够支撑的请求量.排查可能存在的隐藏bug,同时了解了程序的实际处理能力能够帮我们更好的匹配项目的实际需求,节约资 ...

  4. [软件测试]网站压测工具Webbench源码分析

    一.我与webbench二三事 Webbench是一个在linux下使用的非常简单的网站压测工具.它使用fork()模拟多个客户端同时访问我们设定的URL,测试网站在压力下工作的性能.Webbench ...

  5. 网站(Web)压测工具Webbench源码分析

    一.我与webbench二三事 Webbench是一个在linux下使用的非常简单的网站压测工具.它使用fork()模拟多个客户端同时访问我们设定的URL,测试网站在压力下工作的性能.Webbench ...

  6. 压测工具Locuse的使用

    我是听朋友提起的"蝗虫"(Locust),然而她不想用python,我就拿来试一试~ http的 各种压测工具也已经太多了,所以主要是试试locust在当前比较流行的rpc协议上的 ...

  7. 3. 堪比JMeter的.Net压测工具 - Crank 进阶篇 - 认识bombardier

    目录 堪比JMeter的.Net压测工具 - Crank 入门篇 堪比JMeter的.Net压测工具 - Crank 进阶篇 - 认识yml 堪比JMeter的.Net压测工具 - Crank 进阶篇 ...

  8. 6. 堪比JMeter的.Net压测工具 - Crank 实战篇 - 收集诊断跟踪信息与如何分析瓶颈

    目录 堪比JMeter的.Net压测工具 - Crank 入门篇 堪比JMeter的.Net压测工具 - Crank 进阶篇 - 认识yml 堪比JMeter的.Net压测工具 - Crank 进阶篇 ...

  9. 软件性能测试分析与调优实践之路-JMeter对RPC服务的性能压测分析与调优-手稿节选

    一.JMeter 如何通过自定义Sample来压测RPC服务 RPC(Remote Procedure Call)俗称远程过程调用,是常用的一种高效的服务调用方式,也是性能压测时经常遇到的一种服务调用 ...

随机推荐

  1. 小谈SQL表的连接

    简述SQL连接 SQL连接呢,主要分为以下几种内连接,左连接,右连接,全连接(当然还有很多官方的说法,这里就讲讲最常用的). 既然都叫连接了,那至少要有两个对象,也就是说,至少要有两个表,要怎么样的表 ...

  2. Head First设计模式之状态模式

    一.定义 定义:允许对象在内部状态改变时改变它的行为, 对象看起来好像修改了它的类. 主要解决:对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为. 何时使用:代码中包含大量 ...

  3. XML的解析(DOM以及SAX方式)

    感谢http://blog.csdn.net/redarmy_chen/article/details/12951649(关于SAX解析)以及http://blog.csdn.net/zhangerq ...

  4. 简易排水简车的制作 TurnipBit 系列教程

    准备工作   ü TurnipBit 开发板 1块 ü 下载数据线 1条 ü 微型步进电机(28BYJ-48) 1个 ü 步进电机驱动板(ULN2003APG) 1块 ü TurnipBit 扩展板 ...

  5. 我的web聊天之---序章

    有时候自我感觉良好,人啊就开始膨胀,细细想来,自己还是那么苍白.---- 致傻傻的我 大家都知道,平时上班总是拿着手机看看微信,看看新闻,这个不太好,这不是重点,重点是我们公司web版本的微信,QQ都 ...

  6. [UWP]如何使用Fluent Design System (上)

    1. 前言 微软在Build 2017中公布了新的设计语言Fluent Design System(以下简称FDS),不过官网只是堆砌了各种华丽的词语以及一堆动画.至于在UWP中要做成怎么样,怎么做, ...

  7. 强化学习之Sarsa (时间差分学习)

    上篇文章讲到Q-learning, Sarsa与Q-learning的在决策上是完全相同的,不同之处在于学习的方式上 这次我们用openai gym的Taxi来做演示 Taxi是一个出租车的游戏,把顾 ...

  8. Oracle误删数据文件后出现oracle initialization or shutdown in progress解决

    一.错误分析 1.首先本人在出现这种情况的背景是执行如下SQL语句后生成的表空间 --自定义表空间 数据表空间 临时表空间 CREATE TEMPORARY TABLESPACE HOUSE_TEMP ...

  9. 【Dijkstra堆优化】洛谷P2243电路维修

    题目背景 Elf 是来自Gliese 星球的少女,由于偶然的原因漂流到了地球上.在她无依无靠的时候,善良的运输队员Mark 和James 收留了她.Elf 很感谢Mark和James,可是一直也没能给 ...

  10. 浮动(float)与清除浮动(clear)

    上一篇中我们了解了块级元素与内联元素,今天将自己查阅的资料整理写出来,与大家一起理解什么是浮动,以及怎样清除浮动,本文以div元素为例. 浮动的框可以向左或向右移动,直到它的外边缘碰到包含框或另一个浮 ...