从未降级的搜索技术-Hippo在线服务调度系统

          很久很久以前,有一个PE叫川小生,有一个开发叫子小嘉。双11前,他们按照业务的要求给天猫准备了14倍余量,给主 搜准备了1倍余量。结果11号上午流量涨势喜人啊,嗖嗖往上涨。川小生和子小嘉说不对啊,怎么主搜涨这么厉害天猫只涨4倍呢,川小生掐指一 算,干,到 晚上主搜就挂了啊。俩人怂了,把天猫机器迁一点给主搜吧。于是改clustermap下机器,发binary,发依赖数据,发全量,追增量,起进程,改 clustermap加机器,一通折腾一个半小时过去,总算有惊无险。满心期待到了晚上,主搜流量却木有来,有木有,木有来啊。。。

  上面是一个发生某年双11的一个真实的关于搬机器的故事, 好像每年的双11总会发生点超出我们预期的事情,而每次系统变动又总是让人胆战心惊。2014年的双11我们内心变得很平静,这些事情再也不需要咱自己去干了,因为咱有了自动化的解决方案——来自引擎调度系统小组的Hippo。

一、来自一线的诉求

  • PE/App Ops 同学的诉求
    • 业务安全-健壮的系统-不要随便回收应用的机器-及时完整系统反馈/监控
    • 系统利用率低-资源管理/复用-快速方便的调整应用资源(负载高时扩容、负载低时减机器)
    • 捣腾机器太麻烦、大促搬机器累死的呢-集群资源统一管理
    • 太复杂了-简单易理解,系统解耦(避免牵一发而动全身)-系统可视化
    • 熬夜上线可不可以没有-系统级别支持灰度/平滑上线-支持方便快捷的部署/发布系统
    • 上线麻烦,要解决太多依赖-自动部署解依赖
  • 业务/系统开发同学的诉求
    • 上线不求人,不要排期-更加高效的上线流程-系统解耦,不要相互约束-流程(自动化)友好
    • 不要搭环境,不要搞机器-屏蔽运维操作、提供自动运维服务
    • 方便debug-可重入/可复现的系统环境
    • 简单应用不要开发-提供默认的调度、支持非侵入式接入、轻量级
    • 复杂应用支持自定义调度-支持多层调度
    • 随时可用的测试环境-共享测试集群
    • 快速测试反馈-集成测试自动流程
    • 熟悉的开发语言

这些诉求是我们与相关同学长时间深入沟通拿到的(真是很难感觉到幸福啊),转换到对我们的调度系统的需求:集群集中管理、资源调度、应用托管、高效自动部署/变更、应用安全、应用隔离。

二、为什么造轮子

  • 业内系统
    • Borg、Omega(Google)
    • Autopilot Microsoft
    • ARK/IDLE/Matrix(Baidu)
    • TBorg、Torca (Typhoon.Tencent)
  • 有规模应用开源系统
    • Yarn (Hadoop)
    • Condor、HTCondor (CS.WISC)
    • Mesos (Twitter)
  • 公司内部系统
    • JAVA流 (T4,YARN衍生系统)
    • FUXI (Apsara)

  笔者最早接触的是Condor/HTCondor,搞过网格计算的同学应该比较了解;Goolge的Borg应该算是一开始借鉴了很多Condor的东西,Omega则是在解决borg的单master调度的瓶颈问题;Tencent的Tborg/Torca则是和Borg系统有很深的渊源;Yarn和Mesos应该是被更多的人所熟知,都支持多种计算框架;对AutoPilot的认知更多来自于相关的论文;Baidu的系统其实蛮有意思,特别是IDLE(有个组件可以随意种植在任何机器上,当机器空闲的时候则调度一些低优先级并且可以随时K掉的计算任务上去执行,而且他们的PE人员身背机器利用率的KPI,大家都求着调度任务上去,这和咱们的现状完全是两样);FUXI和T4是集团内的系统,大家想要了解可以在内网找到他们。

  搜索中心的在线系统基本都是基于C++的,跑在Yarn/JAVA上不免显得有点重了,所以Yarn其实在意开始就被我们淘汰掉了,但我们重点研究了它的协议。曾经一度离我们最近的是FUXI和Mesos,再看看我们在线服务系统的基础要求:

  • 高可用性。时刻需要保证完整性,对应到对资源分配的需求就是分配稳定不能在不保证服务完整性的情况下回收机器
  • 资源分配的稳定性,在线服务一般迁移成本比较高,需要比较稳定的分配,不到万不得已的时候不能迁移服务
  • 快速变更能力。搜索负责从海量数据进行召回,一般依赖的数据都很大(现在线上单机典型的数据大小,Binary包:700M;Hadoop,jvm:1G;AliWS词典:500M;词典补丁:100M;索引数据:40G;增量索引:1G;前后台类目映射表:10M;算法模型数据:4G),如何快速的安装部署转移是一个很大的难题

我们要的不仅仅是一个资源管理统一调度系统,而是支撑整个在线服务系统的一揽子工程。回到资源管理上来,FUXI和Mesos在资源回收协议上都是强制回收的,异常情况下会影响服务的完整性,这是不能接受的。对于二进制包/依赖数据的分发FUXI和Mesos都是集中式的拉取对于在线的多replica效率没法满足需求。另外考虑到服务迁移的成本,我们希望调度框架是非植入的(目前FUXI在不继承框架的时候只能进行简单的进程起停,不能监控服务进程的健康),能够做到应用的无痛迁移。针对这些问题,我们与FUXI的同学做过深入的交流,对于我们的这需求暂时还不能满足;Mesos来讲,评估过后发现如果我们去改造Mesos的成本并不比全新开发来得低,至于Docker流的Kubernetes以及集成到Mesos那已经是后话了,Hippo启动的时候Docker还没那么火。最终我们的Hippo定位于专注在线服务管理及支撑,并把资源管理管理做薄,一旦FUXI成熟我们可以将整个Hippo系统跑在去,顺带也将上面的服务平滑迁移到云上(是的,上了Hippo咱们服务就提前上云了),我们的目标是尽量不要重复造轮子。

三、Hippo系统架构

  Hippo实现采用两层系统架构:一层(Master)负责资源管理以及核心调度器调度,二层(AM)为具体应用调度。各应用可定制开发或者使用默认调度器调度。

从未降级的搜索技术-Hippo在线服务调度系统_第1张图片

  Hippo系统采用典型的master-slave中心节点架构,包含两个角色:

  • master 服务器端,负责hippo中所有资源的管理以及全局资源调度/应用管理,在运行时多个master以热备方式保证可用性,主要包括下面几个子模块
    • slave manager, 负责所有机器(slave, 每个slave可以分为多个slot,每个slot对应了一组资源)的管理(确定了Hippo集群的管理域)、收集维护各个客户端节点的资源信息
    • app master manager, 负责所有应用调度器的管理和调度(针对简单应用可以直接调度应用本身节点)
    • resource manager, 负责管理系统所有资源以及为每个应用分配资源(以slot为基本管理和调度单位,目前slot通过静态划分、后续会支持通过需求动态生成)
  • slave 客户端,负责管理某一台具体的物理机,它主要完成下面几个功能
    • 客户端资源收集
    • 应用部署/安装/运行/监控/管理支持
    • 提供接口给app master进行业务进程管理,具体调度任务的执行者
    • 单机资源管理,分slot维护,支持多应用复用物理机并保证应用的完整性和独立性

四、Hippo核心特性

  Hippo作为服务器与在线服务应用的中间层,其核心的功能则是向下抽象管理资源、向上抽象在线服务需求并保证两者有效安全的衔接。这一节将按照这个思路来简单介绍一下Hippo的核心特性。

4.1 资源管理

4.1.1 资源

  资源是对Hippo中Slave这些工作节点服务能力的一个抽象。SCALAR数值型(用于描述CPU核数/MEM大小/DISK大小/网络带宽等可计量资源)、TEXT文本型(用于描述不可计量资源,比如描述磁盘是否是SSD)。其中CPU/MEM作为系统内置资源由Hippo自动探测,其余资源由外部设置。Hippo借鉴FUXI对资源的描述支持任意的虚拟资源,特别的是Hippo引入了一个“EXCLUDE_TEXT ”排他资源类型用于对资源可申请对象的约束。Hippo支持动态修改Slave的自定义资源描述。

4.1.2  SLOT(槽)/机器分槽

Hippo以SLOT为粒度进行资源的管理分配,一个SLOT代表了一组资源组合。主要分为两类:普通槽,主要分给普通应用使用,每个槽对有一个槽号(>=0的整数)标识;系统槽,主要给Hippo自身内置服务组件使用,与普通槽的差别是它不占用逻辑资源,槽号为<0的整数。

  一台机器(slave)被加入Hippo的时候可以申明一组资源并需指定槽数slot_count,Hippo将slave划分成slot_count个普通槽,这些槽均分slave上申明的数值型资源并继承文本型资源(内置自动探测资源属于数值型,会将Slave自动探测汇报的资源均分到每个槽),并自动分配system_slot_count个系统槽(系统槽个数在Hippo启动时指定)。举个例子,在一个配置了一个系统槽的Hippo系统中一个32核64G的机器分两个槽的情形:
从未降级的搜索技术-Hippo在线服务调度系统_第2张图片
Hippo目前只支持这种静态分槽(资源切分)的方式,动态分槽的需求目前还不是很明确,后续会根据需求引入。

4.1.3 资源的申请/分配/回收

  应用对机器的需求全部抽象成资源需求,Hippo支持多TAG全量资源申请协议。一般应用系统都存在不同的角色ROLE,而每个角色对资源的需求也都会不一样,比如一个典型的二层调度应用就至少包含两种角色:应用Admin(二层调度器)+ 应用业务Worker,资源申请TAG则对应了应用ROLE的概念。每个TAG的资源需求描述支持或逻辑,比如某个TAG的资源需求描述可以是这个样子{ {CPU|3200, MEM| 65536} or {MACHINETYPE_A7|1}}: 要求分配的SLOT的CPU大于等于32核并且内存大于等于64G,或者分配的SLOT要有自定义的MACHINETYPE_A7资源。采用全量协议而不是FUXI的增量式协议主要原因有两:一是全量协议实现简单,不容易出错;二是在线服务的应用数目与FUXI的JOB数目差几个数量级,全量协议的资源消耗在今后很长一段时间都不会是瓶颈。

  Hippo目前不存在应用优先级所以资源分配遵循最简单的FIFO逻辑,采用二维打分机制来进行最终的分配,一维为资源分(根据请求与资源的最小满足原则计算得分);二维为Hippo引入的“资源亲近性”得分,“资源亲近性”用于描述一个应用对某台机器(SLOT)的“喜好”,Hippo支持三种类型的亲近性:_PREFER、_BETTERNOT、_PROHIBIT,这关系依附于<app, resourceTag, slaveAdrees>三元关系组,分别表示“对应用app的reourceTag尽量分配slaveAdrees上的槽”、“对应用app的reourceTag尽量不要分配slaveAdrees上的槽”、“对应用app的reourceTag不能分配slaveAdrees上的槽”。同时为了更好控制,Hippo给“资源亲近性”加上了失效时间。

  Hippo中SLOT的回收有两个发起点:一是应用主动释放;二是Hippo协议应用释放。在Hippo上除了机器异常情况下可以强制下线机器外,不允许SLOT当前Owner以外的角色发起SLOT回收,以保证应用的安全。一个正常的机器回收逻辑Hippo中是这样实现的:(1)将要回收的机器打上offline标签,该机器上未分配出去的Slot就不能再被分配;(2)hippo master要求使用该机器的应用回收对应的Slot;(3)应用额外申请一批Slot,将Worker迁移上去,保证服务的完整后主动释放要求回收的Slot;(4)Hippo Master发现该机器上所有的Slot已经回收则标识该机器可回收。

  Hippo的整体资源管理方案还比较初级,例如每次分配都追求局部最优并不能做到全局最优,后续会考虑抽象出更多的决策因子(比如包/数据分布)争取提高Hippo的整体效率。

4.2 服务托管

  Hippo系统内对在线服务做了最基础的一层需求抽象—-进程组模型,进程组是在线系统的最小调度单位,我们可以以此为基础构建更高层次的服务抽象。Hippo的进程组模型由<依赖包,进程描述,依赖数据>这样一个三元组来描述,一个进程组包含0个或者多个依赖包、0个或者多个进程实例、0个或者多个依赖数据,特殊的一个空的进程组在Hippo中也是支持的,它不执行任何操作只是持有资源。一个进程组运行在一个Slot上,Hippo Slave提供接口给应用在一个Slot上启动、停止一个进程组,Hippo会根据应用提交的进程组描述自动进行包安装、依赖数据拉取、进程启动管理或者执行相应的退出流程。本章后面部分会分别针对依赖包,进程描述,依赖数据进行一个简单的介绍。

4.2.1 依赖包抽象与运行时支持

  Hippo支持三种类型的依赖包格式:普通文件/目录、RPM包、压缩包。其中普通文件/目录与压缩包需要是一个完整的可执行环境,Hippo只负责将这些数据拷贝到当前应用的安装目录下,而对于RPM包,Hippo首先会将包下载下来然后使用内置的Ainst2工具(一个强大的rpm包解依赖安装工具,支持远程并行安装,目前我们团队在维护)来安装到相应的目录。Hippo中任何依赖包的变化都意味着进程组的重新启动,可以把依赖包视为一种静态依赖。任何一次进程组执行都首先从下载依赖包开始(下载主要分两种方式,一种是通过yum,另外一种是通过Hippo内置的数据链式分发服务DP2,因此包可以发布在任何DP2支持的数据源上),如果下包失败Hippo会自动重试直到最后报告安装包失败。

4.2.2 进程描述抽象与运行时支持

  Hipppo抽象了进程描述,包括进程的启动方式(Daemon/非Daemon)、命令行及参数、环境变量,Hippo特殊的是可以对每个进程指定一个名字和一个instanceId, instanceId对应了一次显示的启动,Hippo如果发现新提交的进程instanceId与当前运行的不一致则会重新执行一次(通常用于有变更需要进程重启生效的场景)。所有的进程由Hippo启动,对于非Daemon进程,Hippo会保证其正常的执行一次;对于Daemon进程只要Hippo发现不存在就会重新拉起(启动时会有重试次数限制,超过限制将直接报告失败),并记录失败信息。

4.2.3 数据依赖抽象与运行时支持

  Hippo内置了一个数据管理模块,负责应用数据的自动拉取(使用DP2服务)及版本管理,应用层面不需要显示的执行数据准备工作。针对在线服务一般依赖的数据比较多的特点,数据管理借鉴了git的全量snapshot版本管理模式同时借助DP2的增量传输协议高效的解决了数据依赖问题,通常数据管理先会把依赖的数据拉取到一个全局缓存然后再链接到应用的工作目录。数据管理同时负责数据的生命周期管理,自动清除过期的数据。当应用有数据变更时,只需要修改数据依赖描述并提交给Hippo,Hippo会反馈数据准备的状态,应用一旦发现数据准备好重新启动进程新数据即生效。

  进程组模型提供了解决服务托管的基础支持,对于那些一维的简单应用使用Hippo内置的Global App Master即可很好的工作,但是对于那些复杂的应用来讲还需要自己编写二层调度器,虽然Hippo现在提供了一个高度封装的SDK但是门槛还是挺高。目前Hippo正在探索更高层次服务抽象,比如最近团队正在开发的App Framework, 而笔者最终希望能够抽象出一个通用的服务模型(以及服务能力模型)通过简单的配置就能完成服务的所有自动化调度(匹之于离线系统的JOB模型)。

4.3 服务安全保障

  由于Hippo调度的都是在线服务系统,所以应用的安全性(可用性)至关重要。简单的从下面几点看看Hippo是如何来保障应用安全的:

  • 代码层级。Hippo一直强调“非注入”,即不要求运行在Hippo上的程序要嵌入Hippo的任何模块(特别是业务进程),业务进程的健壮性完全由业务系统开发自己决定。唯一存在代码依赖在二层调度器使用Hippo SDK(可以选择使用原生Proto协议),包括后面可能引入的调度框架都只影响调度器,仍然保持对业务进程的零注入。
  • 运行时环境。包括:
    • Hippo系统的可用性。Hippo通过Leader election实现了多Master热备;在部署上不跨机房很大程度能够避免网络割裂;决策保护,在出现一定Slave心跳丢失时Master停止决策。
    • 应用环境的稳定。分配方案持久化/决策安全/资源协商回收/进程托管监控/进程信息持久化/进程恢复。
    • 应用间隔离。由于现在需求不强烈(目前搜索中心的引擎基本都是单机独占),所以暂时只做到了应用工作目录的隔离。随着Hippo上应用范围的扩大,应用间隔离一定会实现,容器/Docker等技术准备工作已经完成,Demo开发发现公司的系统版本及安全策略导致现有的解决方案都不会太完美,暂时已经停止等待更好的时机。
    • Hippo独立升级。Hippo系统升级不影响应用进程,Hippo恢复后自动重新接管应用进程。Hippo自身以及涉及到与应用的接口兼容性是Hippo开发的首要准则,出现重大变更时也会有协议转换层保证平滑过渡。
  • 监控报警机制。目前做的一些工作包括:可视化展示、基础监控体系对接、提供BabySitter扩展模块支持等。未来我们会把应用相关的监控报警逻辑放到体系内来。

五、Hippo在线

  双11前我们完成了多个机房的Hippo集群部署上线,将主搜和天猫的后台引擎完全迁移上了Hippo, 并且很好的的支撑了双11需求。Hippo过去时间主要精力在搭建基础平台,业务层面更多的关注了“自动化运维”的一些需求,在未来一段时间我们将主要关注在线服务抽象,针对系统应用开发人员提供更高层次的抽象以降低业务迁移成本,目标是一年后90%的搜索机器(在线服务系统)跑在Hippo上。

你可能感兴趣的:(自动化运维,调度系统,分布式技术)