Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

opt: decrease healthcheck goroutine #2345

Open
wants to merge 10 commits into
base: issue#2336
Choose a base branch
from

Conversation

alpha-baby
Copy link
Member

Issues associated with this PR

#2336

初始化协程池的逻辑还没来得及写,等有空了再补充

Solutions

You should show your solutions about the issues in your PR, including the overall solutions, details and the changes. At this time, Chinese is allowed to describe these.

UT result

Unit Test is needed if the code is changed, your unit test should cover boundary cases, corner cases, and some exceptional cases. And you need to show the UT result.

Benchmark

If your code involves the processing of every request, you should give the Benchmark Result.

Code Style

  • Make sure Goimports has run
  • Show Golint result

@alpha-baby
Copy link
Member Author

初始化协程池的逻辑我想的是放到 mosn 的启动逻辑里面去初始化,协程池的配置放到 mosn 的启动配置文件里面去

@codecov
Copy link

codecov bot commented Aug 27, 2023

Codecov Report

All modified and coverable lines are covered by tests ✅

📢 Thoughts on this report? Let us know!

Copy link
Member

@doujiang24 doujiang24 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm, please continue

@@ -207,7 +207,7 @@ func (hc *healthChecker) startCheck(host types.Host) {
log.DefaultLogger.Alertf("healthcheck.session", "[upstream] [health check] Create Health Check Session Error, Remote Address = %s", addr)
return
}
c := newChecker(s, host, hc)
c := newChecker(s, host, hc, GetWorkPool())
Copy link
Contributor

@jizhuozhi jizhuozhi Sep 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这没有解决我们的问题,实际上因为212-214依旧会为每个host创建一个生命周期与host不绑定的新的goroutine,所以goroutine泄漏或控制数量的问题依旧没有解决

func (hc *healthChecker) startCheck(host types.Host) {
addr := host.AddressString()
if _, ok := hc.checkers[addr]; !ok {
s := hc.sessionFactory.NewSession(hc.sessionConfig, host)
if s == nil {
log.DefaultLogger.Alertf("healthcheck.session", "[upstream] [health check] Create Health Check Session Error, Remote Address = %s", addr)
return
}
c := newChecker(s, host, hc, GetWorkPool())
hc.checkers[addr] = c
utils.GoWithRecover(func() {
c.Start()
}, nil)
atomic.AddInt64(&hc.localProcessHealthy, 1) // default host is healthy
if log.DefaultLogger.GetLogLevel() >= log.INFO {
log.DefaultLogger.Infof("[upstream] [health check] create a health check session for %s", addr)
}
}
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

为了和之前的功能保持一致,默认情况下就是每次就创建一个协程,
至于泄露问题,只要关闭 ssesionCheck 的时候,遗留的协程能释放就行,你是哪儿看出来现在还能泄露的呢?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

也是你说的那个场景,在2.3w个host时候依旧是有2.3w个goroutine,我们要控制goroutine总数的目标并没有达到,现在还额外的增加了复杂度

@jizhuozhi
Copy link
Contributor

jizhuozhi commented Sep 10, 2023

我不认为在Go中使用协程池是一个好主意,在我们的场景中实际上只需要解决两个问题

  1. 避免出现大量长生命周期的协程作为scheduler,这会导致不可预知的内存泄漏
  2. 避免同时出现大量的协程进行健康检查,这会导致竞争pod资源

所以我们只需要一个生命周期与cluster相同的goroutine作为scheduler,在healthchecker时创建request级别的goroutine,通过一个concurrency配置控制并行度即可

@alpha-baby
Copy link
Member Author

我不认为在Go中使用协程池是一个好主意,在我们的场景中实际上只需要解决两个问题

  1. 避免出现大量长生命周期的协程作为scheduler,这会导致不可预知的内存泄漏
  2. 避免同时出现大量的协程进行健康检查,这会导致竞争pod资源

所以我们只需要一个生命周期与cluster相同的goroutine作为scheduler,在healthchecker时创建request级别的goroutine,通过一个concurrency配置控制并行度即可

假设不用池化协程的方式,还有其他更好的方式吗?

  1. 避免出现大量长生命周期的协程作为scheduler,这会导致不可预知的内存泄漏
    proxy 模块就是有协程池的,池化协程怎么和泄露扯上很强的关系了?
  1. 避免同时出现大量的协程进行健康检查,这会导致竞争pod资源
    那么用户可以配置池大小和检查的频率

所以我们只需要一个生命周期与cluster相同的goroutine作为scheduler,在healthchecker时创建request级别的goroutine,通过一个concurrency配置控制并行度即可

每次检查都创建一个新的协程,应该没有池化性能好,我觉得你这种方式更复杂,反而没池化简单,

当然我觉得很多人都用上池化的健康检查,因为他们的微服务规模都很小,所以默认情况下如果不配置协程池配置。那么就是不使用池化,和之前的功能尽可能保持一致,并简化代码的复杂度。

@alpha-baby
Copy link
Member Author

@doujiang24 @nejisama PTAL

@jizhuozhi
Copy link
Contributor

当然我觉得很多人都用上池化的健康检查,因为他们的微服务规模都很小,所以默认情况下如果不配置协程池配置。那么就是不使用池化,和之前的功能尽可能保持一致,并简化代码的复杂度。

如上,这里协程总数并没有变化,由于go本身非阻塞io的特点,在goroutine内发起io请求是没有影响的,现在的实现依旧是在host维度创建了goroutine做scheduler,然后再提交到一个协程池发起实际请求,这就没有必要了。

当然,如果是在cluster维度配置协程池用来做并发控制是没有问题的,不过现在的协程池使用方式增加了实现的复杂度但是没有看到可解释的收益

@alpha-baby
Copy link
Member Author

当然我觉得很多人都用上池化的健康检查,因为他们的微服务规模都很小,所以默认情况下如果不配置协程池配置。那么就是不使用池化,和之前的功能尽可能保持一致,并简化代码的复杂度。

如上,这里协程总数并没有变化,由于go本身非阻塞io的特点,在goroutine内发起io请求是没有影响的,现在的实现依旧是在host维度创建了goroutine做scheduler,然后再提交到一个协程池发起实际请求,这就没有必要了。

当然,如果是在cluster维度配置协程池用来做并发控制是没有问题的,不过现在的协程池使用方式增加了实现的复杂度但是没有看到可解释的收益

此收益看得见的就是减小很内存,看不见的收益就是协程数减小,可以减少调度开销,当然需要在大规模的 host 的场景下,从性能角度来说,每个 cluster 就一个协程池,还不如全局公用一个协程池,

没太懂你协程数没有变化是什么意思,配置上这个配置后也没有变化?
https://github.com/mosn/mosn/pull/2345/files#diff-abde4948df742aee46d88267578b8a313539692fde0af0c23cec3d26e0859218R49

@doujiang24
Copy link
Member

我的理解,这里主要原因是 putCheckTask 中的 workpool.Submit 默认是阻塞等待,这里就会导致协程数量还是可能很大。

我觉得有两个地方需要限制:

  1. 并发健康检查的数量,这个是目前已经限制了的
  2. 等待健康检查的数量,这个目前是一个等待任务一个协程

我的想法:

  1. 可以搞一个队列(带最大数量限制的)来存等待检查的任务,以减少等待任务的协程数量
  2. 或者,干脆简单点,不搞等待检查的队列,worker pool 里没有了,就直接丢弃

1 和 2,不管是那种方案,最终都是可能丢弃的,想要不丢任务,应该是很难的。需要做的是,丢任务的时候,打点日志

@alpha-baby
Copy link
Member Author

alpha-baby commented Sep 12, 2023

我的理解,这里主要原因是 putCheckTask 中的 workpool.Submit 默认是阻塞等待,这里就会导致协程数量还是可能很大。

我觉得可以默认设置成异步提交,或者不允许配置成同步提交

我觉得有两个地方需要限制:

  1. 并发健康检查的数量,这个是目前已经限制了的
  2. 等待健康检查的数量,这个目前是一个等待任务一个协程

我的想法:

  1. 可以搞一个队列(带最大数量限制的)来存等待检查的任务,以减少等待任务的协程数量
  2. 或者,干脆简单点,不搞等待检查的队列,worker pool 里没有了,就直接丢弃

1 和 2,不管是那种方案,最终都是可能丢弃的,想要不丢任务,应该是很难的。需要做的是,丢任务的时候,打点日志

ants 这个库可以配置队列大小(如果配置了队列好像就不能异步提交了,这个地方我再看看),也可以配置是否阻塞提交,
丢任务是在打警告日志的 workpool.Submit 失败的时候打了,会重启启动超时任务,等待下次提交

@alpha-baby
Copy link
Member Author

我的理解,这里主要原因是 putCheckTask 中的 workpool.Submit 默认是阻塞等待,这里就会导致协程数量还是可能很大。

我觉得有两个地方需要限制:

  1. 并发健康检查的数量,这个是目前已经限制了的
  2. 等待健康检查的数量,这个目前是一个等待任务一个协程

我的想法:

  1. 可以搞一个队列(带最大数量限制的)来存等待检查的任务,以减少等待任务的协程数量
  2. 或者,干脆简单点,不搞等待检查的队列,worker pool 里没有了,就直接丢弃

1 和 2,不管是那种方案,最终都是可能丢弃的,想要不丢任务,应该是很难的。需要做的是,丢任务的时候,打点日志

ants 这个库我看了下,

针对你说的方案1:

配置里面有个可配置参数:Nonblocking 如果配置为 true 超过协程池的大小,直接丢弃

方案2:

Nonblocking 如果配置为 false ,然后再设置 MaxBlockingTasks 数量,那么就可以限制等待的协程数量,这样就可以防止等待队列中大量的协程处于等待中,导致协程数量过多的问题

@doujiang24
Copy link
Member

MaxBlockingTasks 有个问题:还是用一个等待任务一个协程。
从限制协程数量上来说,跟一个工作任务一个协程,并没有太大的区别

@alpha-baby
Copy link
Member Author

PTAL @doujiang24

Copy link
Member

@doujiang24 doujiang24 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the worker queue is disabled by default, lgtm

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants