分布式任务编排调度框架设计

运维焦油坑

随着互联网+和去IOE浪潮的推进,传统行业X86服务器的数量逐渐增多。服务器数量剧增带来的直接后果就是运维复杂度的增加。原本一个人可以轻松维护十几台甚至几十台服务器:写几个常用的监控和配置下发脚本、或者利用cronTab制作几个定时任务就可以搞定。当服务器的数量由几十上升到几百,几千时,量变就引起了质变;而且随着应用数量的增多,需要同时对多个应用进行快速高效的备份或者持续部署等非常复杂的操作。传统的运维思路已经很难满足现实的需求了。这个时候,再牛掰的运维人员也会像焦油坑中的巨兽一样举步维艰。而本文将会给大家分享一种跳出运维焦油坑的实现思路。

工欲善其事

说到运维工具,大家都能信手拈来:Ansible,Pupet,Chef,Saltstack等等。每一种工具都给运维人员提供了很大的帮助,让他们从繁琐的运维任务中得到减负。因为本文主要介绍的是分布式任务调度框架,所以这里就不对这几种运维工具的优劣进行过多地比较。就像标题中所说的,我们只要选一种运维工具,再加上一个稳定高效的任务调度框架,那么就可以轻松面对千万台服务器的运维操作了。这里我们选择的是Ansible。主要原因他是Agentless的。对于不喜欢在生产服务器上安装各种插件的运维工程师来说是个不错的选择。

整体结构图

本文主要跟大家分享的是分布式任务编排与调度框架设计。整个平台就有这么几个关键点:分布式、任务编排、任务调度。

所谓分布式,就是平台是分布式部署的,各个节点之间可以无状态和水平扩展;所谓任务编排,就是平台可以支撑多任务步骤,多步骤明细的复杂任务的编排和解析;所谓任务调度,就是平台可以对单一运维操作进行最优切分和调度,从而保证对大量服务器进行运维操作快速高效地完成。

有图有真相。接下来给出整个平台的整体结构图:

整体结构图

通过图上我们可以看到,整个平台依靠Zookeeper作为服务的注册和管理中心;依托RabbitMQ进行服务模块之间消息的传递;采用MySQL作为基础数据的存储。

运行全过程

接下来我们就各个模块所提供的服务和模块之间的交互流程跟大家具体描述。

Web模块是平台的脸面,用来提供任务编排,任务执行进度等信息的展示。在启动之初Web从Zookeeper上获取可调用的Server的信息并缓存起来。同时,在Server有任何变化时(下线和上线),会接到通知来实时更新缓存。从而保证寻找Server服务时能够更加快速和高效。

Server模块是整个平台的核心,主要负责复杂任务编排的解析处理、复杂任务执行流程拆分和调度下发。Server启动之初会向Zookeeper进行服务注册,用来告知其他模块,我已启动,可以干活了;与此同时,它还会从服务中心那里获取其他信息:比如还有几个和他一样的兄弟跟他并肩战斗?还有几个手下(Scheduler)可以用来执行任务?Server就像一台告诉运转的引擎,随时准备处理各种任务。

Scheduler模块主要用于接受Server发过来的任务并进行更加细化拆分和下发。在模块启动之初,跟Server一样进行自身服务的注册和关注服务的获取和缓存,比如,获取真正可以干活的小弟(Worker)的信息。在面对大量服务器的时候就可以把目标机器进行拆分,让每个Worker都能均衡的获取和执行任务,加快任务执行速度。比如:Server发过来一个任务,要你备份一千台服务器的某个目录。怎么做?一个个串行做吗?开玩笑!单这一个任务就够你执行半天。所以,关键时候还是要并发的把任务分配给多个Worker。然后再进行任务结果的收集就可以了。从某种角度讲,Scheduler只能算是一个小组长,负责分配任务,收集结果。还是比较清闲的。

Worker模块的主要负责具体任务的执行,是整个平台中最苦逼的一个模块。它只能接受任务参数,任劳任怨的利用Ansible去目标机器执行任务,然后把任务的处理结果发送到MQ中。其实,worker也会有自己的小算盘。打个比方,Scheduler接受到的一千台服务器备份任务,假设平台有10个worker,那么每个worker将会分到100台服务器的备份任务。Worker会傻傻的一个一个执行吗?当然不会!worker内部也会并发的去多个目标服务器上去执行备份任务,然后进行结果的回收。

核心技术点

啰嗦了那么多,相信大家对平台的整体运转过程有了大致了解。接下来我们就这几个核心技术点进行重点描述:

分布式:分布式的目标就是为了实现高可用,高吞吐量。上面说到的几个模块全部实现了分布式部署,各自的特点体现如下:

Web层,我们在前端采用了Nginx进行请求负载。利用了Redis进行Session信息的存储。用来实现解决web层的高可用问题。

Server层自身就像一个独立的任务编排调度引擎,可以独立对外提供服务,多个server模块之间可以依赖服务中心感知彼此的存在,在某个server挂掉的时候可以快速的把其中的任务转移到其他的server模块来执行。

Scheduler,Worker和Server一样,同样可以分布式部署并支持水平无限扩展。

任务编排:我们拿一个应用部署的过程来举例:我们要发布一个javaweb应用到2台机器上。那么我们要做哪些操作呢?首先,我们要把负载下掉,web服务器(以Tomcat为例)停掉;其次,我们要把一些缓存数据,不用的日志数据清理掉;第三步,我们要把应用包从软件仓库拷贝到目标机器的指定路径;第四步,我们要把web服务器启动;最后一步,把应用的负载进行更新。这样一个由五个操作步骤的复杂任务是如何进行编排的呢?我们将这个复杂任务定义了三个对象:Task, step, job。Task就是整个的复杂任务,step呢就是我们说的这五个步骤。Job就是每个步骤具体要做的事情。在任务编排的时候我们会把这三类对象数据和关联关系数据存放到数据库中。那么上面的这个例子对应出来的任务数据大体下如下:

task:

    step-1

        job-1-1

        job-1-2

    step-2

        job-2-1

    step-3

        job-3-1

    step-4

        job-4-1

    step-5

        job-5-1

在任务执行的时候我们采用的思路是:步骤(Step)之间串行执行,Step内部的Job并行执行。步骤之间串行执行是因为前后两个任务之间存在依赖关系,如果没有依赖关系的任务可以放到同一个步骤来执行。这样就会提高整体任务的执行效率。整个复杂任务的解析执行过程如下:

任务执行过程

首先,Task Engine拿到整个任务数据,先会进行数据校验。在格式正确的情况下依次执行各个步骤(Step),在单个Step中获取具体执行的job列表,并对每个job的任务参数封装成一个任务线程,丢到线程池中去执行。

其次,线程执行时会将具体的任务发送到Scheduler中,然后等待MQ中的job处理结果,Scheduler在接受到任务之后会根据目标机器和worker的数量以及单个worker的处理能力经验值来计算出此次job需要调用worker的最优次数。然后将任务下发到具体的worker中来执行。同时,告知worker任务结果发送到MQ中的消息队列。同时监听MQ中该队列的消息,接收到消息之后处理并回传到server中的任务线程中。

最后,worker在接受到任务参数和结果队列信息之后,会根据目标机器数量进行再次调度,然后收集任务处理结果并发送到scheduler执行的消息队列中去。这样,server接收到该step的所有job执行结果之后会继续执行下一步骤中的各个job。最终把整个复杂任务处理完成。整个过程的数据流图如下图所示:

调度整体框架

任务调度:整个平台的任务调度分为三个层面的调度:首先是server和scheduler之间的调度。他们的调度方式包括pull和push两种模式,即server会定时向scheduler推送任务消息,同时scheduler在执行完任务之后也会向server询问是否有其他可执行的任务;其次是scheduler和worker之间的调度。我们这几假设目标机器有M台,worker数量有W个,每个worker处理任务的经验值是C个。我们的整体算法流程伪代码如下:

if (targets>Worker * C){

    String[] targetArray = splitTarget(C)

    for(String target : targetArray){

        createRequest(target)--->worker

    }

}else{

    C = [targets/Worker]

    String[] targetArray = splitTarget(C)

    for(String target : targetArray){

        createRequest(target)--->worker

    }

}

当然,这个调度算法非常简单,我们可以根据单个worker的实时负载情况进行复杂调度算法的增强,以及对于失败任务的补偿机制的增强处理都是在scheduler和worker之间可以调度优化的地方。

最后是worker内部的调度,worker会根据自身处理能力的大小,将多个目标机器进行再次分割,然后并发执行任务。从而提高整个平台的任务处理能力。

写在最后

本文主要跟大家分享了一种分布式任务编排调度平台的设计思路,重点介绍了平台的三个重要特点:分布式,任务编排,和任务调度以及平台后续的优化方向:

一,任务编排处理过程中增加条件判断逻辑;

二,scheduler和worker之间调度算法优化。

三,任务执行过程中中断和人机交互的增强。

我们目前正在基于这个思路进行相关功能的落地。如果大家有其他的见解或者文中有哪些描述不合理的地方。欢迎批评指正。我们的目标就是构建出一个稳定高效的运维任务调度平台,帮助运维人员尽快爬出运维的焦油坑。



作者:丁明威
链接:https://www.jianshu.com/p/e65da69a5c88
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

猜你喜欢

转载自blog.csdn.net/linglingma9087/article/details/82760245