sysbench 用 Go 实现 -- 定义DSL

2020-02-10

sysbench 是一个常用的性能测试工具,比如用来测试数据库的性能。它是用 C 和 lua 写的,可以通过 lua 脚本进行扩展,如果想自己改一些配置参数或者改一下测试,都比较灵活。

不过 lua 我并不太习惯,平时还是写 Go 更顺手一些。于是问题来了,如果我用 Go 来实现一套 sysbench,如何达到同样的灵活性呢? 毕竟 Go 是一门静态编译型的语言,能不能达到像脚本一样的灵活性。 这里主要思考的是:

  • 提供怎样的使用形态
  • 如果做到轻松扩展

比较确定的是,应该做成一个框架。库,是你在调它,而框架就是它在调你,使用者只在填空。这里特定场景,框架只需要暴露出来,怎么样 Prepare,怎么样 Run 以及 Cleanup。具体的细节,用多少 bench 线程,怎么管理连接的建立和释放,这些应试丢给框架。 然后是业务相关的,建表,测试的表的 schema,数据量的大小,这些部分应该做成由用户决定的。

于是我们可以做一下分类:

  • 第一类是完全静态的,也就是框架写死的部分。特点是逻辑比较固定。
  • 第二类是配置的参数,半动态的。也就是代码确定了,但是细节的值可变。
  • 第三类是完全动态的,跑什么样的代码不确定。

静态配置的比如这么写:

Conn : ConnConfig {
     User: "root",
     Host: "127.0.0.1",
     Port: 3306,
     DB: "test",
}

代码确定的,参数可配置,这很好做...框架是确定的,执行什么代码可配置,这就有点技巧性了。

解决方式是使用 interface。

type RunConfig struct {
     WorkerCount int
     Task TaskRunner
}

注意,这个结构体里面的 Task 字段,它是一个 TaskRunner 接口:

type TaskRunner interface {
     CreateTable()
     InsertData()
     Query()
}

这样我们可以有不同的实现,比如 UpdateRange,SelectRandom,PointGet 等等等,实现不同的 TaskRunner。配置不同,对应的就可以跑不同的测试了。结构体本身也是可以存参数的,一个结构体,就是一个闭包。

比如这样子:

Task: SelectRandom {
      Isolation: SI,
      Range: RandomRange{},
}

或者这样子:

Task: UpdateRange {
      Count: 30,
      Conflict: 50,
}

对,对象是穷人的闭包。只要它们都是实现了 TaskRunner 接口的。

当我们把所有“配置”组合到一起的时候,可以得到一个更完整的例子:

conf := &Config {
     Conn: ConnConfig{
          User: "xxx",
          Host: "127.0.0.1",
          Port: 3306,
          DB: "test",
     },
     Prepare: DefaultPrepare(),
     Run: RunConfig {
          WorkerCount: 4,
          Task: SelectRandom {
                Isolation: SI,
                Range: RandomRange{},
          }
     },
     CleanUp: DefaultCleanUp(),
}

接下来我们要做成配置文件么?比如说

sysbench -config config.json

不不不!我想的是,做成一个 DSL!这个代码本身,既是配置,也描述了怎么运行了。Go 语言里面有 go test 命令,可以直接跑测试的。最后这样子会更酸爽:

package test

import . "github.com/tiancaiamao/sysbench"

func TestT(t *testing.T) {
	conf := &Config {
		Conn: ConnConfig{
			User: "xxx",
			Host: "127.0.0.1",
			Port: 3306,
			DB: "test",
		},
		Prepare: DefaultPrepare(),
		Run: RunConfig {
			WorkerCount: 4,
			Task: SelectRandom {
				Isolation: SI,
				Range: RandomRange{},
			}
		},
		CleanUp: DefaultCleanUp(),
	}
	RunTest(conf)
}

如果不想写配置,最简单的是全部提供默认值,这样就可以

RunTest(DefaultConfig())

自己定义不同的测试,可以修改一部分,比如:

package test

import . "github.com/tiancaiamao/sysbench"

func TestT(t *testing.T) {
	conf := DefaultConfig()
	conf.Run.Task = UpdateRange {}
	RunTest(conf)
}

以上示例都只是想法。坑挖完了,还没填...

sysbenchgolang