起源
当开发http 接口的时候,往往我们会关心开发的server能承受多少压力,这时候一个比较常用的工具是 apache bench。一部分情况下ab工具确实能满足需求,但是很多时候并不能,需要分布式式测试工具。
压力测试比较关心的是产生压力
、获取测试对象的如TPS、响应时延等性能数据
和主机资源如cpu,memory消耗数据
以定位性能瓶颈,简单的单机测试工具并不能很好的满足这些需求。
我们可以选择云平台的分布式测试工具,比如腾讯的wetest,阿里云也有类似产品。但是这种产品往往收费不菲。也可以选择类似的开源产品,比如locust。但是调研发现,这种开源产品往往比较简单,或者过于陈旧。google官网有一个压力测试例子,用的就是locust。大家可以看一下这个工具,基于python,功能非常简陋,master,slave模式,不支持在线编辑脚本,修改测试要重启。
如何设计这样的压力测试工具
k8s是目前比较流行的容器编排系统,是否可以在k8s上自己做一个分布式测试工具呢。当然可以用google推荐的做法,在k8s上运行master slave 模式的locust,又或者自己动手做一个。
既然运行在k8s上,那么这个测试工具实际上关心的事就比较简单了:k8s已经实现了调度,资源监控,我们的工具只需要定义脚本,运行脚本,统计测试结果,收集测试过程的资源消耗。
需要说明的是,测试工具运行在k8s上只是为了利用k8s的基础设施,简化工具设计,事实上测试的对象可以是运行在k8s上或者在k8s外的任何服务。
使用golang做测试脚本
选择golang作为测试脚本的原因一是语言成熟,语法简单,二是goroutine很方便,很容易把压力打上去,三是即有编译型语言的高性能,同时又像脚本一样能够快速运行(编译很快)。
golang本身是编译型语言,不是脚本语言,运行要先编译,但是因为编译很快,实际上很容易当成一个脚本来执行,比如这个例子。或者显式的运行go build,go run,像docker/distribution项目的dockerfile这样。那么要实现一个"动态"运行golang的工具要做的就是:定义一种类型的任务,用户的测试脚本只要实现这种任务的interface,客户端就可以装载这种脚本编译运行。具体举例如下。
// 1. 定义一个TTask作为interface
type TTask interface {
Name() string
Run() int
}
// 2. 用户的测试脚本实现这个interface
// New ....
func newhello() task.TTask {
return &hellotask{}
}
type hellotask struct {
}
func (h *hellotask) Name() string {
return "task"
}
func (h *hellotask) Run() int {
time.Sleep(time.Microsecond * 10)
return 0
}
// 3. 实现一种注册式的插件机制,让用户的任务注册进来
var tasksets = make(map[string]NewFunc, 0)
func Register(newfunc NewFunc) {
t := newfunc()
_, registered := tasksets[t.Name()]
if registered {
panic(fmt.Sprintf("TTask named %s already registered", t.Name()))
}
tasksets[t.Name()] = newfunc
}
// 用户脚本中要注册他的脚本
Register(task.NewFunc(newhello))
// 4. 测试客户端不关心用户脚本的实现细节,运行tasksets里面TTask就可以了
func main() {
for _, f := range tasksets {
f().Run()
...
}
}
一个更复杂一点的例子在这里。
使用k8s的基础设施
首先实现测试任务,使用job是很合适不过的,设置parallelism并发运行。
其次要实现任务的动态添加和挂载,可以使用k8s的configmap来实现。使用configmap来保存用户的脚本,运行agent的时候将脚本自动挂载到agent的容器对应路径,容器启动脚本中加入build流程,这样就能很方便的实现一种"动态"的运行golang脚本的效果了。
当压力测试使用多个节点的时候,我们往往需要同时观测测试客户端和服务端的cpu等资源监控,因为客户端已经天然的运行在k8s上了,可以直接使用k8s的监控设施。
另外测试工具的设计并没有局限在测试运行在k8s上的server,但是如果刚好,被测试的对象也运行在k8s上,那么也可以很方便的或者server的宿主机metrics,如果不是,那么server端就需要装一个收集metric的deamon实现同样的效果了。
效果
dashboard支持查看测试任务,每个任务有一个最近运行的记录和创建时间。
编辑一个测试项目,测试脚本是用golang编辑的,需要实现一个TTaskSet 的interface。支持设置任务的goroutine和运行时间,权重,设置权重之后goroutine数量会在多个taskset之间分配,一个taskset又可以添加多个task。taskset并发运行,一个taskset中的task串行运行,这样设计的好处是可以满足用户并发,串行,带context的串行多种需求,非常灵活。任务可以选择运行的节点,多个节点并发测试。
运行完测试的效果,目前还没有加入测试中的client,server资源监控,但是简单的测试统计已经有了。如图是测试的一个运行在1G虚拟机的nginx容器的测试结果。测试结果同时有各个节点的运行结果和汇总结果,同时绘制latency的百分位图。
完整项目地址在 github.com/arlert/ymir 欢迎拍砖。