资源管理最佳实践

这个是作者原文

原作者是来自Google的Luis Quesada和Doug Colish,他们两位都是Google SRE。

感觉写的挺好的,就搬了一些主要内容过来再加上自己的一些想法。

容量管理的原则

成功的容量管理需要从两点来分配资源:

  1. 针对服务初始运行时的资源供给。
  2. 资源规划:保障服务未来的可用性。

基于这点,容量管理需要基于如下三个原则来保证服务的可扩展性,可用性,可管理性。

  1. 服务必须高效的利用好现有的资源。特别是大型服务需要部署和维护相当多的资源。

    这里我们主要是反对浪费和低利用率。很多服务刚上线的时候会有很多不合理的预期,导致巨大的资源浪费。

  2. 服务必须可靠地运行。限制资源的使用来提升资源使用率可能会导致故障风险和服务中断,因此我们需要在可用性和使用率上进行平衡。

    有些时候我们因为降本的需求,会把资源用的特别满导致出现各种各样奇怪的问题。最终影响到服务的SLA。

  3. 服务增长必须是可提前预测的。

    比如运营活动,比如客户上量。还有正常的增长趋势。

容量管理的复杂性

大型服务是一个时长有非预期行为的复杂有机体。你需要考虑很多方面,在跟工程师沟通后可能还会变更服务的范围。

  1. 服务性能:理解不同组件在服务处于过载下的表现。

    这里一个是靠压测,压测的时候需要考虑数据量和请求量。

  2. 服务失效模式:已知情况下的失效模式和未知场景下失效可能的行为。准备一个可能的限流清单和你能想到的服务依赖。

    这个就需要平时多演练,把这些演练整理成服务的rto。而未知场景基本靠bypass这样的行为。限流清单就是关键时刻你保什么了。

  3. 基于用户所在地域和使用方式来确定用户请求数和流量。

    同样的服务,不同的用户使用行为会产生不同的请求,这个一定要注意的,特别是ToB类的产品更是如此。

  4. 自然增长:随着服务长期运行后所需要的资源。

    比如用户的自然增加,用户上传的内容自然增加等等。

  5. 非自然增长:新增功能或者服务比预期的更成功都会对资源产生长期影响。

    这个我们很容易理解,新增功能可能会引入新的资源,或者请求逻辑变化了等等。

  6. 扩展:如果要给服务资源,我们需要知道如何扩展。

    就是如果要扩展服务,我们需要知道怎么扩展,需要扩展哪些资源,是横向扩展还是向上扩展。

  7. 市场分析:在需要新的资源的时候需要评估市场的变化。研究可以提升性能,可用性和效率的新技术。调查如何能够快速适应新技术,比如用SSD代替HDD。

    这里说的就是新技术的引入,有些是硬件的,比如SSD,智能网卡,新的CPU等等,有些是软件本身的提升,或者是新的中间件等等。

容量管理的目标是控制不确定性。在未知的情况下也能有效运行以及在将来也能运行。这个需要在两方面进行权衡:效能和可用性,精确和复杂性,付出和回报。
我们使用数据驱动去进行容量决策。在创新的道路上,你仍将会犯错以及不停的救火。最终结果是得到一个可靠的关键服务。

资源供给解决了一个战术上的问题,就是我现在如何保持服务运行正常?。 而容量规划解决的是如何在可预见的未来保持服务运行正常。

后面章节我们会详细讨论这些内容。

资源供给

我们讨论的是服务系统,即通过通过查找数据来响应用户请求。然而,这些原则同样可以应用于数据存储服务、数据转换服务以及大多数其他可以用计算机完成的服务。

资源供给涉及到确定服务所需的目标资源利用率并分配这些资源。目标利用率被定义为特定资源类别的最高可能利用率,使服务能够可靠地运行。资源类别是指特定类型的计算资产,如CPU、RAM、存储等。

使用实际的需求信号作为输入,输出就是一堆资源集。

就如下图所展示的。就是一些我们服务的运营指标和对应的计算机资源。

资源短缺的影响

不同资源类型的短缺会产生不同失败类型。

当资源是关键路径中的瓶颈时,用户体验上就会感受到延迟。在最坏的情况下,瓶颈会导致请求积压,从而导致延迟不断增加,并最终导致排队请求的超时。如果没有适当的缓解计划,该服务将无法处理请求,并遭受中断。中断将持续,除非请求流量下降,服务catch,或者直到服务重新启动。

关键路径上的资源有如下几种:

  1. 处理能力
  2. 网络
  3. 存储吞吐

如果资源是在非关键路径中的瓶颈,那么服务的一些非实时性的关键功能会遭受延迟,例如维护和一些异步的进程。如果这些进程延迟的足够长,那可能会影响到服务的性能,功能表现,数据一致性,甚至一些异常的情况会引发故障。

当服务耗尽存储时,写入就会失败。甚至某些读取操作是依赖于写操作,那也可能失败。

当其他资源比如内存或者网络sockets空闲很低的情况下,服务也有可能会崩溃,重启或者hang住。一些服务在低资源的情况下会进行频繁的gc或者产生其他不正确的行为。这些错误会降低服务的容量,并可能触发需要人工交互来解决的级联故障场景。

有关缓解策略,请参见下文的减少中断影响一节。

这一章主要是说明当资源紧缺的时候会出现很多异常的问题,有些可能只是队列堵了,或者gc多了,但是也有一些处于关键路径上的就会产生故障。

评估使用率

由于它们不同的性质,每个服务和每种资源类型的资源利用率和目标利用率都是不同的。为了估计特定服务的目标利用率,需要考虑以下每一个方面。

比如cpu,跑业务和跑数据库,那它们对于cpu的使用率就完全不一样。

峰值使用率

服务的峰值使用量是在给定时间段内的最高使用率,这取决于服务的性质和用户基础。与商业业务相关的服务的凌晨时间可能会达到工作日的高峰。社会相关服务在下午晚些时候、晚上、周末,或与音乐会、体育赛事等社交活动同步。当一个意外的事件发生时,使用量可能会下降或飙升。一个全球服务的用户基础分布在不同的国家和时区,形成了一个更为复杂的日常流量模式。

这就是不同的业务导致的,比如同样做在线教育,如果服务是面向学校的,那峰值可能是早上8点,下午2点这样的,而如果是面试课外补习班的,那高峰可能是晚上8点啥的。而如果是全球服务的,那可能会更复杂,不过好处是资源的利用率会比较高。

假设非恒定负载,在高峰期间,资源利用率不应超过服务所分配资源的100%。通过不使用其全部资源,服务具有足够的能力为峰值服务,并且不会以任何浪费的方式过度配置。

最大峰值利用率

即使在高峰时期,以100%的利用率运行该服务也是一个坏主意。有些软件,变成语言或平台在CPU使用达到100%之前就会出现异常行为或出现不正确的gc。如果组件的内存利用率达到100%,服务将会内存不足(OOM)错误而导致崩溃。

精细地调整监控以在足够小的时间框架(微秒亦或秒)内捕捉精确的资源利用率是一项繁琐的任务。因此,为低延迟应用确定资源使用峰值是困难的。

这个比较好理解,毕竟我们监控很多时候就是10秒,15秒这样的,要到秒级的程度,那收集的日志量对于大型服务来说是非常庞大的。

冗余

第一次碰到的软件,硬件问题或者是计划内的维护都可能导致服务组件出现故障或重启。这可能会导致小到单个二进制实例崩溃,大到整个服务离线等故障。

冗余是一种系统设计原则,它包括只有在替换其他失败的组件时才被激活的重复组件。冗余度用N+x表示,其中N为活动组件的总数,x为备份组件的个数。因此,N+3表示有三个系统组件可能会发生故障,因为有三个重复的组件可以替换它们。同时,无论组件的总数(N)如何,该服务都保持完整的功能。

比如阿里云说的所有云盘都是3副本,那其实就是N+2。但是这种云盘替换本质上对于运行中的实例并不是无损的,因为对应的fd其实都是变化了的,这个时候如果是云盘本身出问题,那只能是重启实例去恢复。但是像物理硬盘中RAID1,RAID5这样的,这种是无需重启实例。

冗余可以在同一区域(AZ)或跨区域(AZ)内应用。区域(AZ)是一个独立的故障域,位于不同于其他区域的物理站点,因此网络问题或自然灾害不会同时影响一个区域。

这个在云上大家要特别注意这点,我们一般至少都是3AZ部署的。但是你自己的服务在部署到AZ的时候一定要分开AZ进行,特别一些主从的服务都必须要跨AZ的。

相同AZ内的冗余

实现单一AZ的冗余是非常容易实现的。

因为在单一az中,你想提供冗余那就多在不同的物理机上启动服务实例,然后通过前面的负载均衡在出问题的时候进行重定向流量就可以了。具体的冗余度需要看你的SLA来决定。
当然单一az的冗余无法从根本解决由于对外网络中断,电力中断,自然灾害等问题产生的故障。

不过单一az我们也可以做的更好一点,不要在一排机柜里,不要所有相同实例的服务在同一交换机下等等。

跨AZ的冗余

跨az的冗余就会复杂很多。

跨az就是需要保护自己免受单个az中断的影响。通过在多个az中部署复制副本或服务堆栈的完整副本,您可以跨区域实现冗余,以适应峰值时的服务负载。如上所述,无论复制副本的数量如何(N),该服务的区域冗余程度的定义如下:
N+0:当服务启动和运行时,但不能容忍任何区域宕机
N+1:当服务可以承受一个区域宕机
N+2:当它仍然可以服务与两个区域宕机

虽然其中一些冗余涉及到容量,但它也与服务体系结构本身有关。例如,一致性的存储服务通常要求大多数副本都在线,以确保不会回滚写入的操作。

为N+2提供服务对可靠性有积极影响:可以立即对整个区域进行维护,在维护期间会降低N+1的冗余。该服务仍然可以容忍在其他地区发生的意外事件, 这时候冗余度降低到N+0,但不会导致中断。请注意,将故障转移到另一个区域可能会影响服务的延迟。

冗余容量的成本

一般来说N后面的数字越大越好,这样的好处是每个N所需要的容量就会降低。如果你是N+1, 那你每个N就必须能够承担50%的请求才行,不然一旦一个挂了,你另外2个节点是没法支撑起100%的流量的。

一般在云上就找那些同一区域中az相对多的区域,比如5个6个什么的,这样方便你后面进行部署,而且有些云厂商跨az是免费的。

同类和异构的服务

对于具有相同副本数的的服务,它比那些具有不同副本数的服务更容易实现冗余。

你的服务必须能够控制失败在最大的区域中。因为如果区域具有不同的冗余(即异构),则在每个区域中承受其他最大区域的不可用所需的能力是不同的。其结果是,较小的区域需要更多的资源,而服务相同负载所需的总体资源也更高。

复制和分布式的流量

对于冗余的配置还依赖于流量特征。

无状态服务,例如处理用户请求的web服务器就可以接收分布在副本之间的流量。从存储服务中读取的请求也可以分布在不同区域的副本中。为N+1或N+2配置这些参数是很简单的,并且遵循前面示例中的逻辑。

处理跨区域复制的请求的服务,如写操作,它们的行为也有所不同。每个对实体的写入都需要最终写入每个副本,以保持服务的数据在副本之间保持一致。这里就是最终一致性,大部分情况下这种服务都是先写入大部分节点就返回了,但是一些一致性非常高,但是性能要求不高的就可以等待全部写入后再进行返回。这个就是根据不同的场景取舍的问题。

当副本不可用时,复制的写入请求不会对保留运行的副本造成额外的负载。但是一些服务可能会进行re-balance,所以也有可能有成本。 然而,当不可用的复制副本重新在线时,就会产生一个成本。此副本需要赶上在停机期间错过的写操作。此操作增加了其负载。仍在运行的副本提供了同步恢复副本所需的数据,从而增加了恢复期间所有副本的负载。理想情况下,这是有上限的,以避免损害整个复制副本集的低延迟流量。

每个服务和每个组件都可以接收到不同比例的复制的流量和分布式流量,这在资源配置时需要考虑在内。

延迟不敏感进程

服务通常有对延迟不敏感的进程比如批处理作业、异步请求、维护和实验。

但是,这些过程在服务处理生产负载时给服务的延迟敏感的进程产生压力。因此,该服务需要额外的资源来适应更高的峰值,从而增加了其成本。

您可以通过分配较低的优先级或在低负载期间调度它们,从而最小化对延迟不敏感的请求的额外成本。注意,这两种解决方案都需要经过适当的测试和仔细的部署,以防止服务中断。

给未知部分增加资源

最后一个需要考虑的方面是一个未知的因素。在提供服务时,有很多很好的理由来添加额外的资源:例如,由另一个团队支持的底层库的性能衰退,或者在实现团队外部需求时,例如加密所有rpc。这个比如要我们突然要做全加密链路,那这种开销其实非常巨大,对于cpu和存储都是会产生巨大的开销。

如果出现任何问题,备用容量可以根据延迟和错误情况来保持服务按照预期运行。但是,请记住,这个决定可能是昂贵的,所以要确保在可靠性、可预测性和可伸缩性方面的权衡是值得的成本。

容量规划

资源配指的是确定正确的资源数量以保持服务的正常运行,而容量规划需要预测需求,以保证未来的资源供应。

这个如果你使用的是云上的资源,那这个其实问题不大,现在大部分云本身的规模都远超你的需求。不会像贵司当年那样要扩容,xx云说没有资源,最后只能调配别的客户的资源,省出一些资源给我们来使用。
但是你要知道你在后期的扩容的重点是在哪里,以及大概的费用情况。

容量规划概述

与资源配置一样,容量规划也是在试图确定维持服务所需的每个计算资产(资源类)的数量。然而,它涉及到在多个时间点做出这些决定:例如,三个月、六个月或一年的资源需求。

未完待续