文章目录
目的
目前(Flink 1.9), Flink采用粗粒度的资源管理方法,task根据job的最大并行度,部署到尽可能多的预定义slot中,而不管每个task/operator可以使用多少资源。
上述方法易于设置,但是不能最佳的利用资源和性能。
- 任务可能具有不同的并行度(parallelisms),因此不是所有的slot都包含完整的任务管道(task pipeline)。对于任务较少的slot,为整个pipeline预定义的slot资源可能会造成浪费。
- 在资源使用方面(堆、网络、托管内存等),很难将slot提供的资源与task需求保持一致。
我们计划实现细粒度资源管理,在tasks资源需求已知、tasks资源需求未知(即可调整),这两种情况下,优化资源的使用。
我们计划改进Flink的资源管理机制,使得:
- 流处理和批处理模式下,均能很好的工作
- 已指定或未知task需要的资源,均能很好的工作
当前FLIP主要关注细粒度资源管理的一个方面:Operator资源管理
- 我们计划基于比例来管理内存配额,以统一以下内存管理场景:Operator指定了资源需求、Operator未指定资源需求
- 我们计划将slot共享组缩小到pipelined regions单元,在不同的pipelined regions中,task可以并发运行,使用这种方法,对于tasks/Operator在同一个slot中共享内存,我们可以得到合理但又不太保守的比例。
范围
- 当前FLIP提出的方法,应该只适用于Blink planner提供的DataStream API和SQL/Table API作业(blink planner内部包括无界流处理和有界批处理job)。当前FLIP不能作用于DataSet API(批处理job)
- 对于Dataset Job,已经有一些基于比例的方法实现(在TaskConfig和ChainedDriver中),我们不会对这些已有的方法做任何更改
- 本FLIP假定Job的Operators具有已知的资源请求配置,这些资源需求配置,已经在 PhysicalTransformations类中ResourceSpecs里描述。
- 本FLIP不会讨论怎样设置一个Job中,Operators的资源需求配置
- 当前状态(包括Flink 1.10开发计划),如何设置Job Opererators的资源需求的可以描述如下:
- SQL/Table API–Blink optimizer(优化器)可以根据用户的configuration(配置),设置Operator资源(默认:未知)
- DataStream API–目前没有设置Operator资源的方法/接口。可以在以后添加
- DataSet API–已经有用户接口,用于设置Operator资源
公共接口
- ResourceSpec对象
- 引入一个新的配置“taskmanager.defaultSlotResourceFraction”,虽然不赞成这种方式,但是与"taskmanager.numberOfTaskSlots"配置保持兼容
修改建议
通用的Workflow(工作流)
细粒度Operator资源管理前提下,一个通用的workflow实现方式如下:
- 未知的(默认)或指定的(例如blink planner)PhysicalTransformations#ResourceSpecs对象,它们描述了transformation的资源需求。
- 当生成JobGraph时,StreamingJobGraphGenerator 为Operator计算内存比例(内存来自slot托管内存),将配置写入StreamConfigs
- 当调度时,Operator的ResourceSpecs 会被转换为Tasks的ResourceProfiles (ResourceSpecs中 chained operators + network 总内存)。根据ResourceProfiles将task(任务)部署到 TMs的slot中
- 在TMs中开始任务时,每个Operator获取slot托管内存的分数,该分数要么是原始请求的绝对值,要么是未知需求的公平份额。
Operator资源需求
Task可以消费TaskExecutor以下资源
- Cpu Core
- 用户堆上内存(Task Heap Memory in FLIP-49)
- 用户堆外内存((Task Off-Heap Memory in FLIP-49)
- 托管的堆上内存(On-Heap Managed Memory in FLIP-49)
- 托管的堆外内存((Off-Heap Managed Memory in FLIP-49)
- 为网络分配的内存((Network Memory in FLIP-49)
- 扩展资源(GPU、FPFA、etc)
Operator在ResourceSpec中声明自己的资源需求,ResourceSpec应该包括所有资源维度,除了Network Memory(网络内存可以在运行时,从执行拓扑图中派生)。在这些维度之间,只要Operator声明了ResourceSpec,CPU core和用户堆上内存(unmanaged on-heap memory,所有Operator可以消费)就是必须使用的。而其他维度是可选的,如果没有显式指定,则默认设置为0。
如果Operator没有指定任何资源(ResourceSpec),它们的资源需求在默认情况下是未知的,这将由运行时决定它们可以消耗多少资源。
对于第一个版本,我们不支持在同一作业中,指定不同Operator不同资源需求(一部分Operator具有特定资源需求,一部分Operator没有指定)。同一作业的所有Operator都应该指定它们的资源需求,或者一个都不指定。StreamingJobGraphGenerator应该进行检查,并在编译阶段检测到混合资源需时,抛出错误。
托管内存申请
基于比例的配额策略
Operator不应在managed memory(托管内存上),获取绝对的内存配额。相反,应该应用一个相对的配额,以统一具有指定和未知的Operator资源需求。
在编译阶段,StreamingJobGraphGenerator应该为每个操作符设置两个比例。
- fracManagedMemOnHeap - Operator在slot中,应该使用的on-heap managed memory(堆上托管内存)比例
- fracManagedMemOffHeap - Operator在slot中,应该使用的off-heap managed memory(堆外托管内存)比例
比例的计算方式如下:
-
有特定资源需求的Operator
fracManagedMemOnHeap = opOnHeapManagedMem / slotSharingGroupOnHeapManagedMem
fracManagedMemOffHeap = opOffHeapManagedMem / slotSharingGroupOffHeapManagedMem -
资源需求未知的Operator
fracManagedMemOnHeap = 1 / numOpsUseOnHeapManagedMemoryInSlotSharingGroup
fracManagedMemOffHeap = 1 / numOpsUseOffHeapManagedMemoryInSlotSharingGroup -
runtime运行时还可以公开接口,以支持为具有不同权重的Operator设置比例。
当Task(任务)部署到TaskExecutor(任务执行程序)时,Operator应该将他们的内存使用比例,注册到MemoryManager。这一切发生在消费内存之前。注册应该根据比例返回绝对配额。通过这种方式,Operator可以根据其配额使用托管内存,并假设内存是可以保证的,或者让内存管理器(MemoryManager)限制它的内存消耗,并让Operator认识到,分配新内存不一定总是成功
Slot Sharing
在编译阶段,StreamingJobGraphGenerator首先标识JobGraph中的pipelined region,流水线区域(pipelined region)被定义为JobGraph中由pipelined edge(流水线顶点)相连的顶点子集,这些顶点总是被安排在一起。否则,当下游任务由于缺乏资源而无法调度,而上游任务由于没有下游任务读取输出而无法完成释放资源时,可能会出现死锁。
StreamingJobGraphGenerator将不同pipelined region的任务设置到不同的slot sharing groups中。这样,当StreamingJobGraphGenerator为Operator设置相对的托管内存配额时,它将只考虑可能同时运行的Operator来计算比例。这提高了bounded batch jobs(有限批处理作业)的资源利用率,在这些作业中,通常不是所有任务都同时运行。
对于使用DataStream API编写的作业,有一些公开给用户的接口允许用户显式地为Operator设置slot sharing group。在这种情况下,应该尊重用户的设置。
保持良好的特性也很重要,即无论作业图拓扑如何,Flink都需要与执行作业的最大任务并行性一样多的slot。对于批处理作业,可以很好地实现这一点,在批处理作业中,不同的pipelined region总是可以顺序运行,复用相同的插槽。然而,当流处理作业(Streaming Job)中包含多个连接组件(connected component)时,情况变得糟糕。虽然不同连接组件中的任务属于不同的流水线区域(pipelined region),但是所有连接组件中的任务都需要并发运行。因此,连接组件(connected component)的最大并行度之和,就是运行job所需的最少slot。
为了解决这个问题,我们需要能够将不同的连接组件(connected component)放到同一个槽共享组(slot sharing group)中,进行流处理作业(streaming jobs),另外,也可以将它们保存在不同的slot sharing group中,以避免大量slot中,任务不需要并发调度的情况。我们需要一个参数scheduleAllSourcesTogether来指示是否在相同的 pipelined region中标识所有的Source(想象一个虚拟Source连接到所有的真实源),并将其以不同的方式传递到StreamingJobGraphGenerator中,以用于流作业和批作业。
在调度阶段,我们总是为每个插槽共享组为每个并行管道请求一个插槽。
- 对于具有指定资源需求的任务,我们将slot sharing group中所有任务的资源需求相加,并使用资源总和请求一个slot。
- 对于具有未知资源需求的任务,我们请求一个具有默认资源的slot。
实施步骤
步骤1. 在ExecutionConfig中引入选项allSourcesInSamePipelinedRegion
- 在ExecutionConfig中引入选项allSourcesInSamePipelinedRegion
- 默认设置为true
- Blink planner将SQL/Table API绑定的批处理作业设置为false
此步骤不应引入任何行为更改。
步骤2. 根据pipelined region设置slot sharing group
在编译时,StreamingJobGraphGenerator为Operator设置slot sharing group 。
- 识别pipelined region,同时参考allSourcesInSamePipelinedRegion
- 根据pipelined region设置slot sharing group
- 默认情况下,每个pipelined region应该进入一个单独的slot sharing group
- 如果用户将多个pipelined region中的Operator设置为同一 slot sharing group,则应尊重这一点
此步骤不应该引入任何行为更改,因为后面的pipelined region调度,可以复用前面已经调度的pipelined region。
步骤3. 在StreamConfig中引入托管内存比例
在StreamConfig中引入fracManagedMemOnHeap和fracManagedMemOffHeap,可由StreamingJobGraphGenerator设置,可以在运行时runtime中,由Operator使用。
此步骤不应引入任何行为更改。
步骤4. 根据slot sharing group设置托管内存比例
- 对于具有指定ResourceSpecs的Operator,根据Operator ResourceSpecs,来计算比例
- 对于未知ResourceSpecs的Operator,根据使用托管内存的Operator数量,来计算比例
此步骤不应引入任何行为更改。
步骤5. Operator根据比例来决定分配多少托管内存
*Operator使用MemoryManager#computeNumberOfPages来获取memory segment(根据返回值)。
- Operator使用MemoryManager#computeMemorySize来保留内存(根据返回值)。
此步骤将激活新的基于比例的托管内存分配策略。
兼容性、弃用和迁移计划
此FLIP应该与以前的版本兼容。
测试计划
- 我们需要更新现有的并添加新的集成测试,以验证新的细粒度资源管理行为。
- 如果上述集成测试失败,其他常规集成和端到端测试也会失败。
被拒绝的方案
编译时设置 slot sharing group的另一种方法是,将具有指定资源需求的任务,设置为单独的slot sharing group(托管组中的任务除外),将具有未知资源需求的任务,设置为相同的slot sharing group。拒绝它是因为,它将任务从相同的pipelined region分离到不同的 slot sharing group,这可能会导致资源死锁的情况。
另一种方案,在调度阶段为Operator设置相对托管内存配额,同时了解哪些任务实际调度到相同的slot中。它被拒绝,因为即使我们在调度时设置了配额,也不能保证将来不会将任何任务部署到相同的槽中,而且动态更新比例要求操作符的内存使用是可撤销的。
后续
Operator托管内存超额分配和撤销
对于第一个版本,我们不允许Operator使用超过其配额的托管内存。
将来,我们希望允许Operator机会性的利用task executor中可用托管内存。Operator就可以申请超过配额的托管内存,只要task executor中有足够的可用托管内存,并且超出的配额在另一个任务需要时,可以撤销。
参考
[1] FLIP-49: Unified Memory Configuration for TaskExecutors