pool: TaskPool 实现
flycash opened this issue · comments
仅限中文
使用场景
在有些时候,我们需要控制住 goroutine 的数量。例如在实际应用中,我们可能有快慢两种请求。对于慢请求来说,如果我们不限制它所能占据的 goroutine 数量,那么慢请求占据了一个 goroutine 之后一直不会释放,那么就会导致越来越多的 goroutine 被慢任务占据。
极端情况下,可能超过一半的 goroutine 都被慢任务所占据。这部分 goroutine 一直占据着资源在缓慢运行。
因此我们可能希望引入一种隔离机制,将慢任务和快任务分离,让慢任务在数量有限的 goroutine 上运行,而其它快任务则没有这种限制。
例如说我们在做数据迁移的时候,开启 10 个 goroutine 并发迁移不同表的数据。
因此我们需要设计一个任务池,用户提交任务,并且可以控制住并发执行任务的 goroutine 数量。
行业分析
如果你知道有框架提供了类似功能,可以在这里描述,并且给出文档或者例子
这方面的典型设计,就是 Java 的 ExecutorService,也就是 Java 中线程池的公共接口,它的方法可以分成好几个部分:
可以认为,它的接口设计聚焦在解决几个问题:
- 退出:Shutdown 和 ShutdownNow,以及 awaitingShutdown。归根结底就是用户希望在关闭的时候,可能希望尽量执行已经提交的任务,但是又不希望等待太久
- 提交任务,任务分成有返回值和没有返回值两种。对应于 Java 里面的 Runnable 和 Callable
- invokeX 系列,如何调度任务
可行方案
TaskPool 接口定义
接口定义只需要描述 TaskPool 所要遵循的规范,查看 TaskPool
每一个方法的设计理由是:
- Submit 要考虑一个额外的阻塞因素。即如果任务池本身有容量的限制,那么用户在提交任务的时候可以被阻塞一会,在阻塞的这段时间,或许能够提交任务成功,否则就返回错误。影响 Submit 的还有 Start 方法和 Shutdown, ShutdownNow 两个方法。对于前者来说,实现者可以自由决定 Start 之后还是否允许继续提交任务——这本质上是一个线程安全的问题;Shutdown 的两个方法,则明确在调用之后就不再允许提交任务了;
- Shutdown 和 ShutdownNow:Shutdown 返回了一个 channel,那么用户监听这个 channel,就可以得知所有任务什么时候被执行完毕,同时自己也可以控制等待所有任务执行完毕的时间。而 ShutdownNow 则可以立刻关闭线程池,并且返回所有剩下未执行的任务
- Start 启动任务:用户可以在更加确切的地方开始调度任务的执行
Task 设计
Task 本身的设计并没有考虑任何返回值的问题,后面我们会提供一个允许返回值的任务的装饰器。为了简化用户的操作,即用户不希望自己的任务也需要实现 Task 接口,那么就可以使用我们的 TaskFunc 进行一个简单的转换:
t := TaskFunc(func(ctx context.Context) {
//.. 做些事情
})
Task 的 Run 方法被设计为接收一个 ctx 参数,这意味着用户应该考虑任务需要进行超时控制的问题,即使是慢任务,用户可能也预期这个任务能够在一小时或者两小时内释放掉 goroutine,即便此时它还没有执行完毕。
BlockQueueTaskPool
基于队列的阻塞式的任务池实现。
核心在于通过 concurrency 和 queueSize 来控制 goroutine 数量和等待任务。
而如何控制 queueSize 和 concurrency 则有很多方案:
- 使用 channel
- 读写锁
- 原子变量
这里我们不做这种限制
其它
你需要:
- 提供详细的测试用例
你使用的是 ekit 哪个版本?
你设置的的 Go 环境?
上传
go env
的结果
一个完整的生产级任务池应该长什么样子
控制住 goroutine 的数量
- 隔离快慢任务,让慢任务在数量有限的 goroutine 上运行,而其它快任务则没有这种限制
优雅关闭
- 用户希望在关闭的时候,可能希望尽量执行已经提交的任务,但是又不希望等待太久
- api设计
Shutdown 开始优雅关闭
ShutdownNow 立即关闭
awaitingShutdown 等待优雅关闭
任务池设计
- 用户希望支持任务池运行时变量的传递
- 用户希望支持任务超时控制
- 用户希望支持任务依赖有序
- 用户希望支持业务指定任务池
感知任务池运行情况
- 任务执行停止,怀疑发生死锁或执行耗时操作
- 任务执行时间超过平均执行周期
- 任务提交了但长时间每执行
运行指标(百分比+数量+top)
- 任务运行时长
- 任务等待时长
- 任务池活跃度
- 队列容量
运行时报警策略
- 任务运行超时
- 任务池活跃度
- 阻塞队列容量
- 触发拒绝策略
- 主流软件告警(微信,钉钉,飞书)
修改运行时生效
- 修改任务池 某一实例配置,或者修改 集群全部实例
监控运行可视化
- Prometheus + Grafana第三方采集监控
- 内置数据池数据采集 + 监控