3

用 golang 来编写压测工具

 2 years ago
source link: https://myzhan.github.io/posts/write-a-load-testing-tool-in-golang/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

用 golang 来编写压测工具

calendar Mar 1, 2016
clock 2 min read

之前在项目中做服务端性能测试,一般用的都是 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!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK