亚马逊AWS官方博客

浅谈数据库连接池优化之 Amazon RDS

本文主要介绍 Amazon RDS 和 Amazon Aurora 数据库连接池优化技巧,通过对数据库集群原理的解读和连接池参数的合理设置,希望可以帮助大家设置一个个位数秒级切换的关系型数据库长连接池,最大程度的减少数据库集群维护对业务造成的影响。

作为全球第一大云服务商,全球有海量的客户信任 Amazon Web Services 并将他们的服务运行在云上,客户们来自各行各业并且都需要长期稳定的对互联网用户提供 365*24 小时的不间断服务。为了满足云上托管服务的合规和安全要求,Amazon Web Services 在安全为第一优先级的理念指导下及时的对托管服务进行软硬件维护,包括但不限于定期的软件 bugfix 和安全补丁修复、软件版本维护和更新、硬件维护和硬件故障后的自动恢复等,这其中就包括了关系型数据库产品。Amazon RDS 和 Amazon Aurora 数据库为用户提供了业界领先的 MySQL 和 PostgreSQL 关系型数据库服务并连续多年被评为云数据库管理系统魔力象限领导者

在使用这些数据库产品的时候往往会有如下的数据库连接切换场景:

  1. 客户需要随着业务发展变更数据实例的规格,比如换用最新的 Graviton CPU 机型或者调整当前机型的规格。
  2. 客户需要保证自己的数据库符合合规要求并保证数据库安全,比如用户需要在规定的时间内对自己的数据库打上安全补丁或者已知的 Bugfix 补丁。
  3. 客户需要升级自己数据库的软件版本来使用最新的数据库产品特性,比如从 RDS MySQL 8.0.35 升级到 MySQL 8.0.36。
  4. 在极小的概率下数据库所在的物理服务器出现硬件故障,这时 Amazon Web Services 会重新启动一台全新的服务器并再次加入当前数据库集群,实现数据库集群的故障自愈。由于数据是保存在 EBS 盘中并做了高可用冗余,这类情况数据不会丢失但是需要我们进行连接切换。

我们先对 Amazon Web Services 的数据库高可用集群进行介绍,然后解读具体的连接池优化原理。

Amazon RDS 数据库集群结构

Amazon RDS 是 Amazon Web Services 提供的一个托管式关系型数据库服务的集合,它支持多种数据库引擎,包括 MySQL、PostgreSQL、MariaDB、SQL Server、Oracle 和 DB2,能 100% 兼容上述的数据库。作为一种易于管理的云托管关系数据库服务,它针对总拥有成本进行了优化,增加了大量的性能增强和高可用设计,能自动执行无差别的数据库管理任务。

以 MySQL 为例,RDS 提供了 2 种高可用结构,详情请看 Amazon RDS 多可用区。第一种是 Multi-AZ DB instance deployments,第二种是 Multi-AZ DB cluster deployments

Multi-AZ DB instance deployments 集群结构

Multi-AZ DB instance deployments 中,Amazon RDS 会自动在同一 Region 中的不同可用区中配置和维护一个同步备用副本。主数据库实例(primary)会跨可用区同步复制数据到备用实例(standby),这提供了数据冗余并可以在系统备份期间将延迟峰值降至最小;在计划内的系统维护期间,运行具有高可用性的 multi-az instance 集群可以提高可用性,在数据库实例发生故障和可用区中断时依然能保证数据库可用。值得注意的是集群中对外服务的永远是 primary 实例,standby 实例只是保持同步并随时待命替换 primary 实例。

当该数据库集群进行维护时,primary 实例和 standby 实例会逐个执行维护操作,期间 2 个实例的角色会互换,原先的 standby 变成新的 primary、原先的 primary 变成新的 standby。RDS 更新 endpoint 的 DNS,让 endpoint 指向最新的 primary 实例来持续对外提供服务。在客户端的角度来看,连接的恢复无需长时间等待 primary 实例完成全部维护操作后才能进行,它只需要根据最新的 DNS 解析来尽快的连接最新的 primary 实例,这能减少集群维护造成的中断时间。

Multi-AZ DB cluster deployments 集群结构

 Multi-AZ DB cluster deployments 在同一个 Region 的三个独立可用区中有一个写入实例(writer)和两个读取实例(reader),一共 3 个实例。实例间的数据同步是半同步复制,当数据从 writer 写入时,会自动同时复制到两个 reader 中,事务不需要在全部的 reader 上都确认后才能提交,只需要其中一个 reader 的确认就能提交,这相比 Multi-AZ DB instance deployments 能可提供更高的可用性、增加读取工作负载容量以及更低的写入延迟。值得注意的是数据库集群中 writer 可进行读写操作,reader 只能进行只读操作。

当该数据库集群进行维护时,会将其中一个 reader 提升为新的 writer,以前的 writer 重新加入集群成为 reader。对客户端而言同样需要根据最新的 DNS 解析来尽快的连接到最新的 writer 实例和 reader 实例上。

Amazon Aurora 数据库集群结构

Amazon Aurora 集群使用了计算和存储分离的结构,能 100% 兼容 MySQL 和 PostgreSQL。集群包含一个或多个数据库实例以及一个共享数据集群卷,其中一个实例作为 primary 实例能进行数据的读写操作,其余的实例作为 replica 实例只能进行数据的读操作。实例间的数据同步由集群卷来完成,卷中的数据块在 3 个可用区中有 6 份副本,只有 6 份副本中的多数副本确认写入后事务才会提交,相比 RDS 进一步提高了数据的可靠性, 由于数据同步不需要额外消耗实例的硬件资源,相比 RDS 能提供更高的性能。

数据层是集群间共享的,所以 Aurora 集群维护时实例间无需要额外考虑已提交事务的数据同步问题,primary 实例和 replica 实例能更快地完成 failover 并刷新 DNS 解析。客户端需要根据最新的 DNS 解析来尽快地连接到最新的 primary 实例和 replica 实例上。

连接池优化原理

我们分析上面的多种高可用集群结构,发现 Aurora 和 RDS 集群维护的时候都会进行实例间的 failover 并通过 DNS 更新的方式来对外公布最新的集群状态。在维护期间,客户端需要根据 DNS 解析来不断的尝试重连,由于 DNS 本身由默认的 TTL 时间(5S)并且某些语言有本地 DNS 缓存,想要及时的得到最新的 DNS 解析并不容易,传统的连接池设计一般需要30-120S才能完成更新。显然,连接池个位数秒级更新无法依靠 DNS 更新的方式来实现,只有直接查询到数据库内部的拓扑关系才有可能实现。

为了解决上述问题,Amazon Web Services 官方提供了数据库 Driver Wrapper,支持 MySQL 和 PostgreSQL 的连接。它不对现有开源数据库 Driver 进行修改,而是在开源数据库 Driver 之上做了一层包装,专为云上的 RDS 和 Aurora 做了优化。目前提供了 Java 版本Python 版本,客户现有的程序不需要修改代码就切换到上述的 Driver Wrapper 上。除了可用到 Amazon Web Services 上外,这个 Wrapper 也可以和标准的自建数据库相兼容,这减少了云上数据库和自建数据库的差异性,方便客户进行本地开发。

以 Java 版本的 Amazon Web Services (AWS) JDBC Driver 为例,它提供了多种 plugin,当我们使用 Aurora 和 RDS Multi-AZ DB cluster 集群时,其中的 Failover Plugin 和 Enhanced Failure Monitoring 可以帮助我们实现连接池个位数秒级刷新。

下图为 failover plugin 的原理图:

Failover Plugin 会自动处理 failover 转移过程,感知到集群拓扑关系变化后会刷新连接池中的连接、重新建立连接到新的 primary 实例和 replica 实例、将后续请求路由到健康的实例。

Aurora 或者 RDS Multi-AZ Cluster 内部各自有独立的集群 metadata 信息,Failover plugin 可以通过这些 metadata 感知到集群拓扑状态,然后进行连接池的刷新。

在 RDS Multi-AZ Cluster MySQL 中,我们可以直接查询到集群拓扑关系,具体的 SQL 查询语句为:

select * from mysql.rds_topology;

在 Aurora MySQL 中,我们也可以查询到集群拓扑关系,具体的 SQL 查询语句为:

SELECT SERVER_ID, CASE WHEN SESSION_ID = 'MASTER_SESSION_ID' THEN TRUE ELSE FALSE END,CPU, REPLICA_LAG_IN_MILLISECONDS, LAST_UPDATE_TIMESTAMP 
FROM information_schema.replica_host_status
WHERE time_to_sec(timediff(now(), LAST_UPDATE_TIMESTAMP)) <= 300 OR SESSION_ID = 'MASTER_SESSION_ID';

Amazon Web Services (AWS) JDBC Driver 内置了多种数据库集群检测器,它们可以可靠地检查到当前的数据库种类(MySQL、PostgreSQL)、数据库的集群类型(Aurora、multi-az cluster 、multi-az instance、others)。 Failover Plugin 主要工作流程就是先用检测器检测到具体的集群结构,然后执行集群拓扑监视工作。如果对某类数据库拓扑关系的检测原理感兴趣,可以查看具体的源码。

下图是增强型故障监控(Enhanced Failure Monitoring,EFM)的原理图:

增强型故障监控(Enhanced Failure Monitoring,EFM)通过主动定期检查数据库实例的健康状况,能够比传统方法更早地发现故障。如果健康检查失败或实例无响应,EFM 会判定该节点处于故障状态,一旦检测到故障节点,AWS JDBC 驱动程序会立即中断与该节点的连接,如果故障节点是集群中的 primary 实例,AWS JDBC 驱动程序会启动故障转移过程,尽快切换到新的健康的 primary 实例。

EFM 本质上是一个采用心跳检查机制的定期检查器,最佳实践是让它和 failover plugin 搭配使用,双管齐下尽早发现数据库集群的异常并采取连接池刷新操作。

使用 Java Spring 项目进行功能验证

上述的分析完成后,本文将以 mybatis-spring-boot-starter 为例进行实际的演示,测试的集群结构为 RDS Multi-AZ Cluster,数据库为 MySQL 8。

在传统普通的 Spring 项目基础,我们只需要补充一个 aws-advanced-jdbc-wrapper 即可,代码无需任何改动。

然后我们进行精细化设置,具体的设置如下:

下表是我提取的关于 aws-advanced-jdbc-wrapper 的重要设置:

spring.datasource.url 必填,通过 aws-wrapper 来生效 aws-advanced-jdbc-wrapper
spring.datasource.driver-class-name

可选,但是建议填充

指定使用 software.amazon.jdbc.Driver,明确提示 spring 使用 aws 提供的 driver

wrapperPlugins

启用的插件列表,我们可以按需选择。非常建议启用前面我们提到的 failover 和 efm2

详情请看:List of Available Plugins

wrapperDialect

可选,但是建议填充,如果提供则会自动进行集群类型检查,填充为具体的集群类型可以提升检查速度

详情请看:Database Dialects

应用启动时,wrapper 会基于开源的 driver 进行连接初始化。failover plugin 这时会获取到集群的拓扑状态并开始持续监控集群拓扑关系,efm2 插件开始持续进行定期检查,2 个插件一起密切协作。

应用稳定运行后,我们手动触发 rds multi-az cluster 集群的 failover。当 failover 发生时,wrapper 会快速发现集群拓扑关系的变化并作出反应,期间可能输出一些 info、warn 和 error(非业务 SQL 错误,不影响业务)信息,wrapper 会尝试尽快重新建立连接。这时我们看到最早的 error 产生的时间点是 2024-09-22T18:31:12.752Z。

连接池刷新过程中,大压力下可能会出现少量的 SQL 执行超时错误,数量不多。下图是业务 SQL 执行错误开始时间,时间点是 2024-09-22T18:31:13.239Z。

下图是业务 SQL 执行错误结束时间,时间点是 2024-09-22T18:31:13.879Z。

计算可知经过优化后的连接池,在遇到大约 1S 完成连接池刷新(典型值为 1~2S),连接池完成刷新,应用恢复正常。

通过实验我们发现通过使用 Amazon Web Services (AWS) JDBC Driver 成功地实现了数据库连接池的个位数秒级自愈,相比传统连接池所需的 30~120S,新的连接池设计指数级地缩短了数据库维护的影响时间。

补充使用建议:

  • 建议在业务低峰期进行线上数据库集群的维护。
  • 建议优先使用 Aurora 多实例结构或 RDS Multi-AZ Cluster 结构,它们是高可用结构、性能更好、failover 耗时更短,能最大程度的减少数据库集群维护造成的影响,实现连接池个位数秒级切换。
    • 不建议正式环境使用 Aurora 单实例或者 RDS 单实例,它们不是高可用结构,failover 会长很多。
    • 如果使用的是 RDS Multi-AZ instance 结构,也可以在连接池参数调整后使用 wrapper 中的部分插件,也能降低连接池的切换时间。
  • 正式环境中关键性业务可能持续承受较大的压力,如果不是经过充分测试和论证不建议直接使用 T 系列机型。
  • Amazon RDS Proxy 也能减少数据库集群的维护影响,但是 2 者的实现原理不一样。用户可以按照自己的实际情况自行选择。

总结

在服务客户的过程中,我们发现很多客户反馈他们的关键业务是强依赖关系型数据库的,数据库的每次维护都会导致连接的切换,切换时间一般是分钟级的,这对线上业务连续性造成了一定的影响。所以我们在使用托管数据库时需要全面考虑数据库集群的高可用性设计,确保自己数据库集群是多可用区部署的、按照业务低峰期设计了数据库维护窗口、做好了定期的数据库备份、开发人员已经对数据库主/被动软件升级做了充分的全面的测试演练。这些前置条件满足后,我们再次回顾整个维护流程会发现连接池优化是整个维护流程的核心,一个优秀的连接池如果做到了动态感知数据库集群拓扑关系变化并立即做出连接调整,它就可以最快地重新建立连接并恢复业务。重连间隔越短,数据库维护造成的影响就越少,最终能最大化的释放人力负担。

虽然本文中是以 RDS MySQL 为例进行的连接池优化解读,但同时也适用于 RDS PostgreSQL 和 Amazon Aurora。

参考链接

本篇作者

罗新宇

亚马逊云科技解决方案架构师,在架构设计与开发领域有非常丰富的实践经验,目前致力于 Serverless 在云原生架构中的应用。