长任务系统如何处理?

有时候我们会遇到一些长时间运行的任务,应该如何处理呢?可能你的第一回答是“使用异步任务”,没错,是需要使用异步任务, 但没有那么简单。常见的异步任务框架,针对的场景,大部分可能都是1分钟以内的任务,例如很多框架都会为任务设置一个超时时间。

而对于长时间运行的任务来说,很多地方是需要系统自己关心的,例如探活、实时输出信息、重试机制、排队等等。举几个常见的 场景:创建虚拟机、重度依赖AI进行分析,这些场景我们并不能简单的依赖框架,而必须要通过程序来保证可靠性。

API

  • API 除了需要做常规的权限检查之外,还需要在入队之前就确保当前队列没有超载,如果超载,则直接拒绝,这其实就是常见的“削峰”
  • 将任务的元信息保存到数据库,然后再发布任务,因为任务可能会使用到数据库,确保好这一个顺序可以避免因为时间先后导致的问题

Scheduler

  • 定期需要检查任务,例如常见的任务执行时间可能是10分钟,那么我们可以检查30分钟前开始,当前状态仍未RUNNING的任务,然后进行操作
  • 如果业务可以重试,那么需要考虑的是,在这么长时间之后,该任务是否仍然能够重试?通常对于长任务来说是不能的

Worker

  • 每次收到任务时,将任务的状态标记为RUNNING
  • 要把长任务中的任务,拆分为多个子任务,每一个子任务都需要标记状态、开始时间、结束时间等信息
  • 最好能抽象一套任务之间的依赖关系,让系统自动解决任务的依赖关系
  • 调整好任务重试的等待时间,通常对于长任务来说,任务重试等待时间越短越好,用户无法接受太长时间的等待;最好让重试的任务走一个高优先级的队列,或者无需排队等待的专有队列
  • 任务本身需要做好重试的处理,看是否能够在任务内就完成重试并且达到预期效果
  • 每个子任务完成之后,都要输出一些信息,追加一些log,这样用户可以看到当前的进度
  • 每个子任务完成之后,子任务所产生的信息、状态需要立即提交保存到数据库,避免丢失
  • 子任务要尽量设计为可重试的,状态要以数据库为准,对于核心状态信息,需要加锁确保准确更新
  • 任务完成后,将整个任务标记为DONE

监控与告警

本身对于异步任务来说,就难以观测程序的运行状态,对于长任务来说,更是如此。因此需要做好日志收集,打点操作,指标收集等工作, 同时加上告警规则,一旦发现告警,需要有工具能帮助我们快速定位问题以及观察当前运行状态。

常见的方案里,是使用OpenTracing做日志追踪和打点,Prometheus收集指标,Grafana做监控,AlertManager 做告警(或者Grafana也可以)。

需要监控的指标包含但不限于:

  • API的成功率、E2E平均执行时间、E2E P50/P90/P95执行时间
  • 业务的成功率、E2E平均执行时间、E2E P50/P90/P95执行时间
  • 任务的触发量、接受量、完成数、成功率、异常数量
  • 任务的平均执行时间、P50/P90/P95 执行时间
  • CPU 和 内存 使用量、使用率
  • 程序本身的重启次数、异常状态

更多文章
  • glusterfs 笔记
  • 用peewee代替SQLAlchemy
  • Go的slice工作机制
  • Linux系统迁移记录(从HDD到SSD)
  • Redis是如何工作的?
  • virsh自动关闭windows虚拟机
  • Golang sort源码阅读
  • 分治的思维方式
  • Debian 使用NetworkManager之后networking.service崩溃
  • httprouter源码阅读与分析
  • 《程序员的自我修养-装载、链接与库》笔记
  • Golang sync.Pool源码阅读与分析
  • MySQL操作笔记
  • Go语言解析GBK编码的xml
  • Golang log 源码阅读