1. 背景
目前接触以及听说过的压测工具/框架繁多,如jmeter/k6/locust/loadrunner/qload等,每个压测工具都有自己特性和不足,如何选择适合自己的压测工具,简单高效的完成自己的压测目标,是我们应该思考的问题,本文旨在对locust的特性以及实现进行梳理,方便并对qload以及jmeter进行一个简单的横向对比。
locust工具地址:https://github.com/locustio/locust/
locust官方文档:https://locust.io/
2. 特性
- 基于python,requests,zeromq(分布式),coroutine(高并发)的开源(支持二次开发)压测工具;
- 压力实现方式为模拟用户操作+gevent实现并发;
- 设计简单优雅,模块间耦合低,为使用者的二次开发拓展提供极大的便利;
- 支持多场景多协议压测,如restapi/redis/mysql等等,原生支持对http协议的压测;
- 支持定制化结果展示,locust默认使用flask后台上报到原生的web前端,可定制使用es+kibana/prometheus+grafana等;
- 支持多平台,locust-master+boomer-salve的分布式方式提高并发。
3. 使用
支持单机/分布式执行压测,由于python 受限于GIL,导致locust高并发下一言难尽,想要发挥单机性能可选用单机主从的分布式方式执行压测,若还不能满足压力要求可进一步增加执行机节点,采用一主错从的方式,甚至可拓展使用boomer来进一步发挥执行机性能。
脚本编写:
3.1单机
locust -f locustfile.py
3.2 单机主从
locust -f locustfile.py --master
locust -f locustfile.py --woker
3.3 多机主从
locust -f locustfile.py --master
locust -f locustfile.py --woker --master-host=192.168.x.xx
4. 实现
实现与qload较为相似,都采用了模拟用户操作+gevent+节点结果采集上报的方式实现生产压力和结果收集。
4.1 架构
locust与qload架构图如下:
- qload与locust架构相似,qload借助QTA的分布式能力,将任务下发到执行机,locust采用master-salve的方式,借助zeroMQ,在压测准备阶段,指定节点的角色;
- master与slave之间使用pyzmq(zeromq的python实现)建立一对多的连接;
- 通过节点采集器通过flask上报到前端,原生的结果展示较为简单且无法保存,往往我需要自己定制结果展示;
4.2 依赖
- gevent:python协程库,给locust提供并发能力;
- requests:发送http请求,locust重新封装;
- flask:web框架,给locust的压测页面提供后台服务;
- pyzmq+msgpack:做分布式的消息通信。
4.3 模块
从核心代码中看locust原生实现
4.3.1 用例模块
类似qload,通过vu来定义各种协议以及动作权重等。
- User:压测所需要的“用户”,用户的行为由其属性以及方法定义,这个类通常由真正客户端需要的用户类来继承,如HttpUser,实现了支持http协议的客户端用户;
- HttpUser:压测所需要可产生http请求的“用户”,继承于User类,这个类在实例时会创建一个client,用于在请求之间保持用户会话;
- HttpSession:为HttpUser的实例提供client,继承于requests.Session,用于执行http请求和在请求之间保存状态,并且使用上下文管理器的方式定制返回的结果,每个请求都会被记录,用于locust的结果展示;
- TaskSet:定义“任务”组,被“用户”所执行,且TaskSet可以嵌套(支持套娃),可以分配权重,执行时间由定义“用户”的User类的wait_time属性决定。
4.3.2 控制模块
- Runner:通过启停和编排“用户”来进行压测,是DistributedRunner的基类;
- DistributedRunner:和Runner基本一样,但是进行了一系列事件监听的注册,用于测试结果的上报,监听在3.3.5事件钩子中会分析;
- MasterRunner:master节点的Runner核心,本身不会产生任何greenlets,但会与WokerRunner进行连接,控制启停greenlets,并将WokerRunner产生的压力结果进行聚合;
- WokerRunner:salve节点的Runner核心,会与MasterRunner进行连接,被master控制的启停“用户”greenlets,并定期将“用户”生成的数据统计,并上报至WokerRunner。
Runner中的state属性记录节点的状态,master与slave共有7种状态
Runner的状态虽然不多,但实现了master和salve之间的状态同步,控制了压测的启停,注:下图中hatching在最新的locust版本中,由spaning代替
- ready:准备就绪,master和salve启动后默认状态;
- spawning:正在准备压力机,master通知salve准备启动压测,salve过渡到running的一个状态;
- running:执行压测;
- cleanup:执行stopping前的一个状态;
- stopping:正在通知各个salve停止压测;
- stopped:压测已停止;
- missing:状态丢失,master3s没有收到salve就会默认为missing;
4.3.3 通信模块
提供m-s之间的通信能力,封装了zeromq
- BaseSocket:socket基类,封装了zeromq,提供了1:N特性,每个master与salve之间各维持一个tcp连接,提供master命令下发和salve信息上报能力;
- Server:继承BaseSocket,作为压测系统的Server端;
- Clint:继承BaseSocket,作为压测系统的Client端;
- Message:消息的封装,序列化与反序列化,数据是通过WorkerRunner的stats_report上报;
master和salve之间通信的消息类型,共10种,salve发送至master的8种,master发送至slave的3种:
- spawn:只有master发送,开始执行;
- stop:只有master发送,点击停止;
- quit:退出包括异常退出;
- client_ready:salve启动后和压测停止;
- client_stopped:压测完成并发停止后;
- heartbeat:心跳,3s一次;
- stats:压测信息,3s一次;
- spawning:用户准备过程中;
- spawning_complete:用户数据集分配等完成;
- exception:user执行过程中出现error;
4.3.4 统计模块
结果采集/分析,定义数据上报格式等,在master和slave通信中的stats的消息类型,作用是salve给master发送的消息,默认3s上报一次,stats中的数据从哪里来?又存储在什么样的对象中?发送给master后,进行怎样的聚合?
- RequestStats:该类保存请求统计信息 locust/stats.py:187
- 在每一个locust实例中(无论是master还是salve),RequestStats都是单例,包含了单个salve汇总的信息,以及各个请求url或或name的统计信息,在分布式下,每一个salve都会维护一个RequestStats实例,3s周期通过stats_reporter方法将信息发送到master,上报的方式在DistributedRunner实例时通过调用setup_distributed_stats_event_listeners方法,用事件钩子方式进行注册监听,发送完后salve runner的stats会调用reset_all方法进行重置;
- self.total :StatsEntry实例,记录成功率,失败率等等,对于master来说,每3s的周期就会调用extend方法进行累加,周期的调用方法也是通过setup_distributed_stats_event_listeners方法;
- self.entities:字典,key为(name, method),value为StatsEntry实例;
- self.errors:字典,key为name+method+error封装的哈希值,value为StatsError实例;
- StatsEntry:表示单个统计项(名称和方法) locust/stats.py:614
StatsError:统计错误信息 locust/stats.py:671
4.3.5 事件钩子
- EventHook:给locust不同类型的事件提供钩子
Events:事件集合
钩子实现原理
定义处理函数 --> add_lisener注册到eventhook --> 触发执行eventhook -->hook的fire 遍历执行处理函数
钩子的使用方式,原生eg:
1. 常规方式
2. 使用装饰器
5. 对比
便捷度:jemter > qload > locust
工具栈:jemter > locust/qload
并发能力:locust(boomer) > qload > jemter
可拓展性:locust > qload > jemter