有时候我们会遇到一些长时间运行的任务,应该如何处理呢?可能你的第一回答是“使用异步任务”,没错,是需要使用异步任务,
但没有那么简单。常见的异步任务框架,针对的场景,大部分可能都是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 和 内存 使用量、使用率
- 程序本身的重启次数、异常状态
更多文章
自己写一个容器
Golang(Go语言)中实现典型的fork调用
软件开发之禅---大事化小,各个击破
程序员的自我修养:链接,装载与库 阅读笔记
Redis源码阅读与分析二:双链表
Redis源码阅读与分析三:哈希表
Redis源码阅读与分析一:sds
Golang runtime 源码阅读与分析
Golang的一些坑
GC 垃圾回收
设计一个路由
Go语言性能优化实战
那些年开发的时候踩过的坑
(关系型)数据库优化总结
动态规划民科教程
文章来源: https://jiajunhuang.com/articles/2023_11_12-long_running_task.md.html
如有侵权请联系:admin#unsafe.sh