经典论文 | Google使用Borg进行大规模集群的管理(第五章、第六章)


【编者的话】这两章探讨的是Borg的资源利用率和隔离性的问题。看文章到此,可是能最枯燥的两章,既没有用户的角度,也没有架构的指点江山,有的只是一些枯燥的定量分析。但我觉得,这恰恰是精华所在,也是能够把软件从一种作坊式定性感觉的设计上升到由科学实验、数据定论的科学指导的关键所在。由此可以看出google的工程师不但能实现好的工程,也是接受过严密的科学训练的科学家。

5. 使用效率

Borg的一个主要目的就是有效的利用Google的机器舰队,这可是一大笔财务投资:让效率提升几个百分点就能省下几百万美元。这一节讨论了和计算了一些Borg使用的技术和策略。

5.1 测度方法论

我们的job部署是有资源约束的,而且很少碰到负载高峰,我们的机器是异构的,我们从service job回收利用的资源跑batch job。所以,为了测量我们需要一个比“平均利用率”更抽象的标准。在做了一些实验后我们选择了cell密度(cell compaction):给定一个负载,我们不断的从零开始(这样可以避免被一个倒霉的配置卡住),部署到尽可能小的Cell里面去,直到再也不能从这个cell里面抽机器出来。这提供了一个清晰的终止条件,并促进了无陷阱的自动化比较,这里的陷阱指的是综合化的工作负载和建模[31]。一个定量的比较和估算技术可以看[78],有不少微妙的细节。

我们不可能在线上的cell做性能实验,所以我们用了Fauxmaster来达到高保真的模拟效果,使用了真的在线cell的负载数据包括所有的约束、实际限制、保留和常用数据($5.5)。这些数据从2014-10-1 14:00 PDT的Borg快照(checkpoints)里面提取出来。(其他快照也产生类似的结论)。我们选取了15个Borg cell来出报告,先排除了特殊目的的、测试的、小的(<5000机器)的cell,然后从剩下的各种量级大小的cell中平均取样。

在压缩cell实验中为了保持机器异构性,我们随机选择去掉的机器。为了保持工作负载的异构性,我们保留了所有负载,除了那些对服务和存储需要有特定需求的。我们把那些需要超过一半cell的job的硬限制改成软的,允许不超过0.2%的task持续的pending如果它们过于挑剔机器;广泛的测试表明这些结果是可重复的。如果我们需要一个大的cell,就把原cell复制扩大;如果我们需要更多的cell,就复制几份cell。

所有的实验都每个cell重复11次,用不同的随机数发生器。在图上,我们用一个横线来表示最少和最多需要的机器,然后选择90%这个位置作为结果,平均或者居中的结论不会代表一个系统管理员会做的最优选择。我们相信cell压缩提供了一个公平一致的方式去比较调度策略:好的策略只需要更少的机器来跑相同的负载。

我们的实验聚焦在调度(打包)某个时间点的一个负载,而不是重放一段长期的工作踪迹。这部分是因为复制一个开放和关闭的队列模型比较困难,部分是因为传统的一段时间内跑完的指标和我们环境的长期跑服务不一样,部分是因为这样比较起来比较明确,部分是因为我们相信怎么整都差不多,部分是因为我们在消费20万个Borg CPU来做测试——即使在Google的量级,这也不是一个小数目(译者:就你丫理由多!)
图4.jpg

在生产环境下,我们谨慎的留下了一些顶部空间给负载的增加,比如一些“黑天鹅”时间,负载高峰,机器故障,硬件升级,以及大范围故障(供电进灰)。图4显示了我们在现实世界中可以把cell压缩到多小。上面的基线是用来表示压缩大小的。

5.2 Cell的共享使用

几乎我们所有的机器都同时跑prod和non-prod的task:在共享Borg cell里有98%的机器同时跑这2种task,在所有Borg管理的机器里面有83%同时跑这2种task(我们有一些专用的Cell跑特定任务)。
图5.jpg

鉴于很多其他的组织把面向用户应用和批处理应用在不同的集群上运行,我们设想一下如果我们也这么干会发生什么情况。图5展现了在一个中等大小的Cell上分开跑我们prod和non-prod的工作负载将需要20-30%多的机器。这是因为prod的job通常会保留一些资源来应对极少发生的负载高峰,但实际上在大多情况下不会用这些资源。Borg把这批资源回收利用了($5.5)来跑很多non-prod的工作,所以最终我们只需要更少的机器。
图6.jpg

大部分Borg cell被几千个用户共享使用。图6展现了为什么。对这个测试,如果一个用户消费超过了10TiB内存(或100TiB),我们就把这个用户的工作负载分离到一个单独的Cell里面去。我们目前的策略展现了它的威力:即使我们设置了这么高的阈值(来分离),也需要2-16倍多的Cell,和20-150%多的机器。资源池的方案再次有效地节省了开销。

但是,或许把很多不相关的用户和job类型打包放到一台机器上,会造成CPU冲突,然后就需要更多的机器进行补偿?为了验证这一点,我们看一下在同一台机器,锁定时钟周期,每指令循环数CPI(cycles per instruction)在不同环境的task下是怎么变化的。在这种情况下,CPI是一个可比较的指标而且可以代表冲突度量,因为2倍的CPI意味着CPU密集型程序要跑2倍的时间。这些数据是从一周内12000个随机的prod的task中获取的,用硬件测量工具[83]取的,并且对采样做了权重,这样每秒CPU都是平等的。测试结果不是非常明显。

  1. 我们发现CPI在同一个时间段内和下面两个量正相关:这台机器上总的CPU使用量,以及(强相关)这个机器上同时跑的task数量;每往一台机器上增加1个task,就会增加0.3%的CPI(线性模型过滤数据);增加一台10%的CPU使用率,就会增加小于2%的CPI。即使这已经是一个统计意义显著的正相关性,也只是解释了我们在CPI度量上看到的5%的变化,还有其他的因素支配着这个变化,例如应用程序固有的差别和特殊的干涉图案[24,83]。

  2. 比较我们从共享Cell和少数只跑几种应用的专用Cell获取的CPI采样,我们看到共享Cell里面的CPI平均值为1.58(σ=0.35,方差),专用Cell的CPI平均值是1.53(σ=0.32,方差)。也就是说,共享Cell的性能差3%。

  3. 为了搞定不同Cell的应用会有不同的工作负载,或者会有幸存者偏差(或许对冲突更敏感的程序会被挪到专用Cell里面去),我们观察了Borglet的CPI,在所有Cell的所有机器上都会被运行。我们发现专用Cell的CPI平均值是1.20(σ=0.29,方差),而共享Cell里面的CPI平均值为1.43(σ=0.45,方差),暗示了在专用Cell上运行程序会比在共享Cell上快1.19倍,这就超过了CPU使用量轻负载的这个因素,轻微的有利于专用Cell。


这些实验确定了仓库级别的性能测试是比较微妙的,加强了[51]中的观察,并且得出了共享并没有显著的增加程序运行的开销。

不过,就算我们假设用了我们结果中最不好的数据,共享还是有益的:比起CPU的降速,在各个方案里面减少机器更重要,这会带来减少所有资源的开销,包括内存和硬盘,不仅仅是CPU。

5.3 大Cell

图7.jpg

Google建立了大Cell,为了允许大的任务运行,也是为了降低资源碎片。我们通过把负载从一个cell分到多个小cell上来测试后面那个效应(降低碎片效应),随机的把job用round-robin方式分配出去。图7展示了用很多小cell会明显的需要更多机器。

5.4 资源请求粒度

图8.jpg

Borg用户请求的CPU单位是千分之一核,内存和硬盘单位是byte。(1核是一个CPU的超线程,在不同机器类型中的一个通用单位)。图8展现了这个粒度的好处:CPU核和内存只有少数的“最佳击球点”,以及这些资源很少的相关性。这个分布和[68]里面的基本差不多,除了我们看到大内存的请求在90%这个线上。
图9.jpg

提供一个固定尺寸的容器和虚拟机,在IaaS(infrastructure-as-a-service)提供商里面或许是比较流行的,但不符合我们的需求。为了展现这一点,我们把CPU核和内存限制做成一个个尺寸,然后把prod的job按照大一点最近的尺寸去跑(取这2个维度的平方值之和最近,也就是2维图上的直线),0.5核的CPU,1G的内存为差值。图9显示了一般情况下我们需要30-50%多的资源来运行。上限来自于把大的task跑在一整台机器上,这些task即使扩大四倍也没办法在原有Cell上压缩跑。下限是允许这些task等待(pending)。(这比[37]里面的数据要大100%,因为我们支持超过4中尺寸而且允许CPU和内存无限扩张)。

5.5 资源再利用

一个job可以声明一个限制资源,是每个task能强制保证的资源上限。Borg会先检查这个限制是不是在用户的配额内,然后检查具体的机器是否有那么多资源来调度这个task。有的用户会买超过他们需要的配额,也有用户会的task实际需要更多的资源去跑,因为Borg会杀掉那些需要更多的内存和硬盘空间的task,或者卡住CPU使用率不上去。另外,一些task偶尔需要使用他们的所有资源(例如,在一天的高峰期或者受到了一个拒绝服务攻击),大多时候用不上那么多资源。

比起把那些分出来但不用的资源浪费掉,我们估计了一个task会用多少资源然后把其他的资源回收再利用给那些可以忍受低质量资源的工作,例如批处理job。这整个过程被叫做资源再利用(resource reclamation)。这个估值叫做task自留地资源(reservation),被Borgmaster每过几秒就计算一次,是Borglet抓取的细粒度资源消费用率。最初的自留地资源被设置的和资源限制一样大;在300s之后,也就是启动那个阶段,自留地资源会缓慢的下降到实际用量加上一个安全值。自留地资源在实际用量超过它的时候会迅速上升。

Borg调度器(scheduler)使用限制资源来计算prod task的可用性($3.2),所以这些task从来不依赖于回收的资源,也不提供超售的资源;对于non-prod的task,使用了目前运行task的自留地资源,这么新的task可以被调度到回收资源。

一台机器有可能因为自留地预估错度而导致运行时资源不足 —— 即使所有的task都在限制资源之内跑。如果这种情况发生了,我们杀掉或者限制non-prod task,从来不对prod task下手。
图10.jpg

图10展示了如果没有资源再利用会需要更多的机器。在一个中等大小的Cell上大概有20%的工作负载跑在回收资源上。
图11.jpg

图11可以看到更多的细节,包括回收资源、实际使用资源和限制资源的比例。一个超内存限制的task首先会被重新调度,不管优先级有多高,所以这样就很少有task会超过内存限制。另一方面,CPU使用率是可以轻易被卡住的,所以短期的超过自留地资源的高峰时没什么损害的。

图11暗示了资源再利用可能是没必要的保守:在自留地和实际使用中间有一大片差距。为了测试这一点,我们选择了一个生产cell然后调试它的预估参数到一个激进策略上,把安全区划小点,然后做了一个介于激进和基本之间的中庸策略跑,然后恢复到基本策略。
图12.jpg

图12展现了结果。第二周自留地资源和实际资源的差值是最小的,比第三周要小,最大的是第一和第四周。和预期的一样,周2和周3的OOM率有一个轻微的提升。在复查了这个结果后,我们觉得利大于弊,于是把中庸策略的参数放到其他cell上部署运行。

6. 隔离性

50%的机器跑9个以上的task;最忙的10%的机器大概跑25个task,4500个线程[83]。虽然在应用间共享机器会增加使用率,也需要一个比较好的机制来保证task之间不互相冲突。包括安全和性能都不能互相冲突。

6.1 安全隔离

我们使用Linux chroot监狱作为同一台机器不同task之间主要的安全隔离机制。为了允许远程debug,我们以前会分发ssh key来自动给用户权限去访问跑他们task的机器,现在不这么干了。对大多数用户来说,现在提供的是borgssh命令,这个程序和Borglet协同,来构建一个ssh shell,这个shell和task运行在同样的chroot和cgroup下,这样限制就更加严格了。

VM和安全沙箱技术被使用在外部的软件上,在Google’s AppEngine (GAE)[38]和Google Compute Engine(GCE)环境下。我们把KVM进程中的每个hosted VM按照一个Borg task运行。

6.2 性能隔离

早期的Borglet使用了一种相对原始粗暴的资源隔离措施:事后内存、硬盘、CPU使用率检查,然后终止使用过多内存和硬盘的task,或者把用太多CPU的激进task通过Linux CPU优先级降下来。不过,很多粗暴的task还是很轻易的能影响同台机器上其他task的性能,然后很多用户就会多申请资源来让Borg减少调度的task数量,然后会导致系统资源利用率降低。资源回收可以弥补一些损失,但不是全部,因为要保证资源安全红线。在极端情况下,用户请求使用专用的机器或者cell。

目前,所有Borg task都跑在Linux cgroup-based资源容器[17,58,62]里面,Borglet操作这些容器的设置,这样就增强了控制因为操作系统内核在起作用。即使这样,偶尔还是有低级别的资源冲突(例如内存带宽和L3缓存污染)还是会发生,见[60,83]。

为了搞定超负荷和超请求,Borg task有一个应用阶级(appclass)。最主要的区分在于延迟敏感latency-sensitive (LS)的应用和其他应用的区别,其他应用我们在文章里面叫batch。LS task是包括面向用户的应用和需要快速响应的共享基础设施。高优先级的LS task得到最高有待,可以为了这个把batch task一次饿个几秒种。

第二个区分在于可压缩资源(例如CPU循环,disk I/O带宽)都是速率性的可以被回收的,对于一个task可以降低这些资源的量而不去杀掉task;和不可压缩资源(例如内存、硬盘空间)这些一般来说不杀掉task就没法回收的。如果一个机器用光了不可压缩资源,Borglet马上就会杀掉task,从低优先级开始杀,直到剩下的自留地资源够用。如果机器用完了可压缩资源,Borglet会卡住使用率这样当短期高峰来到时不用杀掉任何task。如果情况没有改善,Borgmaster会从这个机器上去除一个或多个task。

Borglet的用户空间控制循环在未来预期的基础上给prod task分配内存,在内存压力基础上给non-prod task分配内存;从内核事件来处理Out-of-Memory (OOM);杀掉那些想获取超过自身限制内存的task,或者在一个超负载的机器上实际超过负载时。Linux的积极文件缓存策略让我们的实现更负载一点,因为精确计算内存用量会麻烦很多。

为了增强性能隔离,LS task可以独占整个物理CPU核,不让别的LS task来用他们。batch task可以在任何核上面跑,不过他们只被分配了很少的和LS task共享的资源。Borglet动态的调整贪婪LS task的资源限制来保证他们不会把batch task饿上几分钟,有选择的在需要时使用CFS带宽控制[75];光有共享是不行的,我们有多个优先级。
图13.jpg

就像Leverich [56],我们发现标准的Linux CPU调度(CFS)需要大幅调整来支持低延迟和高使用率。为了减少调度延迟,我们版本的CFS使用了额外的每cgroup历史[16],允许LS task驱逐batch task,并且避免多个LS task跑在一个CPU上的调度量子效应(scheduling quantum,译者:或许指的是互相冲突?)。幸运的是,大多我们的应用使用的每个线程处理一个请求模型,这样就缓和了持久负载不均衡。我们节俭地使用cpusets来分配CPU核给有特殊延迟需求的应用。这些措施的一部分结果展现在图13里面。我们持续在这方面投入,增加了线程部署和CPU管理包括NUMA超线程、能源觉察(例如[81]),增加Borglet的控制精确度。

Task被允许在他们的限制范围内消费资源。其中大部分task甚至被允许去使用更多的可压缩资源例如CPU,充分利用没有被使用的资源。大概5%的LS task禁止这么做,主要是为了增加可预测性;小于1%的batch task也禁止。使用超量内存默认是被禁止的,因为这会增加task被杀的概率,不过即使这样,10%的LS task打开了这个限制,79%的batch task也开了因为这事MapReduce框架默认的。这事对资源再回收($5.5)的一个补偿。Batch task很乐意使用没有被用起来的内存,也乐意不时的释放一些可回收的内存:大多情况下这跑的很好,即使有时候batch task会被急需资源的LS task杀掉。

原文链接:Large-scale cluster management at Google with Borg(翻译:难易

1 个评论

注,第一段的cell密度这个术语改名为cell压缩度更加合理一点。

要回复文章请先登录注册