一种分布式复杂任务调度/算子调度系统的设计

本文讨论了一个项目中需要并行或串行调度的AI和算法进程,提出了使用责任链模式改造的工作流框架,强调了如何在多类型工作节点间进行资源管理和负载均衡,以及采用Go和Python等语言的实现方案。文章还探讨了分布式部署和云上部署的考虑因素,包括任务调度中心的主备策略和Worker的配置优化。
摘要由CSDN通过智能技术生成

背景

工作中遇到一个项目,需要并行或串行的调度若干个AI或算法进程,一共四五类流程,类似流程引擎。区别在于每个流程节点需要启动单独的进程处理,需预估进程的资源消耗并限流、保证负载均衡与两个独立流程间基本的顺序性。
可类比此流程(只是对实际业务的一个类比,非实际业务)

AVI格式
MOV格式
MP4格式
开始
用户输入视频文件
归一化为MP4
AI语音识别字幕
翻译为中文
转码为480P画质
转码为1080P画质
转码为2K画质
合成字幕与视频文件
输出给用户
结束

类似此流程的还有若干个,流程相对固定,基本不会改变。

技术方案

基本架构

以上述流程图描述的流程为例,AI语音识别字幕、翻译,两个流程节点显然是要依赖AI能力,而归一化、转码、合成字母和视频文件则明显是CPU密集型的流程节点。流程节点对机器处理能力的需求各不相同。
实际业务中还可能会有内存密集型、单核运算型、多核运算型、只能跑在Windows下的、只能跑在Linux下的等等,多种的流程节点类型,想在一台机器上将这些特性全部囊括是不可能的,因此,每个流程节点都需要在单独的机器上执行,称为Worker。
而管理这一个流程在若干个Worker上运行,并恰当的使用Worker资源,就需要一个任务调度中心来完成。每个Worker上也需要部署一个Agent,以完成调度中心委派来的指令,启动对应的流程节点处理程序。
任务调度中心使用GO语言最为适合,若使用Java、Python等语言,难免会为了跟踪流程节点的执行状态而开海量的线程,使用go协程可以很好的降低这种负担。而Agent使用Python即可,简单方便。

详细设计

对于流程节点调度,可自行实现一个简易工作流框架,以完成调度。由于流程相对固定,所以可以一定程度上直接硬编码,不用像Flowable之类的流程引擎一样将流程可配置化,搞一堆xml。
基本的实现框架使用责任链模式,不过不能照搬责任链模式,责任链模式会在当前机器上会流式的处理完所有的流程,中途没有停顿,无法加入流控与负载均衡等,并且两个独立流程间各个节点间的执行时序是随机的。
如:A,B流程是同一类流程,A流程有A1,A2两个流程节点,B流程有B1,B2两个流程节点,A流程先被执行,B流程后被执行。那么在责任链模式里,如果对A,B分别开一个线程处理流程,则A1,B1之间的执行顺序是随机的。此时假设全局的1号流程节点,同时只能处理一个,则希望A1的执行优先级高于B1,A2的优先级高于B2,责任链模式在此处无法胜任。最好在调度中心加入流控,并通过其他手段确保顺序性。
因此,具体实现时要对责任链模式做一些改动:为每个流程节点建立一个顺序队列,一个流程节点执行完成后,并非直接执行下一个流程节点,而是丢到下一个流程节点的顺序队列中。顺序队列消费时调用的方法,即流程节点在责任链里的Handler。
当然,还需要一个服务注册中心+监控中心,获取工作节点状态,也可以在调度中心里结合Worker上部署的Agent,自行实现一个工作节点注册中心+监控中心。最好使用后者,因为复杂任务调度过程中很有可能会有除CPU使用率,内存使用率等常规状态以外的状态,如工作节点正在处理的总视频长度、视频精度、字幕条数等。
前文提到的每个流程节点的顺序队列的消费时机,则需要新开一些线程,有多少工作节点类型,开多少线程,轮询此类型工作节点可处理的所有流程节点队列,若此类工作节点的最小占用的节点状态,满足流程节点队列首位的流程节点的资源需求,则开线程执行此流程节点,并追踪流程节点执行状态,直到投递到下一个流程节点队列或失败或结束。
另外,很可能存在流程前后两个流程节点对工作节点的类型需求一致,此时当然需要先执行流程后的流程节点,因此流程下游的流程节点执行优先级要高于流程上游的。
因此轮询流程节点时,要从下游的流程节点开始轮询,分配给一个工作节点或无法分配给任何工作节点时,则立刻continue,等待下一次轮询。
以上述流程图举例,此时可以转化为此时序图。

用户 调度协调器 Worker状态监控 流程节点执行池 发送MP4视频Task1 将Task1投递到 AI语音识别字幕队列(Node1) 任务已收到 查最低负载的AI Worker WorkerD及其负载情况 WorkerD满足需求,创建Job1(Task1, Node1, WorkerD) 收到Job1(Task1, Node1, WorkerD) 基础的输入校验 发送到WorkerD进行Node1(字幕识别)的处理 WorkerD的输出校验 投递Task1到中文翻译队列(Node2) 当前最低负载的AI Worker WorkerC及其负载情况 创建Job2(Task1, Node2, WorkerC) 处理Job2(Task1, Node2, WorkerC) 投递Task1到三个转码队列(Node31,Node32,Node33) 当前最低负载的CPU Worker WorkerA及其负载情况 创建Job3(Task1, Node31, WorkerA) 处理Job3(Task1, Node31, WorkerC) 当前最低负载的CPU Worker WorkerB及其负载情况 Job3的Node31,Node32,Node33未全部完成,直接结束 创建Job4(Task1, Node32, WorkerB) 处理Job4(Task1, Node31, WorkerC) 当前最低负载的CPU Worker WorkerA及其负载情况 WorkerA负载不满足(Task1, Node33)的需求 Task1重入Node33队列,排在队首 Job4的Node31,Node32,Node33未全部完成,直接结束 当前最低负载的CPU Worker WorkerA及其负载情况 创建Job5(Task1, Node33, WorkerA) 处理Job5(Task1, Node33, WorkerA) 投递Task1到字幕+视频合成队列(Node5) 当前最低负载的CPU Worker WorkerB及其负载情况 创建Job6(Task1, Node5, WorkerB) 处理Job5(Task1, Node33, WorkerA) Task1全流程处理完成 Task1处理完成 用户 调度协调器 Worker状态监控 流程节点执行池

当然,实现时还要考虑若干详细逻辑,如

  1. 工作节点连接断开,或若干时间未发送心跳包,则将其视为假死,再过若干时间若还未收到心跳包,则将其视为死亡,需要发送短信通知运维,并将工作节点上的任务回收并重新放置在队首。因此,当然也需要记录工作节点当前正在处理的Job有哪些,方便回收任务,也可作为负载判断时的依据。
  2. 对于可能由于服务器状态波动等原因失败的Job,也需要考虑进行重试,但也不能无限重试。
  3. 如何判断负载量,最简单的,根据工作节点当前的Job数量判断,但稍显不足,因此需要统计能拿到的所有指标,并列一个任务各项指标-资源消耗的散点图,综合分析,得到一个负载预估算法,各个流程节点的负载算法也一定是不一样的。
  4. 对于不同类型的用户,任务处理的优先级也很有可能是不一样的,因此得考虑是否为重要用户单独设一个任务调度集群,或为每个任务加入优先级设定,优先级高的可在排入队列时随机或通过某种算法往前排一些。最好使用后者或两者一起使用,因为优先级算法在同一类流程的不同节点间也需要设置,流程下游的节点执行优先级要高于流程上游,否则在新任务不断进入系统的情况下,会将所有任务卡死在上游。
  5. 后面的流程节点可能会使用前面的流程节点的产出,因此每个流程类型的任务都需要定义一个Context,以存储流程节点的产出。

部署方案

分布式部署方案

分布式部署方案主要考虑任务调度中心的部署,Worker的部署只需要启动Agent即可。
任务调度中心的部署在业务量不大时,可简单通过竞争式的分布式锁做个主备即可,锁的释放时间可以适当设高一点,调度中心的主备切换意味着要重新同步一遍当前活动的所有Task状态,代价很高。提高锁的释放时间可以尽可能的减少误识别主节点死亡的可能性。并且任务调度中心整个都是离线异步处理的,对可用性要求不高,业务上也能接受一两分钟掉线。
而业务量大起来时,可参考Redis的集群部署方案,做个哈希环或哈希链+主备,哈希环上的每个节点可以都做一个备,以尽量避免Rehash,给集群内其它调度中心太多的负载。整个系统在设计时,每个Task一定有一个固定的ID,根据此ID做哈希环的散列轻而易举。
任务调度中心集群化后,Worker与任务调度中心之间的关系建议还是保持一对一的关系,即一个Worker一定只被一个调度中心管理,避免集群内的调度中心对同一Worker的负载统计不一致,导致给Worker过度分配任务而宕机。

云上部署方案

由于Worker上运行的基本全是资源密集型的进程,并且在此架构下,对Worker的可用性基本没有要求,因此可以考虑将调度中心放在云端,而Worker全部放在自建低级别机房里,或每种特性的Worker只在云端留一两份以确保所有类型的任务都能够处理。比起完全上云,成本可大幅缩减。
将Worker本地化还有一个额外的好处:配置可以自由搭配。
如:假设有CPU单核性能需求很高的流程节点,那么完全可以自购一批E52667。如果找云服务商,那不好意思没这种实例卖。再比如假设有多核计算密集型,但内存占用不大的流程节点,那完全可以核心:内存=1:1的配置机器,但如果找云服务商,那不好意思核心:内存最小1:2。
如果业务端与任务调度中心的交互可以全异步化,那么调度中心也可以放在自建机房里。更加节省成本。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值