ecodeclub / ekit

支持泛型的工具库

Home Page:https://doc.meoying.com/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

pool: TaskPool 实现

flycash opened this issue · comments

仅限中文

使用场景

在有些时候,我们需要控制住 goroutine 的数量。例如在实际应用中,我们可能有快慢两种请求。对于慢请求来说,如果我们不限制它所能占据的 goroutine 数量,那么慢请求占据了一个 goroutine 之后一直不会释放,那么就会导致越来越多的 goroutine 被慢任务占据。

极端情况下,可能超过一半的 goroutine 都被慢任务所占据。这部分 goroutine 一直占据着资源在缓慢运行。

因此我们可能希望引入一种隔离机制,将慢任务和快任务分离,让慢任务在数量有限的 goroutine 上运行,而其它快任务则没有这种限制。

例如说我们在做数据迁移的时候,开启 10 个 goroutine 并发迁移不同表的数据。

因此我们需要设计一个任务池,用户提交任务,并且可以控制住并发执行任务的 goroutine 数量。

行业分析

如果你知道有框架提供了类似功能,可以在这里描述,并且给出文档或者例子

这方面的典型设计,就是 Java 的 ExecutorService,也就是 Java 中线程池的公共接口,它的方法可以分成好几个部分:

image

可以认为,它的接口设计聚焦在解决几个问题:

  • 退出: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第三方采集监控
  • 内置数据池数据采集 + 监控

感兴趣的朋友在详细阅读上面的需求后,可以按顺序查看下列PR中讨论以了解设计思路及需求变更: #57 #80 #93