locust 主要特点如下:
1) 使用普通的Python脚本用户测试场景
2) 分布式和可扩展,支持成千上万的用户
3) 基于Web的用户界面,用户可以实时监控脚本运行状态
4) 几乎可以测试任何系统,除了web http接口外,还可自定义clients测试其他类型系统
Locust主要由下面的几个库构成:
1) gevent:gevent是一种基于协程的Python网络库,它用到Greenlet提供的,封装了libevent事件循环的高层同步API。
2) flask:Python编写的轻量级Web应用框架。
3) requests:Python Http库
4) msgpack-python:MessagePack是一种快速、紧凑的二进制序列化格式,适用于类似JSON的数据格式。msgpack-python主要提供MessagePack数据序列化及反序列化的方法
5) six:Python2和3兼容库,用来封装Python2和Python3之间的差异性
6) pyzmq:pyzmq是zeromq(一种通信队列)的Python绑定,主要用来实现Locust的分布式模式运行
为什么 locust 的并发机制会比 jmeter 的性能好?
1、进程
进程之间不共享任何状态,每个进程都有自己独立的内存空间,进程间通信主要是通过信号传递的方式来实现的(管道、事件),需要过内核,所以导致通讯效率比较低。由于是独立空间,上下文切换的时候需要保存先调用栈的信息、cpu各寄存器的信息、虚拟内存以及句柄等信息。所以上下文进程切换开销很大
2、线程
线程之间共享变量,但是对于变量的访问需要锁,线程通讯除了可以使用进程之间铜须的方式以外,还可以通过共享内存的方式进行铜须,比通过内核要快很多,调度方面内存也是共享的,所以上下文切换很快,资源开销较少,但相比进程不够稳定容易丢失数据。
3、协程
协程是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。一个线程可以有多个协程,用户创建了几个线程,然后每个线程都是循环按照指定的任务清单顺序完成不同的任务,当任务被堵塞的时候执行下一个任务,当恢复的时候再回来执行该任务,任务之间切换只需要保存每个任务的上下文内容,协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。
总结:
Locust并发机制摈弃了进程和线程,采用协程(gevent)机制。协程避免了系统级资源调度,可以大大提高单机并发能力。
Locust 的性能缺陷
一、GIL
熟悉 Python 的人应该都知道,基于 cpython 编译器的 python 天生就受限于它的全局解释锁 GIL(global interpreter lock),尽管可以使用 jython、pypy 摆脱 GIL 但是很多经典的库在它们上面还没有经过严格的测试。好在 Locust 使用基于 gevent 的协程来实现并发,实际上,使用了 libev 或者 libuv 作为 eventloop 的 gevent 可以极大地提高 Python 的并发能力,拥有不比 JAVA 多线程并发模型差的能力。然而,还是由于 GIL,gevent 也只是释放了单核 CPU 的能力,导致 Locust 的并发能力必须通过起与 CPU 核数相同的 slave 才能发挥出来。
二、性能不佳的 requests 库
Locust 默认使用 requests 作为 http 请求库,了解 requests 库的人,无不惊讶于它设计得如此精妙好用的 API。然而,在性能上却与它的易用性相差甚远,如果需要提高施压能力,可以使用fasthttp,预估能提高 5 倍左右的性能,但是正如 Locust 作者所说的,fasthttp 目前并不能完全替代 requests。
三、rt 评估不准
相信有些人吐槽过,在并发比较大的情况下,Locust 的响应时间 rt 波动比较大,甚至变得不可信。rt 是通过 slave 去统计的,因此并发大导致 slave 不稳定
思考1:为什么线程会有锁,而协程没有锁
Python是一门动态的脚本语言,它有一个锁叫做全局解释器锁,它这个锁是加在cpython解释器上的,我们说的Python多线程,再线程切换的时候加了锁,用了控制同步。所以多线程不是真正意义的并发,而协程是在线程里面的,单个线程并没有锁,一个线程可以有多个协程,协程又叫微线程,它的切换完全由自己创建,它有几种实现方式,一种是yield和send,一种是gevent,一种是greenlet,线程的并发不好,协程可以有上万次并发。因为协程在线程内,而线程本身没有锁,所以协程没有锁。
思考2:既然locust使用协程来构建tcp连接,本身单机并发能力强,为啥很多时候单核测试时,locust得出来的QPS会比Jmeter要低的?
1. 因为locust内部是由requests库的httpclient发起网络请求,requests库功能齐全,但是性能却很一半。好在 Locust 支持分布式,弥补了一定的性能缺陷。根据自己做的测试,同样几台客户机,jmeter搭建分布式测出的 qps 比 Locust分布式 高1/3。如果要提升 locust 单进程性能,可以将 httpclient 的实现方式从 requests 换成 geventhttpclient