前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >动态QPS压测模型【Go语言】

动态QPS压测模型【Go语言】

作者头像
FunTester
发布2023-08-04 10:37:31
1600
发布2023-08-04 10:37:31
举报
文章被收录于专栏:FunTesterFunTester

之前写Kafka Client Go实践的时候,跟一位粉丝交流,Go语言的channel实现和Java的多线程实现的性能问题。就想做一次两者的性能测试进行对比。可惜Go语言用得少,还没形成快速进行性能测试的基础能力。所以得建设一些基础设施之后才行,今天分享一下,基于Go语言的动态QPS压测模型实现,算是基础能力建设的一部分了。

本文基于上期提到的Go语言的协程池,查到很多资料,有的不建议复用协程。原因主要两点:

  1. 协程本身创建开销非常小,可以忽略。频繁创建和销毁协程并不会导致明显的性能瓶颈。
  2. 协程设计本来基于简单化,使用协程池破坏了使用便捷性

对于第一个观点,以我现在知识和实践经验来说,不是太认同。任何资源的创建和销毁都会消耗资源。假如不适用协程池,那么每一个性能测试任务都消耗一个协程,每秒几万级别的资源创建和销毁事件,总觉得会很大影响施压机的性能。

第二观点,无法反驳,水平不够。就我自己的经验来说,协程池最大的好处是可以控制并发达到控制压测的目的。避免给其他服务造成过载的情况。在实际实践中,限制最大的请求压力,也有很多使用场景,这里不一一叙述。

思路

首先基于Go语言协程池实现,我增加了一个方法,实现将一个任务使用协程池发送N次,也就是添加N次任务即可。这里我复用FunTester在处理大量性能任务的一个经验,就是每次发送任务的数量进行设置。例如:需要任务执行1000次,那么分成100次,每次打包发送10个任务串行。这样可以避免使用更多的线程/协程。增加单个线程/协程每次执行任务的时长,避免下上文切换(协程切换也有成本,没找到对应专业名词),这个做法在Java实践中,效果非常明显。

代码语言:javascript
复制

// ExecuteQps
//  @Description: 执行任务固定次数
//  @receiver pool
//  @param t
//  @param qps
//
func (pool *GorotinesPool) ExecuteQps(t func(), qps int) {
 mutiple := qps / pool.SingleTimes
 remainder := qps % pool.SingleTimes
 for i := 0; i < pool.SingleTimes; i++ {
  pool.Execute(func() {
   for i := 0; i < mutiple; i++ {
    t()
   }
  })
 }
 pool.Execute(func() {
  for i := 0; i < remainder; i++ {
   t()
  }
 })
}

这里的SingleTimes默认值是10,这个根据实际任务执行的速度调整,我自己还没进行对比测试。

接收控制命令

这里依旧使用控制台输入当做控制命令的输入源,方法比较简单。

方法如下:

代码语言:javascript
复制
// HandleInput
//  @Description: 处理控制台输入内容
//  @param handle
//
func HandleInput(handle func(input string) bool) {
 reader := bufio.NewReader(os.Stdin)
 for {
  text, _ := reader.ReadString('\n')
  text = strings.Replace(text, "\n", "", -1)
  if handle(text) {
   break
  }
 }
}

用例Demo

这里有个坑,如果使用Go单元测试无法正常使用控制台输入。所以本次演示使用main方法执行测试用例了。

代码语言:javascript
复制
func main() {
 pool := execute.GetPool(1000, 2, 200, 1)
 var qps int = 1
 go ftool.HandleInput(func(input string) bool {
  put, error := strconv.Atoi(input)
  if error == nil {
   log.Printf("input content: %d", put)
   qps = put
  }
  return false
 })
 for {
  pool.ExecuteQps(func() {
   log.Println(ftool.Date())
  }, qps)
  ftool.Sleep(1000)
 }
 pool.Wait()
}

最后一行,其实有些多余,因为通常使用手动结束进程来结束用例。

通过这个实现,感觉Go语言对使用function当做参数真的非常流畅。这一点Java N个function实现类着实让我有点懵逼。相比之下Groovy的closure也非常丝滑,但由于Groovy语言特性,经常会导致意外错误发生。总结起来,Go语言这方面是真香了。

FunTester原创专题推荐~

-- By FunTester

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2023-07-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 FunTester 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 思路
  • 接收控制命令
  • 用例Demo
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档