nemo64/ dbal-rds-data
doctrine dbal 的 rds-data 驱动程序
Requires
- php: ~7.2||~8.0
- aws/aws-sdk-php: ^3.98
- doctrine/dbal: ^2.7
Requires (Dev)
- phpunit/phpunit: ^8.5
This package is auto-updated.
Last update: 2024-09-29 05:36:06 UTC
README
Aurora Serverless rds 数据 API 的 doctrine 驱动程序
这是一个驱动程序,用于在项目中使用 dbal 进行数据库访问时,使用 aws rds-data API。
它模拟了 MySQL 连接,包括事务。然而:该驱动程序永远不会建立持久连接。
这是一个实验性的项目。我在一个 symfony 项目中实现了它,与 doctrine orm 一起使用,并且驱动程序工作正常。我测试了模式工具、迁移和事务。
目前,我不会推荐使用 rds-data API,因为它在 2020 年年底秘密添加了每秒 1000 个请求的限制。它列在您的服务配额列表中的“每秒数据 API 请求”下,但在文档中并未提及。
你为什么想使用它?
- 数据 API 使您能够在 AWS 托管环境中使用数据库,而无需 VPC,这增加了复杂性和成本,特别是如果您需要通过 NAT 网关进行互联网访问时。
- 您的应用程序不需要明文中的数据库密码。您只需要访问 AWS API,这可以更好地管理。(还有其他方法可以实现相同的功能,但使用数据 API 真的很简单)
- 由于不需要建立直接的数据库连接和自动池管理,可能会有性能优势。
你为什么不使用它?
- 这个实现还没有经过 20 年的实战测试。请查看实现细节部分,看看您是否感到舒适。
- 运行大量小查询时的性能可能是最大的问题。rds data API(截至撰写本文时)有一个不太文档化的每秒每个账户 1000 个查询的限制。如果您曾经使用过 doctrine orm,那么您知道那并不多,特别是如果您运行未经优化的后台作业。AWS SDK 会重试查询,这意味着一切都会变慢。
- rds-data API 在ExecuteStatement调用中有大小限制,当您的应用程序增长时可能会成为问题,尽管它们目前似乎并未启用。
- rds-data API尚未在所有地区可用。尽管如此,这种限制正在逐渐放宽。
- rds-data API 由于其主要是无状态的,因此存在一些固有的限制。最大的问题是您不能设置(会话)变量。这也意味着您不能设置事务隔离级别,尽管在 dbal 中这仍然是一个可选功能。尽管如此,您仍然可以在事务中使用正常的锁定。
- 仅与rds-data API配合Aurora Serverless使用,并且这个库也限制您只能使用MySQL模式。如果您打算使用其他数据库,那么您现在无法使用rds-data API和这个库。以下是一些您可能想要考虑的替代方案:
- Aurora Serverless在Postgres模式下(虽然这很可能很容易添加到此处,我愿意接受pull请求)
- 使用Aurora Classic来获取SLA或从可预测的工作负载中受益于预留实例定价
- Aurora Global提供更好的可用性和Aurora Classic的所有好处
- 或者甚至正常RDS来节省金钱或使用Aurora未模拟的引擎
如何使用它
首先,您必须将数据库凭据作为一个秘密存储,包括用户名。然后请确保正确配置数据库访问以使用秘密和数据库。如果您创建了一个iam用户,则有一个“AmazonRDSDataFullAccess”策略可以直接使用。
如果您直接使用dbal,那么这是方法
<?php $connectionParams = array( 'driverClass' => \Nemo64\DbalRdsData\RdsDataDriver::class, 'host' => 'eu-west-1', // the aws region 'user' => '[aws-api-key]', // optional if it is defined in the environment 'password' => '[aws-api-secret]', // optional if it is defined in the environment 'dbname' => 'mydb', 'driverOptions' => [ 'resourceArn' => 'arn:aws:rds:eu-west-1:012345678912:cluster:database-1np9t9hdbf4mk', 'secretArn' => 'arn:aws:secretsmanager:eu-west-1:012345678912:secret:db-password-tSo334', ] ); $conn = \Doctrine\DBAL\DriverManager::getConnection($connectionParams);
或者使用简短的url语法,这使用环境变量更容易更改
<?php $connectionParams = array( 'driverClass' => \Nemo64\DbalRdsData\RdsDataDriver::class, 'url' => '//eu-west-1/mydb' . '?driverOptions[resourceArn]=arn:aws:rds:eu-west-1:012345678912:cluster:database-1np9t9hdbf4mk' . '&driverOptions[secretArn]=arn:aws:secretsmanager:eu-west-1:012345678912:secret:db-password-tSo334' ); $conn = \Doctrine\DBAL\DriverManager::getConnection($connectionParams);
鉴于我在symfony项目中开发,我也可以添加如何在symfony中定义驱动程序的方法
# doctrine.yaml doctrine: dbal: # the url can override the driver class # but I can't define this driver in the url which is why i made it the default # Doctrine\DBAL\DriverManager::parseDatabaseUrlScheme driver_class: Nemo64\DbalRdsData\RdsDataDriver url: '%env(resolve:DATABASE_URL)%'
# .env # you must not include a driver in the database url # in this case I also didn't include the aws tokens in the url DATABASE_URL=//eu-west-1/mydb?driverOptions[resourceArn]=arn&driverOptions[secretArn]=arn # the aws-sdk will pick those up # they are automatically configured in lambda and ec2 environments #AWS_ACCESS_KEY_ID=... #AWS_SECRET_ACCESS_KEY=... #AWS_SESSION_TOKEN=...
除了配置之外,它应该像任何其他dbal连接一样工作。
驱动程序选项
resourceArn
(字符串;必需)这是数据库集群的ARN。进入您的RDS-Management > 您的数据库 > 配置并从那里复制。secretArn
(字符串;必需)这是存储数据库密码的秘密的ARN。进入[SecretManager] > 您的秘密并使用Secret ARN。timeout
(整数;默认值=45)guzzle的超时设置。设置为0为无限期,但这可能没有用。rds-data API将最多阻塞45秒(见rds-data 文档)。带有continueAfterTimeout
选项的架构更新查询将自动执行。如果您需要运行较长的更新查询,则可能需要直接使用rds数据客户端。使用$dbalConnection->getWrappedConnection()->getClient()
获取aws-sdk客户端。pauseRetries
(整数;默认值=0)数据库暂停时的重试次数。如果您设置了此值,也请考虑设置pauseRetryDelay
以确保大致正确的重试时间。pauseRetryDelay
(整数;默认值=10)如果在最后一次尝试失败且由于数据库暂停而失败的情况下,等待再次尝试的秒数。截至撰写本文时,Aurora从30秒到2分钟暂停。这在大多数情况下等待时间太长。Lambda会自动重试事件,因此您通常最好让事件失败。用户通常也不会等一分钟来加载页面,因此您应该向他们展示适当的错误。请参阅暂停数据库以了解如何执行此操作。
CloudFormation
当然,这里有一个CloudFormation模板来配置Aurora Serverless和一个秘密,将它们组合在一起并设置一个包含所需信息的环境变量。
这可能具有serverless风格,但您应该能够掌握它。
# [...] iamRoleStatements: # allow using the rds-data api # https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/data-api.html#data-api.access - Effect: Allow Resource: '*' # it isn't supported to limit this Action: # https://docs.aws.amazon.com/IAM/latest/UserGuide/list_amazonrdsdataapi.html - rds-data:ExecuteStatement - rds-data:BeginTransaction - rds-data:CommitTransaction - rds-data:RollbackTransaction # this rds-data endpoint will use the same identity to get the secret # so you need to be able to read the password secret - Effect: Allow Resource: !Ref DatabaseSecret Action: # https://docs.aws.amazon.com/IAM/latest/UserGuide/list_awssecretsmanager.html - secretsmanager:GetSecretValue # [...] environment: DATABASE_URL: !Join - '' - - '//' # rds-data is set to default because custom drivers can't be named in a way that they can be used here - !Ref AWS::Region # the hostname is the region - '/mydb' - '?driverOptions[resourceArn]=' - !Join [':', ['arn:aws:rds', !Ref AWS::Region, !Ref AWS::AccountId, 'cluster', !Ref Database]] - '&driverOptions[secretArn]=' - !Ref DatabaseSecret # [...] # Make sure that there is a default VPC in your account. # https://console.aws.amazon.com/vpc/home#vpcs:isDefault=true # If not, click "Actions" > "Create Default VPC" # While your applications doesn't need it, the database must still be provisioned into a VPC so use the default. Database: Type: AWS::RDS::DBCluster # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rds-dbcluster.html Properties: Engine: aurora EngineMode: serverless EnableHttpEndpoint: true # https://stackoverflow.com/a/58759313 (not fully documented in every language yet) DatabaseName: 'mydb' MasterUsername: !Join ['', ['{{resolve:secretsmanager:', !Ref DatabaseSecret, ':SecretString:username}}']] MasterUserPassword: !Join ['', ['{{resolve:secretsmanager:', !Ref DatabaseSecret, ':SecretString:password}}']] BackupRetentionPeriod: 1 # day # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-rds-dbcluster-scalingconfiguration.html ScalingConfiguration: {MinCapacity: 1, MaxCapactiy: 2, AutoPause: true} DatabaseSecret: Type: AWS::SecretsManager::Secret # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-secretsmanager-secret.html Properties: GenerateSecretString: SecretStringTemplate: '{"username": "admin"}' GenerateStringKey: "password" PasswordLength: 41 # max length of a mysql password ExcludeCharacters: '"@/\' DatabaseSecretAttachment: Type: AWS::SecretsManager::SecretTargetAttachment # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-secretsmanager-secrettargetattachment.html Properties: SecretId: !Ref DatabaseSecret TargetId: !Ref Database TargetType: AWS::RDS::DBCluster
我还写了一篇更详细的文章,介绍了如何在多个堆栈之间设置和共享Aurora Serverless:www.marco.zone/shared-aurora-serverless-using-cloudformation
实现细节
错误处理
RDS数据API只提供错误消息,不提供错误代码。为了正确地将这些错误映射到dbal异常,我使用了一个巨大的正则表达式。参见:Nemo64\DbalRdsData\RdsDataException::EXPRESSION
。由于大部分内容都是使用mysql错误文档生成的,应该没有问题,但它可能不是100%可靠的。我在亚马逊开发者论坛上提出了这个问题,但还没有得到回复。
暂停的数据库
如果Aurora Serverless被暂停,您将收到以下错误消息
通信链路故障
上次成功发送到服务器的数据包是在0毫秒之前。驱动程序没有从服务器接收到任何数据包。
我将此错误消息映射到错误代码6000
(服务器错误是1xxx,客户端错误是2xxx)。它也将转换为dbal的Doctrine\DBAL\Exception\ConnectionException
,现有应用程序可能已经优雅地处理。但最重要的是,您可以专门捕获并处理代码6000
,以便更好地向用户说明数据库已暂停,并可能很快可用。
预编译语句中的参数
虽然ExecuteStatement支持参数,但它只支持命名参数。问号占位符需要模拟(这很有趣,因为mysqli驱动程序只支持问号占位符,不支持命名参数)。这是通过将?
替换为:1
、:2
等来实现的。替换算法将避免替换字符串文字中的?
,但请注意,出于安全考虑,您不应该混合重字符串文字和问号占位符。
字符串文字
每个驱动程序都有某种形式的连接感知的字符串文字转义功能。但由于rds-data API是无连接的,它没有这样的方法(当然,除了参数)。为了模拟转义功能,执行对非ASCII字符的检查。如果字符串是纯ASCII,它将直接通过addslashes
并得到引号。如果它包含更奇怪的字符,它将被base64编码以防止任何多字节SQL注入攻击。这应该在大多数情况下透明工作,但您绝对应该避免使用literal
函数,而应在可能的情况下使用参数绑定。