之前在项目中做服务端性能测试,一般用的都是 LoadRunner,或者 jmeter。随着接触的项目越来越多,各种各样的代码都有,光传输协议,就有 HTTP,TCP,UDP。有时数据传的不是明文,可能是 protobuf,也可能是 RSA 加密后的密文。继续用 LR 或 jmeter,有点难以招架。

于是,我开始关注 Locust 这个压测工具。与 LR 使用的 C 和 jmeter 使用的 Java 相比,Locust 使用 Python 来编写压测代码,代码量会少很多。且 Locust 本身的概念少,学习成本较低,上手很快。

在实现并发方面,Locust 并没有使用进程、线程来实现,事实上,CPython 的实现也对多线程不友好。于是,Locust 使用 gevent 提供的非阻塞 IO,和 coroutine来实现网络层的并发请求。实际使用中,使用 Locust 编写的压测脚本,单个实例,可以提供 1000 rps 左右的压力,如果还需要更多的压力,挂多几个 slave,也能满足需求。不过我生性爱折腾,一直想着摆脱 CPython 的 GIL,和 gevent 的 monkey_patch(),过年在家无聊,就一直在构思着怎样实现一个性能更好的施压端,用golang 提供的 goroutine,应该是个不错的选择。

Locust 已经实现了 master & slave 的架构,一个 Locust 进程如果运行在 master 模式,那它要做的事情,就只是收集各个 slave 汇报上来的信息,并展示在 web界面上而已。我的想法很简单,用 golang 来实现 slave 这一部分。实现为一个框架,在运行时,按需创建 goroutine 来跑调用方提供的 function,然后定时将信息汇报给 Locust 的 master。

过年回来撸了一周,最后弄了一个 boomer ,已开源。参考 Locust 的 slave 部分代码来实现的,连文件的命名都一致,应该很容易理解。与 Locust 最大的区别,就是用 goroutine,取代了 gevent。golang 本身的性能,也是一个优势。

用 boomer 来编写一个简单的 http 压测例子。

package main

import boomer "github.com/myzhan/boomer"

import (
	"time"
	"net/http"
	"log"
)


func now() int64 {
	return time.Now().UnixNano() / int64(time.Millisecond)
}


func test_http() {
	/*
	一个常规的 HTTP GET 操作,实际使用时,这里放业务自身的处理过程
	只要将处理结果,通过 boomer 暴露出来的函数汇报就行了
	请求成功,类似 Locust 的 events.request_success.fire
	boomer.Events.Publish("request_success", type, name, 处理时间, 响应耗时)
	请求失败,类似 Locust 的 events.request_failure.fire
	boomer.Events.Publish("request_failure", type, name, 处理时间, 错误信息)
	 */
	startTime := now()
	resp, err := http.Get("http://localhost:8080/")
	defer resp.Body.Close()
	endTime := now()
	log.Println(float64(endTime - startTime))
	if err != nil {
		boomer.Events.Publish("request_failure", "demo", "http", 0.0, err.Error())
	}else {
		boomer.Events.Publish("request_success", "demo", "http", float64(endTime - startTime), resp.ContentLength)
	}
}


func main() {

	task := &boomer.Task{
        // Weight 权重,和 Locust 的 task 权重类似,在有多个 task 的时候生效
				// FIXED: 之前误写为Weith
				Weight: 10,
        // Fn 类似于 Locust 的 task
				Fn: test_http,
	}

	/*
	通知 boomer 去执行自定义函数,支持多个
	boomer.Run(task1, task2, task3)
	*/

	boomer.Run(task)

}

运行

# dummy.py 可以在 boomer 代码库里找到,由于 master 不再负责实际的业务逻辑
# 所以 dummy.py 的格式,只要符合 Locust 脚本的规范就行了
locust -f dummy.py --master --master-bind-host=127.0.0.1 --master-bind-port=5557
go run main.go --master-host=127.0.0.1 --master-port=5557

boomer 目前比较完整地实现了 Locust slave 端的功能,在实际使用中,较原生的 Python 实现,有 5-10 倍以上的性能提升。

Locust 官方表示,master 和 slave 间通讯,用 zeromq 会有较大的性能提升,但是我目前并没有感觉到明显的性能差异,可能是我挂的 slave 数量还不够多吧。目前 boomer 还不支持zeromq,如果将来这里确实是个瓶颈,实现起来也很快。

Enjoy!