暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

【唯实践】CapacityScheduler在唯品会的迁移及应用

唯技术 2021-01-12
459

供稿丨唯品会数据平台

作者冯培坤

更多内容请点击上方蓝字关注我们

 

01 

集群现状

唯品会线上Yarn集群每天运行着50多万的Spark、Hive、Flink等任务,提交的作业量每月以2%-8%的速度在增长,我们数据团队需要给业务方提供一个稳定可靠的计算平台,同时尽可能的提高集群的资源利用率,以业务和技术驱动为基石来提升Yarn集群的调度性能,获取最大收益比。

02

队列设计模式

基于对于资源更好的掌控,我们选择采用层级队列组织方法,将队列构成一个树形结构,根队列叫做ROOT,代表集群全部资源,一个队列可以配置多个分支队列,其资源之和不超过父队。


我们将分支队列和部门项目进行一一映射,且每个项目都有对应的分支队列,分支队列的资源就是项目可使用的资源;同时给项目内任务设定优先级(P0,P1,P2等),映射到不同叶子队列。如图所示,ROOT下有三个队列QA,QB,QC,对应三个项目A,B,C,项目的任务分为P0、P1、P2,同样映射到队列QP0、QP1、QP2中。

图:部门和项目的队列映射

叶子队列反映的是每个项目中不同优先级的作业,这样形成部门级别分支队列控制集群资源导向,叶子队列对部门资源进行资源倾斜的设计思路,而此设计思路有一个特点就是叶子节点的队列名字是一样的,这也反映在我们选择的Scheduler实现上面。

03

FairScheduler       vs CapacityScheduler

由于历史原因,我们一直使用的FailScheduler,业务方通过客户端将作业提交到Yarn集群,Yarn集群统一进行资源管理和任务调度,按照作业指定队列获得对应的资源,从而启动任务执行,通过和底层存储进行交互,完成计算返回给业务方结果。


该Scheduler设计目标是为所有的应用分配公平的资源,主要的特点包括:

▲支持多用户多队列和资源共享,采用树形结构的队列层次,使用固定内存大小和核数来定义队列资源;

▲默认是基于内存的公平调度策略FairSharePolicy,也可以配置为包括内存和cpu的调度,采用Ghodsi等开发的主资源公平算法DominantResourceFairnessPolicy;

▲资源的分配基于公平共享原则,可为队列配置最小资源和设置最大资源限制;

▲通过配置文件可限制每个用户、队列的运行app的数量。


但是就目前而言,社区更侧重发展CapacityScheduler。CapacityScheduler是Yahoo开发的适合共享环境的调度器,随着版本的迭代,对调度的吞吐量做了众多改进,如多线程异步调度,GlobalScheduler等。相对FairScheduler,它在资源管控、优先级配比等方面更便于管理,对此做了以下简单总结:

其中很重要的一点是在任务提交时,FairScheduler需要叶子队列路径,而CapacityScheduler需要叶子队列名称,我们队列设计模式与FairScheduler提交方式是一致的,和CapacityScheduler是不兼容的。此外针对两者的对比,网上总结了很多,我们摘出它们主要的异同点:

04

瓶颈与选择

FairScheduler的瓶颈

在不断的实践运行中,我们发现FairScheduler的问题不断放大,比如基于Fair的调度方式采用的是真实资源配置,随着集群机器的资源变化和业务的调整,我们就需要相应的调整Scheduler配置文件。为此我们还专门写了一个工具用于从一个比例生成FairScheduler的配置文件,实在低效。特别是我们后续准备开启NodeLabel和手动超配资源后,需要快速的调度资源,这个Scheduler调整和维护更是不可忍受的。


总结我们面临的一些瓶颈:

▲ Scheduler资源调整频繁,维护成本变大;

▲部门项目和队列映射混乱,无法准确计算实际资源使用;

▲频繁调整Scheduler,导致资源配置失真;

▲个别队列资源倾斜严重,不能精细管理。


CapacityScheduler切换的思路

在经过讨论后我们决定切换使用CapacityScheduler方式,但是切换过程发现目前我们的队列设计中:叶子队列重复和CapacityScheduler是完全冲突的,也就是说如果我们要切换Capacity,就必须推倒我们的队列设计模式,让业务调度层面完全重构,这个代价无疑是巨大的。


我们内部讨论认为最好能够有一个方案既能使用CapacityScheduler,但又能兼容目前的叶子队列设计模式。在这个过程中我们意外发现了一个社区正在开发的功能:Capacity Scheduler兼容Fair Scheduler 队列设计模式[YARN-7621,YARN-9879]。我们研究这个功能发现这种方式对用户来说是透明的,也是兼容部门队列映射的,需要做的是修改CapacityScheduler中涉及的所有队列提交方式,将叶子队列提交替换为全路径提交,采用和FairScheduler一致的队列提交方式,这样做出的改动主要涉及Resourcemanager,用户作业提交方式不用做任何改变。


综合考虑后我们决定选择采用社区的这个正在开发的功能,保持对用户透明,采用CapacityScheduler兼容FairScheduler的方式来统一队列的提交方式。

05

Capacity Scheduler Support Fair Scheduler

我们对YARN-7621,YARN-9879进行了详细的研究,其中YARN-7621支持使用队列路径来提交应用,YARN-9879支持Scheduler中存在同名队列,这两点符合我们现有需要的调度逻辑,能够做到兼容FairScheduler的调度,我们总结兼容方案核心要点如下:

▲不应将新行为强加给老用户,不修改业务方原有的队列使用方式,尽可能的减少业务影响;

▲兼容Fairschelder队列的任务提交方式,队列名称只能在其直接父队列下唯一,或者换句话说到队列的路径必须是唯一的;

▲由于叶子名称可以不受约束,队列不再是唯一的,队列应能被其完整路径引用。


但是该功能目前还有很多不完善以及没有完成的部分,主要集中在针对队列路径的更改,替换成绝对路径的调用方法:getQueuePath。进而我们针对这部分做了大量的工作。

06

getQueuePath

既然主要的思路就是把对getQueueName的使用全部进行更换,替换成getQueuePath,完成对Queue路径到绝对Queue路径的切换。首先我们来看看在整个App生命周期中哪些地方会使用到队列相对路径。我们都知道资源的调度和管理由ResourceManager来负责,具体的按照队列来进行资源隔离和使用,保证提交的任务都能按照设定资源配置进行运行,那么我们来看看RM如何使用CapacityScheduler来为提交上来的任务进行队列匹配和资源调度的。


ResourceManager服务启动时首先会初始化Sheduler,期间会加载capacity-scheduler.xml,从root节点开始递归解析配置设计的层级队列,将分支队列的信息保存在queues中。每一次递归,会处理一个节点队列,获取对应的资源配置、父队列和子队列的信息等,保存在CSQueue中并加入到queues中,同时对LeafQueue进行检查保证在全局唯一。当作业提交到RM时,对提交队列进行验证并获取对应资源,完成后续任务调度执行。

图:使用QueueName解析和提交任务


引入CSQueueStore

默认Scheduler解析的队列保存在CapacitySchedulerQueueManager类的Map里面,为了方便解析和保存全路径队列,引入了CSQueueStore类,来保存LeafQueue队列全路径、队列的上下文信息,和对应的路径节点的映射。

图:使用QueuePath解析和提交任务

CSQueueStore

    public class CSQueueStore {
    private final Map<String, CSQueue> fullNameQueues = new HashMap<>();
    private final Map<StringSet<String>> shortNameToLongNames = new HashMap<>();
    private final Map<String, CSQueue> getMap = new HashMap<>();


    Map<String, CSQueue> getFullNameQueues() {
    return ImmutableMap.copyOf(fullNameQueues);
    }


    public Collection<CSQueue> getQueues() {
    try {
    modificationLock.readLock().lock();
    return ImmutableList.copyOf(fullNameQueues.values());
    finally {
    modificationLock.readLock().unlock();
    }
      }

    具体替换部分

    本次的替换其中涉及类主要有:AbstractCSQueue,LeafQueue,ParentQueue,CapacityScheduler,CapacitySchedulerQueueManager,CSQueueStore,CapacitySchedulerConfigValidator,QueueMapping,核心思想就是将getQueueName()的逻辑替换为getQueuePath()。


    getQueuePath

      String fullName = queue.getQueuePath();
      fullNameQueues.put(fullName, queue);


      这里改动的部分很多,就不再一一展开,简单介绍一个调度核心需要修改的例子。


      为了支持加载同名的叶子队列,例如root.a.q1,root.b.q1,root.c.q1的这种场景,在进行初始化解析Queue时,会保存队列全路径fullQueueName,同时去除LeafQueue唯一性判断,将具体队列信息保存在CSQueue类里面,并在每一次的递归中将当前的队列加入到CSQueueStore类中,最终完成所有队列的解析,部分代码如下:


      获取全路径的队列

        static CSQueue parseQueue(*){
        CSQueueStore queues,
        ...
        String fullQueueName = (parent == null) ? queueName : (parent.getQueuePath() + "." + queueName);
        String[] childQueueNames = conf.getQueues(fullQueueName);
        ......
        List<CSQueue> childQueues = new ArrayList<>();
        for (String childQueueName : childQueueNames) {
        CSQueue childQueue = parseQueue(csContext, conf, queue, childQueueName,
        queues, oldQueues, hook);
        childQueues.add(childQueue);
        }
        parentQueue.setChildQueues(childQueues);
        ......
        ueues.add(queue);
        }


        默认队列

        在此前FairScheduler中,如果我们没有定义任何队列,所有的应用将会放在一个default队列中,能够对用户设置错误进行兜底,而在CapacityScheduler中并没有这个选项。为了兼容用户的使用习惯,我们也在CapacityScheduler中实现了这个机制。

          public String getFallbackQueue() {
            return get(FALLBACK_QUEUE);
          }


          public void setFallbackQueue(String fallbackQueue) {
            set(FALLBACK_QUEUE, fallbackQueue);
          }


          CSQueue queue = getQueue(queueName);
          // Check if the queue is a plan queue
          if (queue == null) {
          if (conf.getFallbackQueue() == null) {
          return queueName;
          else {
          RMApp rmApp = rmContext.getRMApps().get(applicationId);
          rmApp.setQueue(conf.getFallbackQueue());
          return conf.getFallbackQueue();
          }

          整体功能经过我们测试上线并没有发现重大的调度问题,线上集群使用CapacityScheduler调度也已经正常运行半年。

          通过本次兼容FairScheduler,重新梳理业务方使用部门项目队列,解决各个项目混用队列吃大锅饭的问题,也更方便在业务层面对资源需求进行统计管理。同时依据CapacityScheduler的优势,通过调整Scheduler和开启Node Label,对高优先级任务进行资源倾斜以及资源隔离,提高了集群资源利用率,也满足了多样的计算需求。

          07

          思考

          这里可以看到为了能够兼容业务层面的队列设计思路,我们花了更多的时间在Yarn层面进行兼容,这不一定是所有场景下的最优方案,但却是我们目前场景下最优的选择。目前在很多场景还有队列相对路径没有修改彻底的问题,例如:

          ▲老的Web UI的Scheduler 队列对应的Applications不能正确显示;

          ▲新的UIV2 Queue页面上面调用仍然使用的相对路径,所以展示信息有误;

          ▲Yarn Move App 也使用的是相对路径,所以进行Move就会报错。


          上述线上生产遇到的问题,还需要进行优化修复,完善这个功能进而共享社区,帮助大家解决具有同样场景的问题。


          推荐阅读:【唯实践】基于Alluxio优化电商平台热点数据访问性能

          唯技术”一档专为唯品技术人发声的公众号

          欢迎投稿!!

          只要是技术相关的文章尽管砸过来!


          文章转载自唯技术,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

          评论