【分布式计算】Helix: 基于配额的任务调度
介绍
基于配额的任务调度是Helix任务框架的一项功能添加,它使任务框架的用户能够在分布式任务管理中应用类别的概念。
目的
随着Helix Task Framework在其他开源框架(如Apache Gobblin和Apache Pinot)中的使用,它也看到了它所管理的分布式任务类型的多样性。 Helix也有明确的功能请求,通过创建相应的配额来区分不同类型的任务。
基于配额的任务调度旨在通过允许用户定义由配额类型及其相应配额组成的配额配置文件来满足这些请求。此功能的目标有三个:
- 1)用户将能够优先考虑一种类型的工作流/作业/任务,
- 2)实现任务类型之间的隔离;
- 3)通过跟踪分布状态使监控更容易按类型执行。
词汇表和定义
- 任务框架:Apache Helix的一个组件。用户可以以分布式方式定义和运行工作流,作业和任务的框架。
- 工作流程:任务框架中最大的工作单元。工作流由一个或多个作业组成。有两种类型的工作流程:
通用工作流:通用工作流是由用于一般目的的作业(作业DAG)组成的工作流。如果过期或超时,可以删除通用工作流程。
作业队列:作业队列是一种特殊类型的工作流,由往往具有线性依赖关系的作业组成(但这种依赖关系是可配置的)。作业队列没有到期 - 它一直存在直到被删除。
- 工作:任务框架中的第二大工作单元。作业由一个或多个相互独立的任务组成。有两种类型的工作:
通用作业:通用作业是由一个或多个任务组成的作业。
目标作业:目标作业与通用作业的不同之处在于,这些作业必须具有目标资源,属于此类作业的任务将与目标资源的分区一起安排。为了说明,Task Framework的Espresso用户可能希望在他们的一个名为MemberDataDB的DB上安排备份作业。此DB将分为多个分区(_MemberDataDB_1,_MemberDataDB2,... MemberDataDBN),并假设已提交目标作业,以使其任务与每个分区配对。这种“配对”是必要的,因为此任务是一个备份任务,需要与任务备份的那些分区位于同一物理机器上。
- 任务:任务框架中最小的工作单元。任务是一个独立的工作单元。
- 配额资源类型:表示特定类型的资源。示例可以是JVM线程计数,内存,CPU资源等。通常,在Helix Participant(= instance,worker,node)上运行的每个任务都占用一定量的资源。请注意,只有JVM线程计数是Task Framework当前支持的唯一配额资源类型,每个任务占用每个Helix Participant(实例)可用的40个线程中的1个线程。
- 配额类型:表示给定作业及其基础任务应归类为哪个类别。例如,您可以使用两种配额类型定义配额配置,键入“备份”,然后键入“聚合”和默认类型“DEFAULT”。您可以通过为备份类型提供更高的配额比例(例如20:10:10)来确定备份类型的优先级。当有提交的作业流时,您可以预期每个参与者(假设它总共有40个JVM线程)将具有20个“备份”任务,10个“聚合”任务和10个“默认”任务。配额类型在作业级别定义和应用,这意味着属于具有配额类型的特定作业的所有任务都将具有该配额类型。请注意,如果为工作流设置了配额类型,则属于该工作流的所有作业都将从工作流继承该类型。
- 配额:一个数字,指的是确定给定资源的哪一部分应分配给特定配额类型的相对比率。
例如,TYPE_0:40,TYPE_1:20,...,默认值:40
- 配额配置:一组字符串整数映射,指示配额资源类型,配额类型和相应的配额。任务框架将配额配置存储在ClusterConfig中。
架构
- AssignableInstance
AssignableInstance是一个抽象,表示能够从Controller接受任务的每个实时参与者。每个AssignableInstance将缓存它运行的任务以及基于配额的容量计算中的剩余任务计数。
- AssignableInstanceManager
AssignableInstanceManager管理所有AssignableInstances。它还充当Controller和每个AssignableInstance之间的连接层。 AssignableInstanceManager还提供了一组接口,允许Controller轻松确定AssignableInstance是否能够承担更多任务。
- TaskAssigner
TaskAssigner接口提供基本API方法,涉及基于配额约束的任务分配。 目前,任务框架仅涉及参与者端JVM线程的数量,每个线程对应于活动任务。
- RuntimeJobDag(JobDagIterator)
这个新组件充当Controller的JobDAG的迭代器。 以前,任务分配要求Controller迭代所有作业及其基础任务,以确定是否有任何需要分配和调度的任务。 事实证明这是低效的,并且随着我们对Task Framework的负担越来越大而无法扩展。 每个RuntimeJobDag记录状态,也就是说,它知道需要向Controller提供哪些任务以进行调度。 每次通过Task管道的TaskSchedulingStage时,这都会为Controller节省冗余计算。
用户手册
这个怎么运作
基于配额的任务调度的工作原理如下。 如果设置了配额类型,则任务框架将计算与每个配额类型的所有配额配置数之和的比率。 然后,它将应用该比率来查找分配给每个配额类型的实际资源量。 以下是一个示例:假设配额配置如下:
"QUOTA_TYPES":{ "A":"2" ,"B":"1" ,"DEFAULT":"1" }
基于这些原始数据,任务框架将计算比率。 使用这些比率,任务框架将应用它们来查找每个配额类型的实际资源量。 下表总结了这些计算,假设每个实例有40个JVM线程:
Quota Type | Quota Config | Ratio | Actual Resource Allotted (# of JVM Threads) |
A | 2 | 50% | 20 |
B | 1 | 25% | 10 |
DEFAULT | 1 | 25% | 10 |
每个实例(节点)都将具有如下所示的配额配置文件。这有一些影响。首先,这允许通过将更多资源分配给相应的配额类型来对某些作业进行优先级排序。从这个意义上讲,您可以将配额配置号/比率视为用户定义的优先级值。更具体地说,在上面的示例中采用配额配置文件。在这种情况下,当为每种配额类型提交100个作业时,类型A的作业将更快完成;换句话说,由于配额比率是其他配额类型的两倍,配额类型A在连续的工作流时会看到两倍的吞吐量。
基于配额的任务调度还允许在调度作业中进行隔离/划分。假设有两类工作,第一类是紧急工作,这些工作是短暂的,但需要立即运行。另一方面,假设第二类工作往往需要更长时间,但它们并不那么紧迫,可以花时间运行。以前,这两种类型的工作将被分配,计划和运行,确实难以确保第一类工作以紧急方式处理。基于配额的调度通过允许用户创建具有不同特征和要求的“类别”模型的配额类型来解决此问题。
如何使用
在ClusterConfig中设置配额配置
要使用基于配额的任务计划,您必须首先建立配额配置。这是一次性操作,一旦您确认您的ClusterConfig具有配额配置集,就不需要再次设置它。请参阅以下代码段,例如:
ClusterConfig clusterConfig = _manager.getConfigAccessor().getClusterConfig(CLUSTER_NAME); // Retrieve ClusterConfig clusterConfig.resetTaskQuotaRatioMap(); // Optional: you may want to reset the quota config before creating a new quota config clusterConfig.setTaskQuotaRatio(DEFAULT_QUOTA_TYPE, 10); // Define the default quota (DEFAULT_QUOTA_TYPE = "DEFAULT") clusterConfig.setTaskQuotaRatio("A", 20); // Define quota type A clusterConfig.setTaskQuotaRatio("B", 10); // Define quota type B _manager.getConfigAccessor().setClusterConfig(CLUSTER_NAME, clusterConfig); // Set the new ClusterConfig
需要注意的是 - 如果设置了配额配置,则必须始终定义默认配额类型(使用“DEFAULT”键)。 否则,将不再安排和运行没有类型信息的作业。 如果您在基于配额的计划开始之前一直使用任务框架,那么您可能具有其作业没有任何类型集的重复工作流。 如果您忽略包含默认配额类型,则这些重复工作流将无法正确执行。
在ClusterConfig中设置配额配置后,您将在JK格式的ZooKeeper集群配置ZNode中看到更新的字段。 请参阅以下示例:
{ "id":"Example_Cluster" ,"simpleFields":{ "allowParticipantAutoJoin":"true" } ,"listFields":{ } ,"mapFields":{ "QUOTA_TYPES":{ "A":"20" ,"B":"10" ,"DEFAULT":"10" } } }
为工作流和作业设置配额类型Workders for WorkflowConfig和JobConfig提供了一种设置作业的配额类型的方法。请参阅以下内容:
jobBuilderA = new JobConfig.Builder().setCommand(JOB_COMMAND).setJobCommandConfigMap(_jobCommandMap) .addTaskConfigs(taskConfigsA).setNumConcurrentTasksPerInstance(50).setJobType("A"); // Setting the job quota type as "A" workflowBuilder.addJob("JOB_A", jobBuilderA);
常问问题
- 如果我没有在ClusterConfig中设置配额配置会怎么样?
如果在ClusterConfig中未找到配额配置,则任务框架会将所有传入作业视为默认值,并将100%的配额资源提供给默认类型。
- 如果我的工作没有设置配额类型会怎样?
如果任务框架遇到没有配额类型的作业(即,缺少quotaType字段,是空字符串或文字“null”),则该作业将被视为DEFAULT作业。
- 如果在ClusterConfig中的配额配置中存在配额类型不存在的工作流/作业,该怎么办?
任务框架将无法找到正确的配额类型,因此它会将其视为DEFAULT类型,并将使用DEFAULT类型的配额进行相应的分配和计划。
目标工作怎么样?
配额也将应用于目标作业,目标作业的每个任务占用预设资源量(当前每个任务占用1个JVM线程)。
- 工作队列怎么样?
基于配额的计划适用于所有类型的工作流 - 通用工作流和作业队列。对用户要注意的是要小心并始终验证是否已正确设置作业的配额类型。任务框架不会自动删除或通知用户因配额类型无效而卡住的作业,因此我们提醒所有用户通过在ClusterConfig中查询其设置来确保配额类型存在。
下来的步骤
基于配额的任务调度已在LinkedIn内部进行了测试,并已集成到Apache Gobblin中,使Helix Task Framework和Gobblin的Job Launcher用户能够定义类别和相应的配额值。有一些即时待办事项可以改善此功能的可用性:
- 更细粒度的配额文件
目前,配额配置文件适用于整个群集;也就是说,ClusterConfig中定义的一个配额配置文件将全局适用于所有参与者。但是,某些用例可能要求每个参与者具有不同的配额配置文件。
- 使参与者的最大JVM线程容量可配置
Helix Task Framework的最大任务线程数设置为40.使这个可配置将允许一些用户根据执行此类任务的持续时间来增加任务的吞吐量。
- 向配额资源类型添加更多维度
目前,每个Participant的JVM线程数是Helix Task Framework定义配额的唯一维度。但是,如前面部分所述,这可以扩展到常用的约束,如CPU使用率,内存使用率或磁盘使用率。随着新维度的添加,需要额外实现TaskAssigner接口,该接口根据约束生成任务分配。
原文 : https://helix.apache.org/0.8.4-docs/quota_scheduling.html
- 72 次浏览