使用Locust进行性能测试

  • 开篇
    最近因为某些不可抗拒因素:-D,不能在公司使用Loadrunner,找了一下开源的性能测试工具,决定使用Locust.

  • 什么是Locust
    Locust是一种易于使用的、分布式的、用户负载测试工具。它的目的是对web站点(或其他系统)做性能测试,并确定系统能够处理多少并发用户。

    Locust可以在测试过程中,模拟大量Locust实例会访问目标网站。每个Locust(测试用户)的行为是由自己定义的,并且可以从一个web UI中实时监控着这些进程。

    服务端性能测试工具最核心的部分是压力发生器,而压力发生器的核心要点有两个,一是真实模拟用户操作,二是模拟有效并发。

    在Locust测试框架中,测试场景是采用纯Python脚本进行描述的。对于最常见的HTTP(S)协议的系统,Locust采用Python的requests库作为客户端,使得脚本编写大大简化,富有表现力的同时且极具美感。而对于其它协议类型的系统,Locust也提供了接口,只要我们能采用Python编写对应的请求客户端,就能方便地采用Locust实现压力测试。从这个角度来说,Locust可以用于压测任意类型的系统。

    在模拟有效并发方面,Locust的优势在于其摒弃了进程和线程,完全基于事件驱动,使用gevent提供的非阻塞IO和coroutine来实现网络层的并发请求,因此即使是单台压力机也能产生数千并发请求数;再加上对分布式运行的支持,理论上来说,Locust能在使用较少压力机的前提下支持极高并发数的测试。


  • Locust主要功能
    • 不需要使用笨重的UIs或臃肿的xml代码。基于coroutines而不是回调,使用python代码即可实现.
    • 分布式的和可扩展的——支持成千上万的用户
      Locust支持在多台机器上运行负载测试,而且是基于事件驱动,即使是一个Locust节点也可以在一个过程中处理数千个用户。这背后的部分原因是,即使模拟了许多用户,但并不是所有用户都在积极地访问系统。通常,现实情况下,用户会有一个思考时间。每秒请求!=网上用户数量.
    • Locust有一个简洁的HTML + JS用户界面,可以实时显示相关的测试细节。由于UI是基于web的,它是跨平台的,易于扩展。
    • 可以测试任何系统
      尽管Locust是以网络为导向的,但它可以用来测试几乎所有的系统。只要写一个测试脚本,然后执行Locust来测试系统.
    • 可*扩展
      Locust易于扩展,核心的代码不复杂。所有令人心烦的I / O和coroutines都被委托给gevent。

  • 简单示例
from locust import HttpLocust, TaskSet

def login(l):
    l.client.post("/login", {"username":"test_one", "password":"education"})

def index(l):
    l.client.get("/")

def profile(l):
    l.client.get("/profile")

class UserBehavior(TaskSet):
    tasks = {index: 2, profile: 1} #请求比例是index:profile = 2:1

    def on_start(self):
        login(self)

class WebsiteUser(HttpLocust):
    task_set = UserBehavior
    min_wait = 5000 #最小间隔5s
    max_wait = 9000 #最大间隔9s

在这里定义了一些Locust任务(即def login(l),def profile(l),def index(l)),这些任务是正常的Python方法,它们只接受一个参数(Locust类实例)。这些任务在任务属性中的TaskSet类下收集。然后定义一个表示用户的WebsiteUser类,在WebsiteUser类定义模拟用户在执行任务之间应该等待多长时间,以及TaskSet类应该定义用户的“行为”。TaskSet类可以嵌套。
HttpLocust类继承自Locust类,它添加了一个客户端属性(HttpSession的实例),可用于发出HTTP请求。

另一种更方便的定义方式,使用task装饰器:

from locust import HttpLocust, TaskSet, task

class UserBehavior(TaskSet):
    def on_start(self):
        """ on_start is called when a Locust start before any task is scheduled """
        self.login()

    def login(self):
        self.client.post("/login", {"username":"ellen_key", "password":"education"})

    @task(2)
    def index(self):
        self.client.get("/")

    @task(1)
    def profile(self):
        self.client.get("/profile")

class WebsiteUser(HttpLocust):
    task_set = UserBehavior
    min_wait = 5000
    max_wait = 9000

  • 执行
    • 在命令行执行locust -f locust文件名.py --host=http://example.com
    • 要在多个进程中运行Locust,可以通过指定master来启动一个主进程:locust -f locust文件名.py --master --host=http://example.com
      然后开始任意数量的从属过程:locust -f locust文件名.py --slave --host=http://example.com

      如果是要在多台机器上运行Locust,必须在启动slave时指定master的主机IP(当在一台机器上运行Locust时,这是不需要的,因为主主机默认为127.0.0.1):
      locust -f locust文件名.py --slave --master-host=192.168.0.100 --host=http://example.com


  • UI界面
    启动脚本后,可以在浏览器http://localhost:8089/打开界面,在这里定义模拟用户数量用户生成速度:
    使用Locust进行性能测试
    例如,模拟用户数量是20,用户生成速度是2,则会每秒增加两个用户,在十秒钟后并发用户数量达到20.

上面主要介绍了Locust的基本概念,主要功能和一个示例,现在主要进阶的功能.

Locust类


一个locustfile至少包含一个继承了Locust的类.Locust会为被模拟的每个用户生成locust的实例,这些属性是应该被实现的:

  • task_set
    task_set属性应该指向一个TaskSet类, 这个属性定义了用户的一些行为,例如执行任务的间隔时间min_waitmax_wait.(如果不定义,则默认1s)
  • weight
    可以在一个文件里执行两个locusts:
    locust -f locust_file.py WebUserLocust MobileUserLocust
    如果希望让其中一个locust执行得更多,那么以在这些类上设置一个权重属性weight 。比如说,WebUserLocust比MobileUserLocust多三倍:
    class WebUserLocust(Locust):
        weight = 3
        ....
    
    class MobileUserLocust(Locust):
        weight = 1
        ....
    
  • host
    如果在locust类中声明了一个host属性,那么在命令行中没有指定主机时将使用它。

TaskSet类


每个Locust类必须有一个task_set属性,task_set指向一个TaskSet类。TaskSet相当于Locust的大脑.
任务设置任务的典型方法是使用任务修饰器:

from locust import Locust, TaskSet, task

class MyTaskSet(TaskSet):
    @task
    def my_task(self):
        print("Locust instance (%r) executing my_task" % (self.locust))

class MyLocust(Locust):
    task_set = MyTaskSet

taskset的一个非常重要的特性是它们可以被嵌套,因为真正的网站通常是以分层的方式构建的,有多个子节。嵌套taskset将允许我们定义一个行为,以更现实的方式模拟用户。例如,可以用以下结构定义taskset:

class ForumPage(TaskSet):
  @task(20)
  def read_thread(self):
      pass

  @task(1)
  def new_thread(self):
      pass

  @task(5)
  def stop(self):
      self.interrupt()

class UserBehaviour(TaskSet):
  tasks = {ForumPage:10}

  @task
  def index(self):
      pass

在上面的例子中,如果ForumPage在执行UserBehaviour任务集时被选择执行,那么ForumPage任务集就会开始执行。ForumPage任务集将选择它自己的任务之一执行它.

有一个重要的事情需要注意,那就是在ForumPage的stop方法中调用self.interrupt()。这实际上是停止执行ForumPage任务集,但UserBehaviour实例将会继续运行。如果没有调用ForumPage的中断()方法,那么Locust一旦启动就不会停止运行该ForumPage任务。通过使用中断函数,我们可以结合任务权重定义一个模拟用户离开测试网站的可能。

还可以使用@ task decorator声明嵌套的TaskSet,就像声明普通任务时一样:

class MyTaskSet(TaskSet):
  @task
  class SubTaskSet(TaskSet):
      @task
      def my_task(self):
          pass
  • 使用Http请求
    • 到目前为止,只讨论了Locust用户的任务调度部分。为了实际加载测试系统,我们需要发出HTTP请求。为了做到这一点,需要使用HttpLocust类。当使用这个类时,每个实例都会得到一个客户端属性,它将是HttpSession的一个实例,可用于发出HTTP请求。
      在Locust实例化后创建的HttpSession实例。客户端支持cookie,因此可以在HTTP请求之间保持会话。

    • URL里有动态参数的话,可以通过将名称参数传递给HttpSession的不同请求方法来完成。

      #Statistics for these requests will be grouped under: /blog/?id=[id]
      for i in range(10):
          client.get("/blog?id=%i" % i, name="/blog?id=[id]")
      
    • 对共享公共库的多个locustfile进行分组。在这种情况下,重要的是将project root定义为调用locust的目录,并且建议所有的locustfiles都在project root下的某个地方。
      子目录定义方法参见下面的例子,但是locust只会导入于运行的locustfile所处的那个目录的模块。如果是希望从project root(即运行locust命令的位置)导入,请确保在导入任何公共库之前,在locust文件中加入sys.path.append(os.getcwd()),这将使project root(即当前工作目录)是可导入的。

    • project root

      • init.py
      • common/
        • init.py
        • config.py
        • auth.py
      • locustfiles/
        • init.py
        • web_app.py
        • api.py
        • ecommerce.py

      使用上述项目结构,你的本地化文件可以使用以下方法导入公共库:

      sys.path.append(os.getcwd())
      import common.auth
      

分布式运行Locust


如果单主机满足不了并发要求,可以使用多台机器上运行负载测试。

可以使用--master以主模式启动Locust的一个实例。这个实例将运行Locust的web界面,可以在这个web界面开始测试并查看实时统计信息。主节点自身不发送请求。因此,必须启动一个或多个从属节点,使用--slave标记,以及--master-host指定主节点的IP /主机名。

举例:
主模式:locust -f my_locustfile.py --master
从模式:locust -f my_locustfile.py --slave --master-host=192.168.0.14(192.168.0.14是运行主模式的机器ip)

在命令行直接执行,不使用Web UI界面


如果想要在没有web UI的情况下运行locust(例如,想在一些自动化流程中运行它,比如CI服务器), 使用--no-web标志和-c-r参数.
locust -f my_locust_file.py --no-web -c 1000 -r 100

-c指定并发用户总数,-r指定用户生成速率

-t或者--run-time可以指定程序的运行时间.
locust -f --no-web -c 1000 -r 100 --run-time 1h30m

在命令行分布式运行Locust

需要在主节点指定--expect-slaves 参数,以确定需要多少从节点连接.直到达到这个数量的从节点连接上主节点,才会启动测试.

以csv格式保存测试结果

  1. 如果是用Web UI方式启动Locust,则可以在Download Data Tab页面下载csv格式的测试结果
  2. 如果是用命令行方式启动Locust,则可以指定-csv参数,它将定期保存CSV文件.如果打算以自动化的方式在命令行运行Locust,这点特性将特别有用。
    locust -f my_locust_file.py --csv=foobar --no-web -n10 -t10m
    可以这样自定义csv文件定期保存的间隔时间(默认是2s):
    import locust.stats
    locust.stats.CSV_STATS_INTERVAL_SEC = 5 # default is 2seconds
    
上一篇:httprunner学习16-locust性能测试


下一篇:locust性能测试操作步骤