常见的使用场景
应用程序可扩展性
无服务器和事件驱动型应用程序中的连接处理
无服务器和事件驱动型应用程序(例如由 Amazon Lambda 支持的 API 和 Web 服务)通常必须支持大量短期客户端请求。这种使用模式可能导致数据库端的连接流失,同时在应用程序端无法实现连接池。单单由于并发连接的数量较多,数据库性能就可能会下降,或者数据库可能超过其连接限制,从而导致面向客户端的错误。
在这些场景中,RDS 代理可以带来以下优势:
-
它将建立连接的成本从数据库转移到代理,并提供连接池和多路复用功能,从而将大量客户端连接转换为很少量的后端数据库连接。这有助于减少连接开销和数据库争用,特别是在像 PostgreSQL 这样的数据库引擎中,建立和维护连接的成本相对较高。
-
它可以更平稳地应对连接激增。例如,当数据库超过连接限制时,它会立即向客户端返回错误。如果 RDS 代理需要从连接池中借用连接,但连接池已达到容量限制,代理可以等待连接可用。此功能可以通过将严重错误转变为查询延迟的轻微增加,来改善客户端体验。
-
通过可配置的连接池大小,您还可以将 RDS 代理用作节流或负载减少机制。如果连接数量超过您指定的限制,RDS 代理会等待连接在可配置的超时时段内可用。如果数据库为多个工作负载提供服务,而您希望限制某个特定工作负载对数据库可能造成的压力,这种作法会非常有用。
基于容器的分布式应用程序中的连接处理
基于容器的分布式应用程序架构可能有数百甚至数千个容器,每个容器都运行同样的一份应用程序代码。即使单个容器能够使用连接池,这些连接池也是针对特定容器的,因此非常小。容器数量乘以每个容器迷你池的大小可能会超过您的 Amazon RDS 或 Aurora 数据库的连接限制。
在这种情况下,RDS 代理执行连接池(重复使用连接)和多路复用(使用一个后端连接为多个客户端提供服务)的能力最有价值。您仍然可以在每个容器内使用连接池来减少应用程序线程与 RDS 代理之间的连接流失,但代理可以帮助将后端数据库连接的数量降低到可管理的水平。
提高只读副本的利用率
读密集型数据库可能需要多个只读副本来支持只读流量。应用程序可以使用自己的逻辑来选择连接哪个副本,或者更常见的是,它们使用基于 DNS 的轮询机制,例如 Aurora 集群读取器端点。不过,基于 DNS 的方法可能由于 DNS 缓存而导致副本利用不均衡。例如,客户端可能将自己“固定”到特定副本,可能无法识别正在添加到集群中的新副本,或者可能尝试连接到不再存在的副本。
当您使用 RDS 代理只读端点时,代理会使用“最少未完成连接”逻辑在所有可用副本之间路由客户端连接。RDS 代理不会根据 CPU 利用率等数据库指标对流量进行负载均衡,但它会尝试平衡每个副本上的客户端连接数量(根据数据库的连接限制进行加权)。例如,如果您有三个 Aurora 副本正在运行,max_connections 设置分别为 500、500 和 1000,代理尝试向第三个副本发送的连接数大约是发送到其他两个副本的两倍。
您可以将 RDS 代理读取器端点与 Aurora 集群结合使用,或者将 Amazon RDS Multi-AZ 数据库集群部署与两个可读备用副本结合使用。对于带有只读副本的 Amazon RDS 数据库实例部署,不支持代理读取器端点。
提高连接效率
在客户端应用程序与数据库之间引入代理时,您的目标通常是提高连接处理效率,同时考虑额外增加一个网络跃点后,通过代理时的延迟成本。增加一个中间层看起来对于提高连接效率似乎是违反直觉的,因为任何本来会直接打开数据库的连接现在都必须通过代理打开。协议握手步骤在这两种情况下都是相同的,所以如果您仍然在连接握手上消耗资源,效率提升的来源可能并不明显。
代理并不一定能使建立连接的成本更低。相反,它将大部分握手处理的负担从数据库层转移到了代理层。当您为数据库资源付费时,您希望将这些资源用于数据库工作,而不是用于辅助开销。当使用加密连接时,这变得尤为重要:尽管通过现有连接传输加密数据的开销不大,但建立加密连接的初始开销却很大。在每秒快速处理数百或数千个连接的环境中,额外的工作量很快就会累加。您可能不希望将 CPU 时间花在数据库资源上(这样相对昂贵),而是将这些工作转移到代理层(这样相对便宜)。
关于额外网络跃点带来的延迟,其重要程度取决于您的应用程序调用有多频繁,以及在每次与数据库的“对话”中运行多少语句。在 RDS 代理中,您通常会观察到增加的延迟在较低的个位数毫秒范围内,但它不一定会对您的应用程序产生明显影响。例如:
-
在查询执行时间为几十或几百毫秒(或更长)的工作负载中,不太可能注意到代理开销,因为代理开销只占总查询时间的一小部分。
-
对于运行个位数毫秒或亚毫秒级查询的应用程序来说,这种差异会非常明显,因为与查询执行时间相比,查询开销(每次查询多一个网络跃点)显得相当可观。如果客户端会话只涉及少量查询,那么累积的开销仍然很小,这可能就不是问题。
如果在您的情况中增加的延迟既明显又不可取,您必须将其与使用代理的其他好处(连接池、多路复用、失效转移处理)进行权衡。
高可用性
在 Amazon RDS 和 Aurora 上运行的多可用区数据库(不包括 Aurora DSQL)使用失效转移机制,以便在主数据库实例出现问题时恢复可用性。失效转移还用作操作工作流的一部分,例如主实例的计算扩展。失效转移涉及 DNS 更改,将主(可读写)数据库端点从之前的主实例移动到新提升的实例。此 DNS 更改必须被客户端应用程序观察和识别到,以便客户端能够无延迟地跟随主实例。
由于存在操作系统或应用程序级别的 DNS 缓存,一些应用程序可能会在及时识别 DNS 变化方面遇到困难。虽然在应用程序级别解决 DNS 缓存问题是一种最佳做法,但由于应用程序的复杂性或引入更改的成本,这并不总是可行的。
在这种情况下,RDS 代理是避免 DNS 相关失效转移延迟的有效方法。RDS 代理提供的读/写端点和可选的只读端点是“稳定的”,因为当数据库实例交换角色时,这些端点背后的 IP 地址不会变化。RDS 代理会持续跟踪后端数据库的拓扑结构,并将实例角色更改从客户端抽离出来。
处理 DNS 缓存问题有其他许多方法,例如使用能够直接检测数据库拓扑而无需依赖 DNS 的高级驱动程序。不过,在数据库前部署单个代理,可能比在应用程序层引入代码更改和新驱动程序更加容易。
读取可用性
除了在数据库失效转移期间改善客户端体验之外,RDS 代理还有助于在只读副本出现问题时提高应用程序的可用性。它通过两种方式实现这个目的:
-
如果一个只读副本不可用,但集群中还有其他副本可用,代理可以将新连接路由到可用的副本。客户端不需要担心 DNS 传播延迟或 DNS 缓存。
-
对于已采用多路复用的客户端连接,代理可以将该连接的后续查询发送到其他可用副本。在这种情况下,客户端可能甚至不会注意到后端副本出现了问题。采用此技术时,RDS 代理会确保新副本在复制延迟方面不会落后于之前的副本。这样,客户端发送的后续查询就不会看到可能被认为是过时的数据。
对一个目标使用多个代理
最佳做法是,每个数据库目标(例如 Amazon RDS 实例或 Aurora 集群)应使用一个 RDS 代理。这在大多数情况下都非常适合,它保持了配置的简单性,并使客户端体验更加可预测。相比之下,使用多个代理需要您仔细地在所有代理之间协调配置,以避免意外行为。例如,您必须确保所有代理池的总大小不超过单个数据库目标的连接限制。
在某些情况下,您仍然可以考虑使用多个代理。以下各个部分讨论了最常见的情况,并概述了单代理与多代理方法的优缺点。
代理可用性
RDS 代理基础设施设计为提供高可用性,部署在多个可用区(AZ)上。代理容量分布在多个节点上,进行持续的运行状况监控,并且会根据预调配实例大小或数据库的 Serverless v2 最大 ACU 设置自动调整。由于代理采用分布式多可用区设计,您不需要为了实现高可用性,在 Amazon RDS 或 Aurora 集群前运行多个代理。
事实上,在单个数据库目标前使用多个代理意味着应用程序堆栈现在需要负责检测和应对问题,而不能依赖代理内置的强大机制。因此,强烈建议不要出于高可用性原因使用多个代理,因为相比它能解决的问题来说,这可能会引入更多问题。
处理多个客户端池
每个代理提供一个读/写端点,还可以选择提供额外的读/写端点或只读端点。当单个代理用于处理多组客户端或多个工作负载时,这些工作负载可能会相互干扰。例如,一个工作负载可能会独占连接池,以至于没有足够的空闲连接来处理其他工作负载。可以使用单独的代理来隔离多个工作负载,这可能是一个有吸引力的解决方案,但您可以先考虑其他选项:
-
数据库本身可能提供比运行多个代理更为简单的替代方案。例如,如果问题是由某些用户独占连接池引起的,您可以使用数据库的权限系统来限制允许这些用户打开的并发连接数。
-
同样,数据库级别的查询超时和空闲超时可以解决遗留连接或失控查询的问题。
-
如果问题是由查询或事务的持续时间或每个查询的资源消耗引起的,而不是由并发连接的数量引起的,那么使用额外的代理无济于事,因为代理并不限制查询或事务的权重。如果问题无法在源头得到解决,您可以使用数据库的事件调度器来运行代码,根据任意条件检测并终止客户端活动,而不是将这些有问题的客户端移到单独的代理。
如果您决定在同一个目标前使用多个代理,请确保所有连接池的总大小不超过目标数据库的容量。例如,所有代理的 MaxConnectionsPercent 总和必须小于 100,否则代理尝试打开的连接数可能会超过数据库所配置的处理连接数。
每个代理都是单独计费的,计费费率取决于底层数据库资源的规模,而不是工作负载活动。考虑一下运行多个代理的成本与实施替代解决方案(如果有的话)的成本。
单独控制读取池和写入池
每个代理提供一个读/写端点,还可以选择提供额外的读/写端点或只读端点,但限制和超时是为整个代理目标配置的,而不是单独为每个代理端点配置的。
例如,您可能有一个 Aurora 集群,它使用多个只读副本和代理的读取器端点来处理大量只读查询,但您希望限制读/写连接的数量,以减轻单个写入器的资源压力。由于 MaxConnectionsPercent 设置是为整个代理目标(集群)定义的,因此您无法在不影响只读端点限制的情况下限制对读/写端点的连接数。
有多种方法可以处理这个挑战:
您可以在集群中启动额外的只读副本,并按比例减少代理的 MaxConnectionsPercent 设置。这种作法在减少写入器上允许的最大连接数的同时,还能保持只读连接池的总大小不变。不过,它也增加了集群成本,而且随着副本数量的增加,其效果逐渐降低。
您可以使用实例级参数组,在写入器之外,为只读副本单独配置数据库连接限制(例如 Aurora MySQL 或 PostgreSQL 中的 max_connections)。然而,您必须避免使用不对称的参数配置,因为在失效转移期间副本可能会被提升为写入器角色,所以最初的参数差异不会永久存在。您可能会考虑更改实例级别的失效转移优先级设置,以影响在失效转移期间选择哪些副本进行提升,并将其用作软失效转移排除机制,使非对称配置更加可预测。但是,失效转移优先级仅作为提示,并不能保证失效转移行为。因此不建议使用具有不对称设置的多实例配置,因为它们需要持续监控,并且如果失效转移的目标为您不偏好使用的实例,可能会产生意外的行为。
在这种情况下,使用多个代理可能是单独管理读取池和写入池的最简洁方法。您可以为单个数据库目标创建两个代理,然后将应用程序配置为使用第一个代理的写入器端点和第二个代理的只读端点。一个代理仅处理写入工作负载,另一个代理仅处理读取工作负载,并且可以为每个代理独立配置 MaxConnectionsPercent(以及其他设置)。这个解决方案涉及运行第二个代理的额外成本,但它比前述的替代方案更简单、更可预测。