作者 | Amir Souchami
译者 | 王强
策划 | Tina
要点
跨多个可用区的分布式系统可能会产生大量的数据传输成本和性能瓶颈。
组织可以通过区域感知路由技术来降低成本和延迟,同时不会牺牲可靠性和高可用性。
区域感知路由是一种策略,通过尽可能将流量引导到同一可用区内的服务来优化网络成本和延迟。
端到端实现区域感知路由需要多种工具,例如 Istio,还要选择支持此功能的。
要留心当你的服务分布不均匀时出现的问题。请处理集群热点并具备在特定区域范围内扩展服务的能力。
微服务架构方法已成为构建成功产品的核心因素。这种方法是通过服务网格、容器和无服务器计算等先进云技术的应用来实现的。快速增长、可维护性、弹性和高可用性的需求,使构建“深度”分布式系统(具有许多微服务层,跨多个可用区甚至区域的系统)成为了团队的标准操作。这些系统常见的特征是有数十个必须通过网络频繁相互通信的,聒噪 的微服务。
将我们的资源分布在不同的物理位置(区域和 AZ)对于实现弹性和高可用性是非常重要的。但是,在某些部署中可能会产生大量的数据传输成本和性能瓶颈。本文旨在提高对这些潜在问题的认识,并提供在克服这些挑战的同时保持弹性的指南。
问 题
使用多个 AZ 是一种最佳实践,也是保持服务可用性的重要因素。无论是我们的微服务、负载均衡器、数据库还是消息队列,我们都必须跨多个区(AZ)配置它们,以确保我们的应用程序在地理上分布开来,并能承受各种中断事件。
在一些关键系统中,为了实现容错,将资源和数据分布在多个区域(region)甚至多个云提供商之间也很常见。
将我们的服务分布在多个可用区,就需要我们在这些可用区之间传输数据。每当两个微服务相互通信,或每当一个服务从分布式数据库读取数据时,通信就可能跨越可用区边界。这是由于负载均衡的性质使然。
通常,负载均衡器会在上游服务(被调用的服务)的实例之间均匀分配流量,而无需了解原始服务所在的可用区是哪一个。因此实际上,每当在多个可用区上配置系统时,跨可用区流量就可能大量出现。
但为什么它会成为云成本负担甚至性能瓶颈呢?让我们来分析一下。
图 1:跨多个可用区的请求流图示
成本负担
每次在两个可用区之间传输数据时,大型云提供商通常会对传入和传出的跨可用区数据传输操作收取双向数据传输费用。例如,如果每个方向的费用为 0.01 美元 /GB,则两个位于相同区域的可用区之间传输 1TB 数据将花费 10 美元(传出)和 10 美元(传入),总计 20 美元。但是,这些成本在分布式系统上很快就会失控。想象一下,作为请求的一部分,AZ-1 上的负载均衡器实例可能会联系 AZ-2 中的服务 A,后者又调用 AZ-3 上的服务 B,B 又从 AZ-2 上的数据库读取键 / 值记录,并且经常消费来自 AZ-1 上的一个 Kafka 代理的更新(见图 1)。
因此,虽然这种架构选择优先考虑可用性,但它容易出现极端情况,即单个请求的所有服务之间的通信都是跨可用区进行的。所以现实情况是,在具有各种数据库、消息队列和许多相互通信的微服务的系统中,数据必须频繁移动,并且成本可能会变得非常高昂。每次向堆栈添加服务时,负担都会增加。
性能瓶颈
每个可用区与同一数据中心或区域内的其他可用区之间,在物理上会相隔一段不短的距离,虽说它们之间的距离最多都在几英里之内。这通常会在可用区之间产生至少个位数毫秒的往返延迟,有时甚至更长。所以,回到我们的示例,对 AZ-1 上的负载均衡器实例的请求可能会到达 AZ-2 中的服务 A,后者又调用 AZ-3 上的服务 B,B 又从 AZ-2 上的数据库中读取键 / 值记录,并且经常消费来自 AZ-1 上的一个 Kafka 代理的更新。这样,我们很容易为每个请求增加十几毫秒的时间。当服务位于同一可用区时,这段宝贵的时间可能会降至不到一毫秒。与成本负担一样,你向堆栈中添加的服务越多,这种负担就越大。
那么,我们如何才能在不牺牲性能的情况下获得跨可用区的可靠性?
有什么办法可以解决这些额外的数据传输成本吗?
让我们深入探讨这些问题。
解决方法:区域感知路由
区域感知路由(又称拓扑感知路由)是解决这些问题的方法。在 Unity 的 Aura 中,我们在过去一年逐步推出了此功能。我们发现它在某些系统中节省了 60% 的跨可用区流量。我们了解了如何利用它来优化性能并显著降低带宽成本。我们还发现了它不适合用于哪些场景,哪里应该避免使用它。那么就让我们来介绍区域感知路由,以及想要充分利用它需要考虑哪些因素。
什么是区域感知路由?
区域感知路由是一种策略,旨在通过尽可能将流量引导到同一可用区内的服务来优化网络成本和延迟。它最大限度地减少了跨区数据传输的需求,从而降低了相关成本和延迟。
区域感知路由的宗旨是通过本地区将来自原始服务的所有或大部分流量发送到上游服务。理想情况下,通过本地区进行路由还是执行跨区路由,应取决于源服务本地区中上游服务的健康实例百分比。此外,如果当前可用区中的上游服务发生故障或中断,区域感知路由组件应能够自动跨可用区对请求重新路由,以保持高可用性和容错能力。
区域感知路由器的核心应充当一个智能负载均衡器。在尝试保持对区域局部性的感知的同时,它还负责在服务的所有上游实例之间分配每秒相同数量的请求。其目的是避免特定实例受到流量轰炸的情况。这种流量偏差可能导致局部过载或利用不足,从而导致效率低下并可能产生额外成本。
由于这种性质,区域感知路由在区域流量和资源分布均匀的环境中非常有用(例如,当你将流量 50/50 分配到具有相同资源量的两个可用区时)。但是,对于不均匀的区域分布,由于需要平衡多个区的流量并克服热点(轰炸和过载实例 - 见图 2),它可能变得不那么有效。
图 2:不均匀区域分布的图示。两个服务位于两个区。当服务 B 为来自 AZ-2 的所有请求提供服务时,它能否坚持下去?
我如何采用区域感知路由?
区域感知路由的概念有多种实现方式。因此,采用它时要找到适合你的设置和技术栈的实现方式。我们来看一些选项:
Istio 本地负载均衡
Istio 是一个开源的 Kubernetes 服务网格平台。Istio 可能拥有目前最有前途的区域感知路由实现:本地负载均衡。它是 Istio 中的一项特性,允许根据服务和上游服务实例的本地性,将请求有效地路由到服务最近的可用实例。在 Kubernetes 集群中安装和配置 Istio 时,可以根据地理区域、可用区域和子区域因素来定义位置。
Istio 的本地负载均衡使请求可以优先路由到与原始服务位于同一位置(即区域)的上游服务实例。如果与原始服务位于同一位置的服务没有可用的健康实例,Istio 可以进行故障转移并将请求重新路由到最近可用位置(例如另一个区甚至另一个区域)的实例。这确保了高可用性和容错能力。
此外,Istio 允许你为不同的位置定义权重,使你能够根据容量或优先级等因素控制跨可用区的流量分布。这样,你就可以调整每个区域中保持本地化的流量规模与跨区域发送的流量规模,从而更轻松地平衡不均匀的区域分布。
拓扑感知路由(又称拓扑感知提示)
如果你在没有 Istio 的情况下使用 Kubernetes,可以使用 Kubernetes 的一项原生特性,拓扑感知路由,在版本 1.17 中引入(名为拓扑感知提示)。虽然它比 Istio 提供的功能稍微简单一些,但它允许 Kubernetes 调度程序根据集群的拓扑做出智能路由决策。它考虑拓扑信息,例如节点的地理位置(例如区域)或可用区,以优化 pod 的放置和流量的路由。
Kubernetes 还围绕此特性提供了一些必要的保护措施。Kubernetes 控制平面将在使用该特性之前应用这些保护规则。如果这些规则不成立,Kubernetes 将不会本地化请求。相反,TokenPocket官网它将选择一个实例,不管它在哪个区或区域,以保持高可用性并避免特定区域或实例过载。规则可以评估每个区是否都有可用的服务实例,或者是否确实有可能在区之间实现平衡分配,从而防止在集群上产生不均匀的流量分布。
虽然拓扑感知路由可以优化跨区流量,并处理使用多个可用区的隐藏性能负担和成本,但它的能力略逊于 Istio 的局部负载均衡。主要缺点是它不能像 Istio 那样处理故障转移。相反,它的保护措施方法会完全关闭服务的区域局部性,这是一种更严格的设计选择,要求服务均匀分布在各个区才能从这些规则中受益。
端到端区域感知路由
使用 Istio 的本地负载均衡或 Kubernetes 拓扑感知路由来维护区域感知路由是解决分布式系统数据传输成本和性能瓶颈的重要一步。但是,要实现端到端区域感知路由,并将跨可用区的数据传输降至最低,我们还必须确定我们的服务是否能通过本地区从 DB 和消息队列(MQ)读取其数据(见图 3)。
图 3:端到端单个可用区本地化的请求流图示
DB 和 MQ 通常分布在多个可用区中以实现高可用性,在每个可用区中维护每条数据的副本。这就是为什么 DB 和 MQ 容易被跨区访问,从而给系统带来性能和成本负担。那么,我们能否通过本地区访问数据来优化数据库读取延迟并降低数据传输成本,同时又不影响弹性?
以下是一些可以支持区域感知路由的数据库和 MQ 示例:
Kafka 的 Follower Fetching 特性
Apache Kafka 是一个开源分布式事件流平台,用于高性能数据管道、流分析和数据集成。与其他 MQ 一样,它通常用于连接多个微服务并在分布式系统之间移动数据。
Follower Fetching 是 Kafka 客户端库的一项特性,允许消费者优先从本地可用区读取数据,可以从领导者节点或副本节点读取。此特性基于 Kafka 的 Rack Awareness 特性,后者旨在通过利用数据中心的物理或逻辑拓扑来增强数据可靠性和可用性。
Kafka 的 Rack Awareness 要求为集群中的每个代理分配其对应的机架信息(例如其可用区 ID),以确保主题分区的副本分布在不同的可用区中,从而尽可能降低在可用区或节点发生故障时数据丢失或服务中断的风险。虽然机架感知不能直接控制客户端读取的位置,但它确实允许 Kafka 代理通过机架标签提供其位置信息,以便客户端库可以使用它来尝试从位于同一机架或区中的副本读取数据,或者在本地代理不可用时回退到另一个区。因此,它遵循区域感知路由的概念,可优化数据传输成本和延迟。要利用 Follower Fetching,我们必须为消费者分配机架标签,指示它当前从哪个可用区运行,以便客户端库可以使用它来定位同一可用区上的代理。
请注意,当使用 Kafka 的 Follower Fetching 特性并且本地代理的复制落后于领导者时,它将增加消费者在该本地区内的等待时间,而不是影响跨区的随机消费者,从而将某些问题的爆炸半径限制在本地区。此外,与其他区域感知实现一样,它对消费者的区域分布不均情况非常敏感,这可能会导致某些代理过载并要求消费者处理故障转移。
Redis 和其他区域感知数据库
Redis 是分布式系统中常用的键值数据库。它是用于高吞吐量、大规模应用程序的缓存和其他亚毫秒级查询的数据库之一。出于冗余原因,数据库通常会分布在多个可用区中。因此,任何从 Redis 读取的,分布在多个地理位置的应用程序都会因为本地区读取获得出色的性能和成本效益。
Redis 没有内置根据客户端的可用区自动将读取请求路由到本地副本的能力。但是,我们可以通过某些 Redis 客户端库获得此功能。例如,在使用 Lettuce(一个 Java 库)时,将客户端的“ReadFrom”设置为 LOWEST_LATENCY,将让客户端从数据延迟最低的副本读取数据,无论它是在副本还是主节点上。此副本通常驻留在本地区中,因此减少了跨区数据传输。
如果你使用的客户端库不支持选择最近的副本,则可以实现自定义逻辑,通过从本地 Redis 端点检索数据来实现相同的目的。最好在需要时回退到跨可用区端点。
还有一些更流行的数据库技术支持区域感知路由;以下两种值得一提:
Aerospike:一种专为高性能数据操作而设计的分布式键值数据库。它具有机架感知功能,与 Kafka 一样,它为数据库客户端提供了一种机制,使其优先从最近的机架或区读取数据。
Vitess:MySQL 的分布式可扩展版本,最初由 Youtube 开发,通过其本地拓扑服务启用区域感知路由,使其能够通过本地区域路由查询。
虽然我们可能会注意到区域感知路由的概念在每项技术中都有略微不同的名称,但所有这些实现都有相同的目标——在不牺牲服务可靠性的情况下改善读取延迟和数据传输成本。
将我们的 DB 和 MQ 分布在多个可用区的另一个成本负担是,我们写入的每条数据都需要在 DB 集群本身内复制到每个区域。复制本身可能会导致大量的数据传输成本。虽然从技术上来说这是没办法的,但必须注意的是,常见的托管 DB 服务(例如 AWS MSK(亚马逊的 Apache Kafka 托管流)、AWS RDS(亚马逊关系数据库服务)、Elasticache(亚马逊的托管 Redis)等)不会向用户收取集群内跨可用区数据传输的费用,而只收取进出集群的数据的费用。因此,如果你计划复制大量数据,选择托管服务可能会成为一个重要的成本考虑因素。
处理热点和不均匀的服务分布
虽然我们强烈建议大家保持服务在多个区上的均匀分布,但这并不总是那么简单或可行。处理热点是一个复杂的话题,但解决它的一种方法是考虑为每个区单独部署和单独设置自动扩展策略。允许服务根据特定区内的流量独立扩展,可以确保如果负载集中在特定区,它将得到充分扩展以使用专用资源处理该负载,从而无需回退到其他区。
总 结
在高可用性分布式系统中应对数据传输成本和性能是一项真正的挑战。为了解决这个问题,我们首先需要找出哪些微服务容易发生频繁的跨可用区数据传输。我们需要以数据为导向,衡量跨可用区成本,并跟踪微服务通信的延迟,以确保我们的努力方向正确。
然后,我们必须采用适当的工具来消除或最小化这些跨可用区调用。采用哪些工具和优化取决于我们的技术栈。要端到端采用区域感知路由,我们必须针对不同的用例采用不同的工具。某些工具(如 Istio 和拓扑感知路由规则)旨在为相互通信的 Kubernetes pod 解锁区域感知能力。但是,它们不适用于你的微服务从数据库或消息队列跨可用区使用数据的情况。因此,我们必须选择正确的 DB 和 MQ,并在其客户端库中嵌入区域感知路由。
最后,我们必须确保自己不会在不均匀的区域分布设置中部署这些工具和优化,结果破坏系统的高可用性和弹性,这会导致热点(被流量轰炸的实例)。这样的部署可能会违背初衷,意味着我们为了解决成本和性能问题却引入了新的问题。
总体而言,优化跨可用区流量至关重要;它会影响我们应用程序的性能、延迟和成本,但必须谨慎处理,以确保我们不会牺牲应用程序的可靠性和高可用性。
作者介绍
Amir Souchami 是 Unity 的 Aura 首席架构师。他对技术充满热情,不断学习最新知识以保持敏锐,创造、扩展和推动积极的业务投资回报率。关注他来讨论 Gen AI、机器学习、流处理、微服务和 AdTech。
How to Minimize Latency and Cost in Distributed Systems (https://www.infoq.com/articles/minimize-latency-cost-distributed-systems/)
声明:本文由 InfoQ 翻译,未经许可禁止转载。