从零开始用golang编写一个分布式测试工具

3,307 阅读5分钟

起源

当开发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 欢迎拍砖。