在 Amazon Aurora 全球数据库中实现运行时写入同步 数据库博客
在 Amazon Aurora 全球数据库中实现运行时写入同步
关键要点
在这篇文章中,我们介绍了一种轻量级的运行时方法,用于验证 Amazon Aurora 全球数据库集群中的写入复制。借助此解决方案,您可以将 Aurora 全球数据库应用于先前因跨区域写入同步需求而具有挑战性的工作负载或特定查询/事务。
Amazon Aurora 是一种 MySQL 和 PostgreSQL 兼容的关系数据库,专为云端设计,结合了传统企业数据库的性能和可用性以及开源数据库的简单性和经济性。全球数据库 是 Aurora 的一项功能,旨在用于全球分布的应用程序,允许单个 Amazon Aurora 数据库横跨多个 AWS 区域。全球数据库包含一个支持读写操作的主区域和最多五个只读的副区域。数据以异步方式进行复制,不影响数据库性能,并在每个区域内实现快速本地读取和低延迟。此外,它还提供故障转移和切换功能,以便进行计划的区域迁移或从区域服务故障中恢复。
Amazon Aurora 支持全球复制的高级功能,包括:
在兼容 PostgreSQL 的版本中,可以使用管理恢复时间目标配置,确保至少有一个副集群在目标 RPO 窗口内。兼容 MySQL 的版本支持可配置读一致性模式,作为其写入转发功能的一部分。此功能便于那些必须看到最新数据且不能容忍跨区域复制延迟的读取工作负载,同时能够将写入语句从读副本转发到主实例。请注意,写入转发功能在多区域全球数据库设置和没有全球数据库的单区域设置中都被支持。此篇文章专注于多区域场景。有关单区域用例的更多信息,请参见本地写入转发博客文章。
Aurora 全球数据库中的运行时复制跟踪用例
某些用例可能需要额外措施,快速验证全球复制的状态。在主区域进行写操作时,您可能需要验证这些写操作是否已传递到所有副区域中的某些或所有副区域,然后才能解锁其余的应用流程。

例如,您可能有一套全球性的微服务套件,提供“写入”和“读取”操作的 API。开发人员可以通过按不同顺序调用这些 API 构建工作流。您希望能够通过副区域扩展“读取” API,但又不想让开发人员担心全球复制延迟打破调用间的因果关系假设。您的 API 是无状态的,因此您不能简单地将特定的开发人员会话固定到单个区域。
在这种情况下,您可以:
考虑使用管理 RPO 功能。然而,RPO 是针对整个数据集配置的集群级参数,不适合实时检查复制状态,且该功能也不支持 Aurora 的兼容 MySQL 版本。对于在副区域处理的任何 API 调用,使用 GLOBAL 一致性模式。这可以通过少量代码更改来实现,但也会将每次读取的延迟增加数百毫秒,无论其是否之前进行了写入。截止目前,读取后写入一致性模式仅在 Aurora 的兼容 MySQL 版本中可用。在写入路径中实现基于时间的等待,使得每个“写入” API 等待足够长的时间,以便其效果被复制。这是一种与数据库引擎无关的方法,适用于 Aurora 的 MySQL 和 PostgreSQL 版本,但也会在正确性要等待多久和性能避免等待过长时间方面面临重大挑战。在本篇文章中,我提出了一种新颖的解决方案,针对需要在写入路径中进行复制验证的用例,使用 Aurora 全球数据库的元数据和几条存储过程。该解决方案旨在使数据库客户端能够验证最近在当前主区域中进行的更改写入是否已被副区域接收。验证可以在运行时进行,无需启用其他 Aurora 功能或修改集群范围的配置。
该解决方案的前提与 Aurora MySQL 和 Aurora PostgreSQL 兼容。本文使用 Aurora MySQL 进行存储过程中代码和演示,但您可以应用相同的逻辑来在 Aurora PostgreSQL 中实现该解决方案。
需要注意的是,该解决方案提供了一种验证复制进度的方法,但不保证成功复制。它不会改变全球集群的 RPO 状态,也不提供“零 RPO”或“零数据丢失”的路径。
解决方案概述
Aurora 全球数据库的复制基于记录集群中所有数据更改的事务日志。日志流在主区域生成,并复制到所有副区域。日志按日志序列号 (LSN) 线性排序,副区域按主区域中的相同顺序应用更改。由于更改的 LSN 排序,您可以使用 LSN 作为追踪每个副区域相对于主区域“写入位置”的“复制位置”的真实来源。追踪和比较这些 LSN 位置是写入同步解决方案的基础。
解决方案实现得益于必需的 Global Database LSN 元数据,适用于 Amazon Aurora 的 MySQL 兼容和 PostgreSQL 兼容版本:
对于 Aurora MySQL,元数据可以在版本 3040 及以上的auroraglobaldbstatus系统视图中找到。对于 Aurora PostgreSQL,相同的信息由在所有版本中支持的auroraglobaldbstatus函数提供。我们使用存储过程来编码逻辑,检查主区域的当前 LSN,并等待直到副区域满足所需条件。存在两个存储过程,以覆盖两个不同的使用场景:
等待给定数量法定人数的副区域达到主区域的 LSN。这在您希望确保数据至少被全球复制“N”次时相关。等待指定名称的副区域达到主区域的 LSN。如果您的全球拓扑包含多个副区域,并且您希望验证最近的更改是否已传递到特定的副区域而不仅仅是“任何”区域,则此方法非常有用。一旦条件满足,过程将返回,SQL 会话可以继续。代码支持简单的超时功能、易于调整的输入验证,以及对错误/不可能条件的基本检查。
以下图表展示了使用此解决方案的客户端会话的端到端流程:
客户端在主区域执行写入。这些写入必须在应用的其余流程成功运行之前复制到副区域。客户端调用存储过程,会话等待直到过程返回。存储过程逻辑验证所需条件是否得到满足例如,特定副区域是否在写入完成后跟上了进度。程序将控制返回到客户端会话。如果程序在没有错误的情况下执行,客户端可以假设更改已被副区域接收。实施步骤
本节描述了在 Amazon Aurora MySQL 兼容版本上实现解决方案的步骤。您可以通过调整代码以与 PostgreSQL 存储程序语法兼容来遵循相同的总体逻辑。
前提条件
要实施该解决方案,您需要:
一个 AWS 账户。一个位于至少两个区域的 Aurora MySQL 全球数据库集群:一个主区域和至少一个副区域。请选择 Aurora MySQL 3040 或更高版本。一个具有 MySQL 客户端软件的客户端环境。本演示使用的是一个带有mysql命令行客户端的Amazon 弹性计算云EC2实例。客户端环境与 MySQL 上的 Aurora 实例之间的连接。对 MySQL 存储程序语言的基本理解。实施
如前所述,解决方案使用两个存储过程以支持两种使用场景:
等待法定人数的区域,由存储过程auroraglobalwaitquorum实现。通过名称指定的区域进行等待,由存储过程auroraglobalwaitregion实现。您可以使用以下链接下载过程代码:
存储过程代码 auroraglobalwaitquorum存储过程代码 auroraglobalwaitregion接下来两节,让我们走过每个存储过程逻辑的关键部分。如果您熟悉 MySQL 存储程序语言,可以跳过这些部分,直接阅读代码文件。
存储过程 auroraglobalwaitquorum该过程旨在等待给定数量的副区域达到主区域的 LSN。让我们看一下代码:
sqlCREATE PROCEDURE auroraglobalwaitquorum(waitquorumsize INT waitintervalms INT waittimeouts INT debug BOOL)
该过程接受四个输入参数:
waitquorumsize 等待的副区域数量。waitintervalms 元数据检查之间的毫秒数。换句话说,当过程运行循环以检查复制元数据时,这就是循环迭代之间的休眠时间。waittimeouts 元数据检查循环的时间限制。如果在超时内未满足所需条件,程序将返回错误。debug 过程是否应在执行过程中返回信息消息。接下来,代码对输入参数进行基本的有效性检查:
蘑菇加速器使用教程sql 确保该过程只能在主区域中的写入实例上运行。IF @@innodbreadonly = 1 THEN SIGNAL SQLSTATE 45000 SET MESSAGETEXT = 该过程只能在写入实例上运行END IF
输入检查:确保我们在等待至少一个副区域IF waitquorumsize lt 1 THEN SIGNAL SQLSTATE 45000 SET MESSAGETEXT = 最低法定人数为 1END IF
输入检查:防止调用者过于频繁地检查复制状态IF waitintervalms lt 100 THEN SIGNAL SQLSTATE 45000 SET MESSAGETEXT = 最低等待间隔为 100 毫秒END IF
输入检查:确保等待超时的合理下限IF waittimeouts lt 1 THEN SIGNAL SQLSTATE 45000 SET MESSAGETEXT = 最低等待超时为 1 秒END IF
逻辑从检查主区域的当前 LSN 开始,这是您希望副区域达到的写入点。您可以通过其复制延迟时间戳字段中的“零”值识别主区域:
sqlSELECT awsregion highestlsnwritten FROM informationschemaauroraglobaldbstatus WHERE lastlagcalculationtimestamp = FROMUNIXTIME(0) INTO vprimaryname vprimarylsn
接下来,代码检查全球拓扑中的副区域总数,如果请求的法定人数等待的区域数量大于集群中实际存在的副区域数量,则抛出错误:
sqlSELECT COUNT() FROM informationschemaauroraglobaldbstatus WHERE awsregion ltgt vprimaryname INTO vsecondarycountall
IF vsecondarycountall lt waitquorumsize THEN SIGNAL SQLSTATE 45000 SET MESSAGETEXT = 请求的法定人数大于副区域总数END IF
实现的主要部分是在循环中检查全球复制状态,按照配置的间隔循环。当法定人数满足时循环成功结束,或者如果在超时内未能满足法定人数,则抛出错误:
sql 设置超时计时器的开始时间戳SELECT NOW(3) INTO vwaitstarttimestamp
当当前跟进的副区域数量小于法定人数时重复WHILE vsecondarycountquorum lt waitquorumsize DO
如果超出了等待超时,则退出IF TIMESTAMPDIFF(SECOND vwaitstarttimestamp NOW(3)) gt waittimeouts THEN SIGNAL SQLSTATE 45000 SET MESSAGETEXT = 法定人数等待超时END IF 在检查复制状态之前休眠waitintervalms毫秒SELECT SLEEP(waitintervalms/1000) INTO vwaitsleepoutputSET vwaitsleepcount = vwaitsleepcount 1 获取已经跟上进度的副区域数量SELECT count() FROM informationschemaauroraglobaldbstatusWHERE highestlsnwritten gt= vprimarylsn AND awsregion ltgt vprimarynameINTO vsecondarycountquorum
END WHILE
实施说明:
程序在成功执行时返回“Query OK”响应,表示满足了等待条件,除非启用了调试消息,输出将为空。程序使用SIGNAL语句和“未处理异常”错误代码来指示错误条件。这使得应用程序可以更容易地检测错误,与其他机制如警告代码或select “Something” AS ErrorMessage相比。如果程序返回错误或任何不是“Query OK”的内容,您应该假设 LSN 等待不成功。当在严格控制的环境中使用该过程时,您可以通过删除不必要的检查来提高实现效率。例如,您可以删除将请求的法定人数与实际的副区域数量进行比较的检查。这将节省一次元数据查询,但如果请求的法定人数大于副区域数量,您将经历一次循环超时。您可能会注意到,循环在检查全球复制状态之前首先执行休眠函数。如此一来,可以在最近的写入语句后立即调用该过程时提高效率。因为全球复制将需要一点时间才能跟上,所以我们不妨先休眠。您可以将操作顺序更改为“先检查,再休眠”,如果您的用例通常涉及在最近的写入语句与过程执行之间存在延迟。存储过程 auroraglobalwaitregion该过程旨在等待特定区域达到主区域的 LSN。代码与之前讨论的相似,因此让我们关注关键区别。
sqlCREATE PROCEDURE auroraglobalwaitregion(waitregionname VARCHAR(20) waitintervalms INT waittimeouts INT debug BOOL)
该过程接受三个输入参数。waitregionname 参数是要等待的区域名称,其他两个参数在前一节中已讨论。使用API注解作为区域名,例如“useast1”或“euwest1”。
与之前一样,逻辑始于了解主区域名称及其当前 LSN。如果请求的区域要等待的区域与主区域相同,代码将立即返回,因为无需等待。
sqlSELECT awsregion highestlsnwritten FROM informationschemaauroraglobaldbstatus WHERE lastlagcalculationtimestamp = FROMUNIXTIME(0) INTO vprimaryname vprimarylsn
如果我们等待的区域是主区域,立即返回IF vprimaryname = waitregionname THEN LEAVE procedureblockEND IF
循环本身看起来相似,但它检查的是特定区域的 LSN,而不是计数区域数量:
sql 当副区域 LSN 追赶上主区域 LSN 时重复WHILE vsecondarylsn IS NULL OR vsecondarylsn lt vprimarylsn DO
如果超出了等待超时,则退出IF TIMESTAMPDIFF(SECOND vwaitstarttimestamp NOW(3)) gt waittimeouts THEN SIGNAL SQLSTATE 45000 SET MESSAGETEXT = 法定人数等待超时END IF 在检查复制状态之前休眠waitintervalms毫秒SELECT SLEEP(waitintervalms/1000) INTO vwaitsleepoutputSET vwaitsleepcount = vwaitsleepcount 1 获取我们等待的副区域的 LSNSELECT highestlsnwritten FROM informationschemaauroraglobaldbstatus WHERE awsregion = waitregionname INTO vsecondarylsn 检查 LSN 信息是否返回。如果没有,意味着该区域不在拓扑中IF vsecondarylsn IS NULL THEN SIGNAL SQLSTATE 45000 SET MESSAGETEXT = 请求的副区域在全球拓扑中未找到END IF
END WHILE
实施