长任务系统如何处理?
有时候我们会遇到一些长时间运行的任务,应该如何处理呢?可能你的第一回答是“使用异步任务”,没错,是需要使用异步任务, 但没有那么简单。常见的异步任务框架,针对的场景,大部分可能都是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 和 内存 使用量、使用率
- 程序本身的重启次数、异常状态
更多文章
本站热门
- socks5 协议详解
- zerotier简明教程
- 搞定面试中的系统设计题
- frp 源码阅读与分析(一):流程和概念
- 用peewee代替SQLAlchemy
- Golang(Go语言)中实现典型的fork调用
- DNSCrypt简明教程
- 一个Gunicorn worker数量引发的血案
- Golang validator使用教程
- Docker组件介绍(二):shim, docker-init和docker-proxy
- Docker组件介绍(一):runc和containerd
- 使用Go语言实现一个异步任务框架
- 协程(coroutine)简介 - 什么是协程?
- SQLAlchemy简明教程
- Go Module 简明教程