乐趣区

关于devops:多服务在系统中的公平性

咱们能够通过对 API 速率限度来管理系统申请,避免过载。

Service-oriented architecture(SOA, 面向服务的架构)具备十分清晰的权责划分,以及使零碎之间耦合尽量涣散。

偏心

bin-packing algorithms

  1. 执行 placement algorithms 以在 fleet 中找到一个 spot 来运行新的 workload。(相似于找到一个有足够容量来搁置 workload 的 bin)
  2. 继续监控每个 workload 和每个 server 的 utilization 来挪动 workload。(相似于在 bins 之间挪动 workload 以确保没有 bin 过满)
  3. 监控整个 fleet utilization,并依据须要减少或者缩小 capacity。(相似于在所有 bins 都行将装满之前减少新的 bin,或者在他们都行将为空时缩小 bin)
  4. 只有零碎没有失去充沛的利用,就能够使工作负载超出硬调配的边界,并在零碎失去充分利用时将工作负载放弃在边界之内。(相似于容许 workloads 在每个 bin 中扩大,只有他们不会挤出其余 workloads 即可)

先进的算法联合了下面这些技术,比方一个 fairness system 能够 monitor 每个 workload,评估是否有任何两个 workloads 能够充分利用某些的资源,之后将它们挪动到一个 bin 中。只有一个 workload 没有充分利用其调配的资源,同一个 bin 中的另一个 workload 就能够借用这些资源。
在借用资源的时候,工作负载无需留神借用。如果工作负载须要应用所有调配的资源,则返回这些借用资源的工夫简直是刹时的。
另外,workload 须要能够在 bins 之间疾速挪动。如果一个忙碌的 workload 习惯于通过从其街坊那里借用超出其已调配的资源,然而其街坊扭转了其行为并开始应用其更多的已调配资源,则须要将繁忙的 workload 移至另一个 bin。

通过 Load shedding 减少偏心

通常来说,随着零碎负载的减少,零碎应该主动进行扩大。最简略的办法是减少 capacity 并进行程度扩大。而对于像是基于 AWS Lambda 构建的无服务器架构的服务,因为可按需分配容量来解决工作,因而简直能够即时进行程度扩大。对于有服务器服务,主动扩大就须要更长时间了。

通常对扩大的工夫要求在几分钟之内即可,然而,如果服务器上的负载减少快于 Auto Scaling,则咱们须要采纳疾速故障策略,减去多余负载,这样能够为解决到的申请保持一致的性能。这样做的益处在于,零碎的所有咱们都是 可预测 的。

通常咱们会将服务设计为尽快将不能解决的申请返回给客户端,以最大水平的缩小服务器执行的工作量。然而 这样会造成单机黑洞,因为相应快,而导致负载均衡器将更多的申请发送过去,因而咱们须要成心加快疾速谬误的响应速度,以匹配胜利响应的提早。

通常来自多个客户端的负载是不相干的,因而如果服务的总负载忽然减少,很有可能是由单个客户端所引起的。出于均衡思考,咱们须要防止因为单个客户端的计划外负载而导致所有客户端中都呈现申请失败的景象。对于这种状况,咱们应用速率限度来限度计划外的流量增长。能够为每个 client 设定某个额度的资源和操作的最大值。这样,如果多客户端服务遇到了计划外的负载减少,则该工作负载的计划外局部将被回绝,其余工作负载将持续以可预测的性能运行。

然而配额的应用会缩小服务的可用性,当一个客户端的工作量超过其配额时,它将开始看到其多余的申请失败。然而实际上,该服务可能具备满足这些申请的能力。

通常会以 429 状态码返回“超出 API 速率限度”响应。

状态码在 500-599 范畴内意味着服务器因为某种原因而失败
状态码在 400-499 范畴内意味着客户端正在做意外的事件,在本文所述的状况下,是计划外的 API 调用量

Note: 在理论利用时,会发现某些服务实际上为超出速率返回 503 状态码,这是因为 2012 年 RFC 6585 才正式将 429 状态码退出到 HTTP 标准中。因为为了放弃 backward compatibility,很多服务其实会对“超出 API 速率限度”返回 503。

深刻配额

服务所有者通常会为每个 client 配置配额。例如,对于 AWS 服务,Client 通常是一个 AWS account。有时,配额会放在比 client 更细粒度的地位上,例如放在 Service 领有的特定资源上,比方 DynamoDB 表。服务的所有者须要定义规定,为每个调用者提供默认的 quota。或者如果 client 预期行将呈现的负载减少,则他们须要要求服务减少其配额。

quota 的品种:

  1. the number of things the client can have running at the same time. 例如 Amazon EC2 为特定 AWS account 能够启动的实例数量施行配额。
  2. rate-based quota. 基于比率的配额通常以“每秒申请数”这样的单位进行掂量。本文次要着重于基于比率的配额,但文中的探讨在另一种配额中也大体适当。

下图演示了配额的应用。它显示了具备无限容量的服务(通过百分比显示)。该服务具备三个客户端。该服务已为每个客户端调配了其总容量的 1 /3. 该图显示客户端 Blue 试图超过其预调配的吞吐量,然而未能胜利,对其余客户端对服务的调用也未造成影响。

为了使可预测性更高,以及不便客户端更加理解调用,服务端能够为客户端提供可查看和应用的指标,以在其使用率靠近最大配额时收回警报。例如,DynamoDB 公布 Amazon CloudWatch 指标,该 metric 显示为表配置的吞吐量,以及该吞吐量和工夫的关系。

某些 API 的服务老本远高于其余 API。因而,服务可能会为每个客户端调配较少的低廉 API 配额。同样,服务端并非总是事后晓得操作老本。例如,返回单个 1KB 的行查问比返回 1MB 的行查问便宜。分页能够避免这种开销过于失控,然而最小页面大小和最大页面大小之间依然可能存在老本差别,这使设置正确的阈值非常具备挑战性。为了解决此类问题,某些服务将较大的申请视为多个申请。此技术的实现形式是,将每个申请视为最便宜的申请,而后在 API 调用实现后,依据实在的申请老本返回,并记录为客户端的配额。

施行配额须要有肯定的灵活性。例如:Client A 的配额为每秒 1000 个事务(1000TPS),然而该服务曾经扩大为能够解决 10000TPS,并且该服务以后其所有客户端总共状况为 5000TPS(并非总配额为 5000TPS!)。如果 Client A 从 500TPS 飙升至 3000TPS,则 2000TPS 将被回绝,然而服务理论足够解决这些申请。这时咱们能够让服务容许这些申请。如果之后其余 client 也同时应用更多配额,则该服务能够开始“删除”client A 的超出配额的申请。对于这种“计划外配额”,应该及时向客户端发出信号,让 client 晓得它曾经超出配额,并且在不可预感的将来会有产生谬误的危险。同时,该服务应该晓得它可能须要扩大其 fleet,并且能够肯定水平上主动减少 client A 的配额。

下图演示了这种状况。图中创立了一个相似于上图用于显示向其 client 硬调配配额的服务的图表。然而,在下图中,服务为其 client 的配额减少了灵活性,而不是对其进行硬调配。stack 容许客户端应用为利用的服务容量。因为橙色和灰色为应用满其配额,因而容许蓝色超出其预配置的阈值并利用未应用的容量。如果橙色或灰色决定应用其容量,则其流量必须优先于蓝色的突发流量。

在 Amazon,有通过思考客户的理论用例来钻研灵活性和突发性。例如,EC2 instance(及其负载的 EBS 卷)在启动实例时通常比当前更忙。这是因为启动实例时,须要下载并启动其操作系统和应用程序的代码。当咱们思考到这种流量模式时,咱们发现咱们能够更慷慨的应用后期突发配额。这样能够缩小启动工夫,并且依然提供了咱们的长期容量布局工具,以确保工作负载之间的偏心。

还能够思考配额是否能够随工夫变动。例如,某些服务会随着客户的增长主动减少其配额。然而,在某些状况下,客户须要并依赖固定配额,例如,用于管制老本的配额。

配额有时候并不一定是爱护机制,而是服务的性能。

对准入控制系统的设计

决定流量大小,缩小负载,施行基于速率的配额的零碎称为 admission control systems。

亚马逊的服务采纳多层准入管制设计,能够防止出现大量的须要回绝的申请。咱们常常在服务之前应用 API Gateway,并让其解决配额和速率限度的某些方面。API Gateway 能够解决宏大的 fleet 流量。这意味着咱们的 fleet 不必累赘任何额定流量,能够可预测的服务于理论流量。咱们还能够配置 Application Load Balancer 或 CloudFront。以及应用 Web 应用程序防火墙服务 AWS WAF 进一步加重 admission control 的累赘。为了提供进一步的爱护,AWS Shield 提供了 DDos 爱护服务。

在本大节中,将探讨一些技术,包含如何构建服务器端准入管制,如何依据其调用的服务的压力测试后果来做一个优雅响应的客户端,以及如何思考这些零碎的准确性。

Local admission control

一种实现准入管制的罕用办法是应用令牌桶 (token bucket) 算法。令牌桶保留令牌,并且每当申请被承受时,都会从令牌桶中取出令牌。如果没有可用令牌,则申请被回绝。令牌以配置的速率增加到令牌桶中,直到达到最大容量。该最大容量称为突发容量,因为这些令牌能够立刻被耗费,从而反对流量突发。
令牌的这种刹时突发耗费是一把双刃剑,它容许流量呈现某些天然的不平均性,然而如果突发容量太大,则会有大量申请被回绝。
能够应用组合的令牌桶来避免无限度的突发流量。让一个令牌桶具备绝对较低的速率和较高的突发容量,让另一个令牌桶具备较高的速率和较低的突发容量。通过查看第一个令牌桶,而后查看第二个令牌桶,能够实现高并发,但并发量无限。
对于传统服务(不具备无服务器架构的服务),还能够思考针对给定客户的申请在服务器上的统一性或不统一性。如果申请不统一,能够应用更宽松的突发值或分布式准入控制技术。
Google Guava 的 RateLimiter 就是一种现成的本地速率限度的实现。

Distributed admission control

Local admission control 对于爱护本地资源很有用,然而配额的设置或者说偏心通常须要在程度扩大的 fleet 中执行。amazon 的团队采纳了许多不同的办法来解决 distributed admission control 的问题,包含:
将配额除以服务器数量,调配到每个服务器上。应用这种办法,服务器依据它们在本地察看到的流量速率执行 admission control。这种办法有一个假如:申请在服务器之间的散布绝对平均。当 LB 以 round-robin(轮询)形式在服务器之间申请时,这种办法是可行的。如下图,假如流量在服务器之间绝对平均,且能够应用单个 LB 进行解决:

然而,在某些 fleet 的配置中,LB 并不是以 round-robin 形式向服务器发送申请,而是向具备起码连接数的服务器发送申请。即 LB 并不是用于申请均衡模式,而是连贯均衡模式。当每个服务器的配额足够高时,在实践中兴许不会产生问题。然而当一个十分大的 fleet 具备多个 LB 时,对于申请在服务器之间的散布绝对平均的假如可能会生效。在这种状况下,client 只会将申请发送给局部服务器。
下图阐明了上述情况,其中尽管有多个 LB,然而因为 DNS 的 caching,导致 client 的流量并没有平均的发送给多个 LB。尽管当 client 经常关上和敞开 connection 时,根本不会呈现问题。

通常咱们能够应用一致性哈希来进行 distributed admission control。某些服务的所有者运行独自的 fleet,例如 Amazon ElastiCache for Redis fleet。它们将 throttle keys 上的一致性哈希利用于特定的速率跟踪器服务器,而后让速率跟踪器服务器依据本地信息执行 admission control。该解决方案甚至在服务器数量很多的状况下也能够很好的扩大,因为每个 rate tracker server 只须要晓得 fleet 的一个子集。然而,当以足够高的速率申请特定的 throttle key 时,根本实现会在缓存队列中创立“hot spot”,因而须要向服务增加一些 intelligence,以逐步在特定 key 的 throughput 减少时,依赖本地准入管制。
下图阐明了对数据存储应用一致性哈希的状况,即便在流量不平均的状况下,应用一致性哈希来计算某种数据存储(例如缓存)中的流量也能够解决 distributed admission control 问题。然而这种架构引入了扩大挑战。

下图阐明了一种新办法。应用服务器之间的异步信息共享来解决非平均流量的问题。

Reactive admission control

配额对于解决惯例的意外流量顶峰非常重要,但服务应该筹备好应答各种意外的工作负载。例如,有问题的客户端可能会发送格局谬误的申请,或者客户端可能会发送比预期更低廉的工作负载,又或者客户端可能有一些意料之外的利用程序逻辑,导致服务端流量暴增。解决这些问题的灵活性十分重要,因而咱们能够建设一个准入控制系统,该零碎能够对申请的各个方面作出反应,例如 user-agent, URI, source IP address 等。

Admission control of high cardinality dimensions

简略的准入控制系统只须要跟踪以后察看到的申请量和配额,例如,一个服务被十个不同的应用程序调用,则只须要跟踪十个不同的申请量和配额值即可。
然而,在解决更细粒度维度的准入管制时,零碎将变得更加简单。比方,服务可能会为世界上每个 IPv6 地址、DynamoDB 表中的每一行或是 S3 存储桶中的每个对象设置基于速率的配额。
当运行这样一个具备高基数为度的零碎时,咱们须要对流量随工夫的变动进行操作可视化。

Reacting to rate-exceeded responses

当服务的客户端收到速率超过配额的谬误时,它能够重试或返回谬误。Amazon 的零碎能够采纳两种形式之一来响应速率超出谬误,取决于零碎是同步零碎还是异步零碎。

同步系统对实时性要求十分高,重试申请会有肯定机会在下一次尝试中胜利。然而,如果客户端依赖的服务频繁返回速率超过限度的响应,重试只会减慢每个响应的速度,并且会在曾经负载过重的零碎上占用更多资源。因而当服务频繁返回谬误时,咱们须要有工具主动进行重试。
对异步零碎解决更加容易一些。在 client 收到速率超过配额的响应时,client 能够加快处理速度,直到申请胜利。例如,一些异步零碎定期会定期运行,而且对他们的冀望便是工作须要很长时间能力实现。对于这些零碎,它们能够尝试尽可能快的执行,并在某些依赖项称为瓶颈时加快处理速度。

Evaluating admission control accuracy

无论咱们应用哪种准入控制算法来爱护服务,咱们都须要评估该算法的准确性。
能够采纳的一种办法是在每个申请的内容中蕴含节流键和速率限度。并执行日志剖析以测量每个 client 每秒的理论队列申请。而后咱们将其与配置的配额进行比拟。由此,对于每个 client,咱们剖析了“true positive rate”(被正确回绝的申请率),“true negative rate”(被正确容许的申请率),“false positive rate”(被谬误决绝的申请率),“false negative rate”(被谬误承受的申请率)。
在 amazon 咱们能够应用 cloudwatch 或 Athena 来进行剖析。

配额之外

向服务增加准入管制以进步其服务器端可用性,爱护 client 端免受彼此侵害,仿佛曾经十分完满。然而,配额的应用也会对 client 带来不便。当 client 试图实现某件事时,配额会减慢它们的速度。当咱们在服务中建设偏心机制的同时,咱们也须要寻找帮忙 client 快读实现工作的办法,而不是让他们的吞吐量收到配额的限度。
咱们帮忙 client 防止超出配额的办法能够因 API 是管制立体 API 还是数据立体 API 分为两类。前者的代表性例子有 S3 CreateBucket,DynamoDB DescribeTable 和 EC2 DescribeInstances 等,后者的代表性例子有 S3 GetObject,DynamoDB GetItem 和 SQS ReceiveMessage 等。

防止超出配额的容量治理办法

数据立体工作负载具备弹性,因而咱们能够将数据立体的服务设计为具备弹性的服务。为了使服务具备弹性,咱们能够设计底层基础架构来主动扩大以适应客户工作负载的变动。咱们须要帮忙客户在治理配额时放弃这种弹性。Amazon 的 service team 应用各种技术来帮忙其客户治理配额并满足客户对于弹性的需要:

  1. 如果 fleet 装备了一些未充分利用的‘slack’capacity,咱们会揭示咱们的 client。
  2. Amazon 施行了 Auto Scaling 并随着每个 client 在失常业务过程中的增长而减少其配额。
  3. 咱们让 client 很容易看到它们间隔配额的间隔,并让它们配置 alarm,以在达到极限的时候立刻揭示
  4. 咱们会留神 client 何时靠近并达到配额的限度。即,当服务以较高的整体速率流量或同时有太多 client 达到配额限度时,咱们会收回警报。

防止超出配额的 API 设计办法

对于管制立体,上述探讨的一些技术可能并不实用。因为管制立体被设计为绝对不频繁的被调用,而数据立体被设计为会被大量调用。然而,当管制立体的 client 最终创立了许多资源时,他们依然须要可能对这些资源进行治理、审计和执行其余操作。客户在大规模治理许多资源时可能会用完他们的配额并遇到 API 速率限度,因而咱们须要寻找代替办法来通过不同类型的 API 操作满足他们的需要。以下是 AWS 在设计 API 时采纳的一些办法,能够帮忙客户防止可能导致用完基于 rate 的配额的调用模式:

  1. Supporting a change stream. 例如,咱们发现一些客户订起轮询 EC2 DescribeInstances API 操作,以列出他们的所有 EC2 实例。通过这种形式,他们能够找到最近启动或终止的实例。然而随着客户的 EC2 instances 的增长,这种 API 调用会变得越来越低廉,导致超出基于 rate 的配额的可能性减少。对于某些 user cases,咱们可能通过 AWS CloudTrail 提供雷同的信息,来防止 API 被真正调用。CloudTrail 公开操作的更改日志,因而客户能够对流中的更改做出反馈,而不是定期轮询 EC2 API。
  2. Exporting data to another place that supports higher call volumes. S3 Inventory API 就是一个这样的例子。客户如果在 S3 中有大量对象,而其须要从中筛选以找到特定对象时,他们会应用 ListObjects API。为了帮忙客户实现高吞吐量,Amazon S3 team 依据这种状况,提供了一个 Inventory API 操作,该操作将存储桶中的对象列表异步导出到一个称为 Inventory Manifest file 的 S3 对象中,该文件蕴含存储桶中所有对象的 JSON 序列化列表。这样客户就能够以数据立体的吞吐量拜访其存储桶的 Manifest 了。
  3. Adding a bulk API to support high volumes of writes. AWS 的客户会有调用一些 API 写入操作来创立或更新管制立体中的大量内容。一些客户违心容忍 API 施加的速率限度。然而,他们不想编写程序,也不想解决局部失败和速率限度产生的异样,以防止其余写入用例也失败。AWS IoT team 通过 API 设计解决了这个问题。它们增加了 asynchronous Bulk Provisioning APIs。应用这些 API 操作,客户上传一个蕴含他们想要进行更改的所有文件,当服务实现这些更改时,它们会向调用者提供一个蕴含后果的文件。这些后果将会显示哪些操作胜利了,哪些操作失败了。这使得客户能够不便的解决大批量操作,但他们不须要解决重试、局部失败等这样的细节。
  4. Projecting control plane data into places where it needs to be commonly referenced. EC2 DescribeInstances 管制立体 API 操作从每个实例的网络接口返回无关实例的所有所有元数据到块设施映射。然而,其中一些元数据与在实例自身上运行的代码十分相干。当有很多实例时调用该办法时,每个实例调用 DescribeInstances 的流量都会很大。如果调用失败,这对于在实例上运行的客户应用程序来说将是一个很大的问题。为了完全避免这些调用,Amazon EC2 在每个实例上公开一个本地服务,提供了实例元数据。通过将管制立体数据投影到实例自身,客户的应用程序将会通过防止同时近程调用,从而不会有 API 调用超出配额的状况。

准入管制是一个 feature

在很多状况下,客户会发现准入管制比无约束的弹性更可取,因为它有助于他们管制老本。通常,服务不会对被回绝的申请想客户免费,因为他们往往很少产生并且解决起来绝对便宜。例如,AWS Lambda 的客户要求可能通过限度潜在的并发调用次数来管制老本。当客户想要这种管制时,重要的咱们须要提供他们这种能够通过 API 调用轻松自行调节的能力。它们还须要足够的 visibility 和 alarming capabilities。通过这种形式,他们能够看到零碎中的问题,并在他们认为有必要时进步配额。

结语

被多个客户端调用的服务具备资源共享的属性,使他们可能以更低的基础架构老本和更高的运行效率运行。因而咱们在多客户端的服务中建设公平性,为咱们客户提供可预测的性能和可用性非常重要。
服务配额时实现公平性的重要工具。基于速率的配额通过避免一种工作负载的不可预测的减少影响其余工作负载,使多客户端的服务更加牢靠。然而,施行基于速率的配额并不总是足以给客户提供优质的体验。Customer visibility,controls, burst sharing, and different flavors of APIs 都能够帮忙客户防止超出配额。

分布式系统中的 admission control 的实现时简单的。在 AWS 中,API Gateway 提供了多种节流性能。AWS WAF 提供了另一层服务爱护,它能够集成到应用程序负载均衡器和 API Gateway 中。DynamoDB 在单个索引级别提供与配置的吞吐量管制,让客户可能隔离不同工作负载的吞吐量需要。同样,AWS Lambda 公开了每个函数的并发隔离以将工作负载彼此隔离。

应用配额的准入管制是构建具备可预测性能的高弹性服务的重要办法。然而只有准入管制是不够的。咱们也要确保在准入管制之外解决问题,例如应用 Auto Scaling,这样如果出现意外的 load shedding,咱们的零碎就会通过 Auto Scaling 主动响应减少机器的需要,来解决 load shedding 的问题。

从外表上看,在将服务作为单 client 服务与公开作为多 client 服务之间的老本和工作负载隔离之间仿佛存在固有的衡量。然而,咱们发现通过在多 clients 零碎中实现公平性,从而使 customer 鱼和熊掌兼得。

退出移动版