myzhan / boomer

A better load generator for locust, written in golang.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

为什么每次spawn之前要先stop所有的job,而不是像python版那样在已有连接上增加呢?

xyzdev-cell opened this issue · comments

Describe the bug
因为每次stop再重新建立, 之前的连接被清理, 导致最终的请求数不准

case "spawn":
			r.state = stateSpawning
			r.stop()
			r.onSpawnMessage(msg)

参照examples中的tcp写的script, 可以认为就是用的 tcp中的client.go

2022/10/08 15:44:05 Spawning 1 clients immediately
2022/10/08 15:44:06 Spawning 2 clients immediately
2022/10/08 15:44:07 Spawning 3 clients immediately
2022/10/08 15:44:08 Spawning 4 clients immediately
2022/10/08 15:44:09 Spawning 5 clients immediately
2022/10/08 15:44:10 Spawning 6 clients immediately
2022/10/08 15:44:11 Spawning 7 clients immediately
2022/10/08 15:44:12 Spawning 8 clients immediately
2022/10/08 15:44:13 Spawning 9 clients immediately

Spawning 2 clients 把 1clients停了
Spawning 3 clients 时把 2 clients停了

而在python中, 是根据已有worker来新增或减少的

        for user_class_name, user_class_count in user_classes_count.items():
            if self.user_classes_count[user_class_name] > user_class_count:
                user_classes_stop_count[user_class_name] = self.user_classes_count[user_class_name] - user_class_count
            elif self.user_classes_count[user_class_name] < user_class_count:
                user_classes_spawn_count[user_class_name] = user_class_count - self.user_classes_count[user_class_name]

        # call spawn_users before stopping the users since stop_users
        # can be blocking because of the stop_timeout
        self.spawn_users(user_classes_spawn_count)
        self.stop_users(user_classes_stop_count)

嗯,这里的实现确实跟 python 版本不一致。主要是 python 版本依赖 locustfile,master 发消息过来,它以为 worker 是知道 locustfile 里面的 task 定义的。但是编写 boomer task 的时候不需要这个文件。这里需要想想怎么关联起来。

但是目前这个实现,跟你描述的 “重新建立, 之前的连接被清理, 导致最终的请求数不准”,这两者没有直接的关联吧。一般做 TCP 压测的时候,我建议是用预先创建好的连接池,然后 task 从连接池里面取连接来发请求,避免频繁地创建和销毁连接。

取决于
2022/10/08 15:44:05 Spawning 1 clients immediately
2022/10/08 15:44:06 Spawning 2 clients immediately
2022/10/08 15:44:07 Spawning 3 clients immediately
这中间的创建速率, 这里有3个疑问

  1. master 每1秒发一个 spawn 要求, 但是实际 10秒过去, worker并没有每秒发1个直到10个, 而是最终master不发之后才开始创建请求
  2. 如果master发的增加 请求慢一点, worker 实际在请求中间已经开始工作了, 然后再被停止重新开始, 这就是我说的可能导致最终请求数量不准
  3. 为了能统一停止worker任务, 使用了 boomer.Events.Subscribe(boomer.EVENT_SPAWN, func(workers int, spawnRate float64) {}, 但是因为并行的原因(猜测), 触发 boomer.EVENT_STOP 时可能 boomer.EVENT_SPAWN 还没执行, 我增加了一个 WaitGroup 来保证 boomer.EVENT_SPAWN一定会先执行, 但这也拖慢了 spawn 的间隔, 更可能导致 上述疑问2的发生.

实际情况确实会出现在 spawn 过程中,已经有协程开始发送请求的情况,也就是 spawnWorkers 调用了 safeRun 方法,这个方法是阻塞的,直到用户自定义的 Fn 执行完一次时候(通常做法是,用户需要在这个函数内发送请求,接收到响应,然后去 record success / failure,然后 boomer 实例会将请求数量加一)才会返回,然后 spawnWorkers 内由于 for...select,会再去获取 stopChanshutdownChan 信号,如果接收到了信号就会退出这个请求的协程,但是刚完成的请求是可以正常被记录的。
另一方面,每次接收到下一个 spawn 信号,会 close stopChan,而如前所述,相关压测协程的退出,是在执行完这一次请求结束之后,请求数量已经加一,不会丢掉这个请求数

@xyzdev-cell 如果只有请求数量不准这个问题,你可以自行用 atomic 记一下请求数,然后把 runner.go 里面定时上报给 master 的 stats 信息打出来核对一下。

是否有计划将其改成类似于python这种形式,实现spawn的时候,在已有的连接上增加呢。创建一个work的线程池是否可以可以实现这种功能

@kylezk777 需要先澄清一下,连接和 goroutine 数没有直接联系。后续在 goroutine 的增加和减少上,可以考虑做成 locust 那样。

我们目前项目想要实现阶梯性的压测,比如target是1000个goroutine,分阶段达到1000,类似于jmeter那种,目前好像只能通过手动去改变spawn,但是就像这个issue所说的,每次spawn会stop之前所有的,压力会有个骤降,然后再重新for循环开启goroutine,这种效果不是很理想。请问是否有确切的计划,去改善这方面的功能吗

@kylezk777 阶梯性的压测,用 ratelimiter 来控制 RPS 能否满足?还是说一定要通过 goroutine 来实现多个并发?我目前知道要怎么改善,但是因为工作比较满,暂时没时间编码。如果你能完成,欢迎 PR。

改善这块的主要考虑点:

  1. spawn 的阶梯性递增和递减都要支持
  2. 多个 task 在递增和递减时,要按权重来分派
commented

是否考虑引入context,增加worker 就把对应的cancelFunc 加入到cancelFuncList 。需要减少worker 的时候就从cancelFuncList尾部执行对应cancelFunc。
由于需要比对当前worker数量和目标worker数量的差值, 用于执行增加或者减少worker 。会引入锁来保护cancelFuncList以及当前worker 数量。
这样增加和减少worker 会更平滑。
可行的话我可以提pr

commented

目前已经实现。