性能测试工具 Locust 性能评测及优化详解

上帝De助手 · 2019年07月22日 · 最后由 在路上 回复于 2020年12月25日 · 6421 次阅读

这篇文章是用来补前一篇文章挖的坑,在解析了 Locust 的整体流程之后,还是要回归落地,看看它到底好不好用,能不能用。

性能评测

在《性能测试工具 Locust 源码浅析》中,我们进行了一个主流程的分析。本次我们将对 Locust 进行实际的评测,在具体的评测之前,为了评测结果尽量的准确,我们需要做如下的规约:

  • 服务器端没有性能瓶颈(假设有无限能力)
  • 系统环境没有限制设定(网络连接数无限制,TIME_WAIT 回收及时)
  • 外部环境没有额外消耗(网络监控软件、限流软件没有启动)
  • 网络带宽没有瓶颈
  • 不同待评测工具在同一台机器上进行评测(中间预留足够的资源回收时间)

环境准备

1、压测环境准备

  • 机器配置:4 核 8G
  • 操作系统:CentOS(尽量选择 Linux 系统)
  • 网络环境:千兆局域网
  • 文件句柄数限制设定:65536
  • socket 连接回收时间:30ms

2、服务环境准备

  • 服务端服务:nginx 8 worker 挂载一个静态文件(hello world)
  • 机器配置:4 核 8G
  • 操作系统:CentOS(尽量选择 Linux 系统)
  • 网络环境:千兆局域网
  • 文件句柄数限制设定:65536
  • socket 连接设置:net.ipv4.tcp_tw_reuse=1,net.ipv4.tcp_timestamps=1,net.ipv4.tcp_tw_recycle=1,net.ipv4.tcp_fin_timeout = 30

3、压测工具准备

  • Locust
  • Jmeter
  • ab
  • http_load

压测开始

在同一套环境分别使用不同的工具来进行相同场景的请求,这里只发送一个请求 hello world 的静态文件。不同测试之间停留 10 分钟以上间隔,以保证 2 台机器各自资源的回收。

  • CPU、内存
  • Load Avg(系统队列长度)
  • socket 连接数
  • Window Size(TCP 窗口)

Locust

针对 Locust 先使用单实例进行压测,脚本中设置 min_wait 和 max_wait 均为 0;由于 Locust 使用的是 requests.session 来发起请求,所以默认支持 http 的 keep-alive;在单实例执行完成后,使用 4 实例来进行相同场景的压测。

具体的压测脚本如下:

from locust import HttpLocust, TaskSet, task

class WebsiteTasks(TaskSet):
    @task
    def index(self):
        self.client.get("/")

class WebsiteUser(HttpLocust):
    task_set = WebsiteTasks
    host = "http://10.168.xx.xx"
    min_wait = 0
    max_wait = 0

启动 Locust 的命令如下:

# 单实例
locust -f performance.py --no-web -c 2 -r 2 -t 5s
# 分布式
locust -f performance.py --master
locust -f performance.py --slave
# 访问http://127.0.0.1:8089 启动压测

不同并发和实例的压测结果如下:

注:分布式场景下,locust 停止默认 client 貌似有 bug,web 端停止不了。

Jmeter

对于 Jmeter 工具,首先设置 JVM 堆大小为固定 2G,不设置思考时间,默认勾选 keep-alive。分别使用不同的并发数进行场景压测,最终评测出最优并发用户数和最大 QPS。

Jmeter 的 HTTP 请求设置如下:

启动 Jmeter 的命令如下:

sh jmeter -n -t ../xxx.jmx -l /data/xxxx.jtl

不同并发数下的压测结果如下:

ab

ab 是 apache 服务器中的一个压测工具,如果你不想安装整个 apache,那么你可以直接安装 httpd-tools 即可。ab 可以通过-k 参数开启 keep-alive 模式,同时可以指定并发数和请求总数。

ab 的启动命令及参数如下:

./ab -n 6000000 -c 150 http://10.168.xx.xx/index/index.html

ab 不同并发数下的压测结果如下:

为什么 ab 做了这么多次测试呢?因为本来没有想过能压到这么高的并发。另外会发现使用 keep-alive 性能会提升很高。

http_load

http_load 工具需要下载后在本地编译,由于 http_load 不支持 keep-alive 设置,所以只能指定并发数和请求总数。具体的压测命令如下:

./http_load -p 100 -f 6000000 http://10.168.xx.xx/index/index.html

http_load 不同并发数下的压测结果如下:

因为 http_load 不支持设置 keep-alive,所以它的数据和 ab 不使用 keep-alive 时差不多。

压测说明

由于压测场景比较单一,所以数据只能代表在该场景下,各工具在压测能力上的不同体现。如果换作另外的场景,可能工具之间的性能表现会有所变化。但总体来讲应该不会有太多的可变性。

各工具的压测能力,基本上与其实现的语言执行效率成正比。C > JAVA > Python。另外,在使用 keep-alive 的情况下,确实会提高通信性能。

判定压测工具最大并发能力,在确保手工测试时间与基准时间接近的情况下,依据 QPS 曲线来判定。如果压测的同时手工测试时间明显大于基准时间,则表示服务器先出现了性能问题。

很多工具的响应时间统计显示为 0,所以单纯从工具端获取响应时间是不准的。需要在压测同时人工访问并计时,结合服务器端的 QPS、响应时间等综合来得出。

性能优化

通过上面简单的对几个工具的评测,从这组数据的体现来讲,Locust 是最弱的,Jmeter 和网络上的评测结果接近。但是因为 Locust 属于 Python 系列,所以还是抱着希望来看看 Locust 是否还有优化的潜力。

Locust 优化项

为了尝试给 Locust 进行性能提升,收集并思考从如下几种方式来进行尝试:

  • 思考时间设置为 0(默认为 1 秒,上述已设置)
  • 使用 keep-alive 模式(默认为 keep-alive,待确认是否生效)
  • 替换为 urllib3 基础库(requests 是基于 urllib3 进行的封装)
  • 替换为使用 socket 库发送请求
  • 替换为 go 实现的客户端发送请求

测试 Locust 默认是否为 keep-alive

为了检测是否使用了 keep-alive,可以通过 wireshark 来进行抓包,并查看不同请求是否复用了一个 TCP 连接;如果是则为 keep-alive,否则就不是 keep-alive 模式。

从结果可以看出,requests.session 确实默认是支持 keep-alive 的。所以如果使用 locust 的默认 client,这块是不需要优化的了。

替换为 urllib3 实现 client

因为 requests 底层使用的是 urllib3 库,所以这里我们也尝试直接使用 urllib3 作为 locust 的 client,看在性能上是否有提升。client 代码如下:

import time
import urllib3

from locust import Locust, events
from locust.exception import LocustError
# from requests import Response

class Response:
    def __init__(self, url):
        self.url = url
        self.reason = 'OK'
        self.status_code = 200
        self.data = None

class FastHttpSession:
    def __init__(self, base_url=None):
        self.base_url = base_url
        # self.http = urllib3.PoolManager()
        self.http = urllib3.HTTPConnectionPool(base_url)

    def get(self, path):
        full_path = f'{self.base_url}{path}'
        return self.url_request(full_path)

    def url_request(self, url, name="hello world"):
        rep = Response(url)
        start_time = time.time()

        try:
            # r = self.http.request('GET', url)
            r = self.http.urlopen('GET', url)
            total_time = int((time.time() - start_time) * 1000)
            events.request_success.fire(request_type="urllib3", name=name, response_time=total_time, response_length=0)
        except Exception as e:
            total_time = int((time.time() - start_time) * 1000)
            events.request_failure.fire(request_type="urllib3", name=name, response_time=total_time, exception=e)

        rep.status_code = r.status
        rep.reason = r.reason
        rep.data = r.data
        return rep

class FastHttpLocust(Locust):
    client = None

    def __init__(self):
        super(FastHttpLocust, self).__init__()
        if self.host is None:
            raise LocustError(
                "You must specify the base host. Either in the host attribute in the Locust class, or on the command line using the --host option.")

        self.client = FastHttpSession(base_url=self.host)

从 urllib3 请求时录制的 TCP 通信可以看出,它默认也是使用了 keep-alive 模式。

具体压测执行结果如下:

从压测结果可以看出,使用 urllib3 并发能力增加了将近一倍;不过相比较于其它语言的实现,还是有一定的差距。

替换为 socket 实现 client

本来准备继续使用 socket 来实现 client,但是 TCP 协议编程这块有坑,没有达到理想的效果,这个坑先留着日后再填!

替换为 go 实现 client

在查找 Locust 优化方案的时候,发现已经有人实现了 go 语言的 client。github 地址:https://github.com/myzhan/boomer,安装步骤也很简单,按照项目说明即可很快完成。

使用 go 语言的 client 也很方便,只要把原来启动 slave 的命令替换为启动 go 程序即可。具体命令如下:

locust -f performance.py --master
./http.out --url http://10.168.xx.xx/index/index.html

不同并发数下的压测结果如下:

在 boomer 项目里,实现了 2 个版本的 go 客户端;除了上面的那个,还有一个 fast 版本的,启动命令如下:

locust -f performance.py --master
./fasthttp.out --url http://10.168.xx.xx/index/index.html

不同并发数下的压测结果如下:

注意:普通版本 client 和 fast 版本的对应 go 文件分别为examples/http/client.goexamples/fasthttp/client.go

总结

从当前评测的结果来看,python 实现的客户端在压力生成上并没有优势;而像 ab 这样的工具在场景支持上却不够丰富;如果希望 2 者兼得,那么 go 版本的 locust 客户端或许是个不错的选择!

locust 官方 github 上有一个 issue,相关人员对于 locust 施压能力不足的解释是:“locust 主要解决场景开发效率问题,而不是解决生成压力的问题,因为人效成本远大于硬件的成本”。

如果你也在关注和学习性能,或者有觉得有出入的地方,那么欢迎一起来积极讨论,挖掘出即好用又高效的压测方案!

获取更多关于 Python 和自动化测试的文章,请扫描如下二维码!

共收到 8 条回复 时间 点赞

哥。你这图全挂了,怎么看呀

韩将 回复

哦,我这边浏览是正常的啊!

除了你最后面的二维码,其余的图全挂

这是直接从 CSDN 复制过来的吧。。现在这些网站基本上都做了防盗链的,要么自建图床,要么就手动上传到 testerhome。

arrow 回复

重新上图了

其实我也简单做过 locust 和 jmeter 的对比,楼主 4 核的机器,如果用 locust,不是 master-slave 模式的话,只会使用 1 个核,所以 CPU 使用率 25%,要充分利用机器资源也很简单,起 1 个 master,3 个 slave,slave 对应的 master host 是 127.0.0.1 就行,这样的话性能数据是不会太差的,另外 0.15 以后的版本都支持 FasterHttpLocust 模式,使用的是基于 gevent 的 httpclient,性能上整体来说不会太差

我叫GTD 回复

大哥,请问怎么切换成 FasterHttpLocust 模式?

busgade 回复

如图,可替换为 FastHttpUser 模式

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册