将读取请求发送到只读副本

AWSBeginner
立即练习

介绍

订单应用目前将读写都发送到同一个主数据库。你将创建 PostgreSQL 只读副本,让应用查询使用副本,并证明主库新增数据能在副本读取,而副本拒绝写入。

这是数据库连接、SQL 和恢复实验之后的可选扩展。本独立环境提供主数据库和三笔示例订单。最后你将删除副本和主库。

认证考试关联

本实验为以下考试主题提供入门实践。

创建并检查只读副本

本步骤创建第二个 PostgreSQL 实例,用于接收主库的变更。

只读副本从复制的数据提供查询。PostgreSQL 通过预写式日志(WAL)从主库发送变更。备用引擎重放这些变更;副本是独立的数据库引擎,复制可能落后于刚提交的写入。

只读副本与 Multi-AZ 备用实例用途不同。只读副本为应用查询提供读取端点。标准 Multi-AZ DB 实例部署的备用实例用于可用性与故障转移,不提供应用读取。本实验实现只读副本,不执行 Multi-AZ 故障转移。

加载准备好的连接设置:

cd /home/labex/project
source database.env

检查来源数据库:

aws rds \
  describe-db-instances \
  --db-instance-identifier orders-db \
  --query 'DBInstances[0].{Identifier:DBInstanceIdentifier,Status:DBInstanceStatus,BackupRetention:BackupRetentionPeriod,Endpoint:Endpoint}'

RDS 要求只读副本来源的备份保留期非零。本单元提供保留期为 1 的主库。你将使用准备好的来源配置,本练习不测试自动备份调度。

创建副本:

aws rds \
  create-db-instance-read-replica \
  --db-instance-identifier orders-reader \
  --source-db-instance-identifier orders-db \
  --db-instance-class db.t3.micro \
  --no-publicly-accessible \
  --query 'DBInstance.{Identifier:DBInstanceIdentifier,Source:ReadReplicaSourceDBInstanceIdentifier,Status:DBInstanceStatus,Endpoint:Endpoint}'
aws rds \
  wait db-instance-available \
  --db-instance-identifier orders-reader

检查两个资源:

aws rds \
  describe-db-instances \
  --query 'DBInstances[].{Identifier:DBInstanceIdentifier,Source:ReadReplicaSourceDBInstanceIdentifier,Endpoint:Endpoint.Address}' \
  --output table

应看到 orders-reader 的独立端点,以及指向 orders-db 的来源关系。AWS View 应列出主库和只读副本。

查询实际副本引擎:

psql \
  "service=orders-reader" \
  --command 'SELECT current_database(), pg_is_in_recovery();'

应看到数据库 orders,且 pg_is_in_recovery 为 t(true)。PostgreSQL 实际运行于备用恢复模式,而不仅仅是 RDS 资源被标记为副本。

分离应用读写端点

本步骤让写入保持使用主库,并将应用查询发送到副本。

写入主库;读取可使用异步更新的副本

概念图:写入主库;读取可使用异步更新的副本。

检查应用配置:

cat app-config.json

两个主机字段目前都指向主库。获取副本端点:

READER_ENDPOINT=$(aws rds \
  describe-db-instances \
  --db-instance-identifier orders-reader \
  --query 'DBInstances[0].Endpoint.Address' \
  --output text)

仅修改 read_host:

jq \
  --arg host "$READER_ENDPOINT" \
  '.read_host = $host' \
  app-config.json > app-config.new
mv app-config.new app-config.json

通过应用读取:

curl \
  --silent \
  --show-error \
  --fail \
  http://127.0.0.1:8080/application/orders | jq

应看到三笔原始订单和 read_only: true。响应来自应用与备用数据库的实际 SQL 连接。AWS View 应将应用数据来源显示为只读副本。

副本使用从来源复制的数据库身份。端点选择决定连接到哪个实例;数据库认证和 SQL 权限仍然适用。

观察复制并拒绝副本写入

本步骤通过主库保存新订单,并从副本读取。

通过应用添加订单。应用的 POST 处理使用仍指向主库的 write_host:

curl \
  --silent \
  --show-error \
  --fail \
  --request POST \
  --header 'Content-Type: application/json' \
  --data '{"order_id":104,"customer":"Kai","total":"5.00"}' \
  http://127.0.0.1:8080/application/orders

应看到 saved: true。分别独立查询来源和副本:

psql \
  "service=orders-db" \
  --command 'SELECT order_id, customer, total FROM orders WHERE order_id = 104;'
psql \
  "service=orders-reader" \
  --command 'SELECT order_id, customer, total FROM orders WHERE order_id = 104;'

两者都应显示 Kai 的 5.00 订单。如果第一次副本查询还没有记录,稍等后重复同一查询:异步复制允许延迟。只读副本适合读取较多的工作,但需要立即一致性时,刚写入的值可能需要从主库读取。

再次读取应用列表:

curl \
  --silent \
  --show-error \
  --fail \
  http://127.0.0.1:8080/application/orders | jq

应看到四笔订单和 read_only: true。

尝试在副本上实际插入数据:

psql \
  "service=orders-reader" \
  --set=ON_ERROR_STOP=1 \
  --command "INSERT INTO orders (order_id, customer, total) VALUES (105, 'Lena', 9.00);"

应看到无法在只读事务中执行 INSERT 的错误。即使使用数据库主用户,副本恢复模式也会拒绝写入。确认两个引擎都不存在尝试写入的订单:

psql \
  "service=orders-db" \
  --command 'SELECT count(*) FROM orders WHERE order_id = 105;'
psql \
  "service=orders-reader" \
  --command 'SELECT count(*) FROM orders WHERE order_id = 105;'

两者都应返回 0。写入应在主库进行,一致性要求允许时,查询可以使用副本。 应用从只读副本读取原始订单及复制过来的新订单。

删除副本和主库

本步骤删除两个练习数据库实例。

先删除副本,再删除来源:

aws rds \
  delete-db-instance \
  --db-instance-identifier orders-reader \
  --skip-final-snapshot \
  --query 'DBInstance.DBInstanceIdentifier'
aws rds \
  wait db-instance-deleted \
  --db-instance-identifier orders-reader
aws rds \
  delete-db-instance \
  --db-instance-identifier orders-db \
  --skip-final-snapshot \
  --query 'DBInstance.DBInstanceIdentifier'
aws rds \
  wait db-instance-deleted \
  --db-instance-identifier orders-db
aws rds \
  describe-db-instances \
  --query 'DBInstances[].DBInstanceIdentifier'

应看到 [],AWS View 不应显示数据库。保留准备好的应用和网络资源。

总结

你创建了 PostgreSQL 只读副本,并验证实际备用恢复模式。你分离了应用读写端点,观察主库新增写入到达副本,并确认直接写入副本会被拒绝。最后你删除了副本和主库。

只读副本支持读取访问模式,其行为和用途不同于标准 Multi-AZ DB 实例部署中的备用实例。最终挑战将运用你学到的数据库连接和诊断技能。