使用逻辑故障切换槽的高级灾难恢复 (DR)

本页面介绍如何使用逻辑故障切换槽配置 Cloud SQL for PostgreSQL 的 逻辑复制,使其与 高级灾难恢复 (DR) 操作( 特别是 Cloud SQL 企业 Plus 版实例上的切换和副本故障切换)无缝协作。

Cloud SQL 的高级灾难恢复 (DR) 功能可实现强大的灾难恢复功能。与 PostgreSQL 的逻辑复制相结合时,至关重要的是,复制流在切换或副本故障切换后保持不中断。

将高级灾难恢复 (DR) 与 PostgreSQL 的逻辑复制结合使用,您可以确保逻辑订阅者不会丢失任何数据,并且可以在灾难恢复事件后自动重新连接到新的主实例,从而确保业务连续性。

您可以在具有以下配置的 Cloud SQL 实例上使用此功能:

  • PostgreSQL 版本 17 或更高版本
  • Cloud SQL 企业 Plus 版
  • 专用服务访问通道

    我们建议使用专用服务访问通道域名服务 (DNS) 写入端点来启用自动逻辑订阅者重新连接。

准备工作

使用逻辑复制设置高级灾难恢复 (DR)

使用 PostgreSQL 的逻辑复制设置高级灾难恢复 (DR) 的过程包含以下简要步骤:

  1. 设置环境变量和堡垒虚拟机
  2. 创建和设置主实例
  3. 创建和指定灾难恢复副本
  4. 创建和设置逻辑订阅者实例
  5. 创建逻辑复制订阅
  6. 执行切换或副本故障切换
  7. 验证复制
  8. 清理新副本上的孤立复制槽
  9. 可选:执行切换回

设置环境变量和堡垒虚拟机

  1. 设置以下环境变量。

    # Project
    export PROJECT="PROJECT_ID"
    
    # Instance names
    export PRIMARY_INSTANCE_NAME="PRIMARY_INSTANCE"
    export DR_REPLICA_NAME="DR_REPLICA"
    export SUBSCRIBER_INSTANCE_NAME="SUBSCRIBER_INSTANCE"
    export BASTION_VM_NAME="BASTION_VM"
    
    # Regions and zones
    export PRIMARY_REGION="PRIMARY_REGION"
    export REPLICA_REGION="REPLICA_REGION"
    export SUBSCRIBER_REGION="SUBSCRIBER_REGION"
    export VM_ZONE="VM_ZONE"
    
    # Network
    export NETWORK_NAME="NETWORK"
    
    # Credentials
    export POSTGRES_PASSWORD="PASSWORD"
    
    # Set gcloud project
    gcloud config set project PROJECT_ID
    

    替换以下内容:

    • PROJECT_ID:您的项目的 ID。
    • PRIMARY_INSTANCE:主 Cloud SQL 实例的名称。
    • DR_REPLICA:副本的名称。
    • SUBSCRIBER_INSTANCE:订阅者实例的名称。
    • BASTION_VM:堡垒虚拟机的名称。
    • PRIMARY_REGION:主实例所在的区域。
    • REPLICA_REGION:副本所在的区域。副本必须与主实例位于不同的区域。
    • SUBSCRIBER_REGION:订阅者所在的区域。
    • VM_ZONE:堡垒虚拟机所在的可用区。
    • NETWORK:您的 VPC 网络的名称。
    • PASSWORDpostgres 用户的密码。
  2. 创建一个 Compute Engine 堡垒虚拟机。

    Cloud SQL 实例使用专用 IP。因此,请在 VPC 网络中创建一个 Compute Engine 堡垒主机虚拟机。

    gcloud compute instances create $BASTION_VM_NAME \
      --zone=$VM_ZONE \
      --machine-type=e2-small \
      --network=projects/$PROJECT_ID/global/networks/$NETWORK_NAME \
      --image-project=debian-cloud \
      --image-family=debian-11 \
      --project=$PROJECT
    
  3. 连接到堡垒虚拟机。

    gcloud compute ssh $BASTION_VM_NAME \
      --zone=$VM_ZONE \
      --project=$PROJECT
    
  4. 在堡垒虚拟机上,安装 PostgreSQL 客户端。

    sudo apt-get update
    sudo apt-get install -y postgresql-client
    exit
    

后续步骤中的 PostgreSQL 命令将从堡垒虚拟机运行。

创建和设置主实例

  1. 创建主 Cloud SQL 实例。

    gcloud sql instances create $PRIMARY_INSTANCE_NAME \
      --database-version=POSTGRES_17 \
      --edition=ENTERPRISE_PLUS \
      --region=$PRIMARY_REGION \
      --tier=db-perf-optimized-N-2 \
      --no-assign-ip \
      --network=projects/$PROJECT/global/networks/$NETWORK_NAME \
      --project=$PROJECT
    
  2. 启用逻辑解码。

    gcloud sql instances patch $PRIMARY_INSTANCE_NAME \
      --database-flags=cloudsql.logical_decoding=on \
      --project=$PROJECT
    
  3. 在主实例上为 postgres 用户设置密码。

    gcloud sql users set-password postgres \
      --instance=$PRIMARY_INSTANCE_NAME \
      --password="$POSTGRES_PASSWORD" \
      --project=$PROJECT
    
  4. 从堡垒虚拟机连接到主实例。

    1. 检索主实例的专用 IP 地址。

      gcloud sql instances describe $PRIMARY_INSTANCE_NAME \
        --format="value(ipAddresses[0].ipAddress)" \
        --project=$PROJECT
      

      复制并保留主实例的专用 IP 地址。

    2. 通过 SSH 连接到堡垒虚拟机。

      gcloud compute ssh $BASTION_VM_NAME --zone=$VM_ZONE --project=$PROJECT
      
    3. 从堡垒虚拟机连接到主实例。

      psql -h PRIMARY_PRIVATE_IP -U postgres
      

      PRIMARY_PRIVATE_IP 替换为此过程 中检索到的主实例的专用 IP,该 IP 在 第 4.a 步 中检索到。

    4. 系统提示输入密码时,输入变量 $POSTGRES_PASSWORD

      您的堡垒虚拟机现在已通过 PostgreSQL 连接到主实例。

  5. 授予权限并创建发布内容。

    1. postgres 用户授予 REPLICATION 权限。

      ALTER USER postgres WITH REPLICATION;
      
    2. 授予对公共架构和表的必要权限。

      GRANT SELECT ON ALL TABLES IN SCHEMA public TO postgres;
      
    3. 为所有表创建发布内容。

      CREATE PUBLICATION my_publication FOR ALL TABLES;
      
    4. 输入 exit 以退出 PostgreSQL,然后再次输入 exit 以关闭堡垒虚拟机的 SSH 会话。

创建和指定灾难恢复副本(灾难恢复副本)

  1. 创建灾难恢复副本。

    gcloud sql instances create $DR_REPLICA_NAME \
      --master-instance-name=$PRIMARY_INSTANCE_NAME \
      --edition=ENTERPRISE_PLUS \
      --tier=db-perf-optimized-N-2 \
      --region=$REPLICA_REGION \
      --no-assign-ip \
      --network=projects/$PROJECT/global/networks/$NETWORK_NAME \
      --project=$PROJECT
    
  2. 将此副本指定为灾难恢复副本。

    gcloud sql instances patch $PRIMARY_INSTANCE_NAME \
      --failover-dr-replica-name=$DR_REPLICA_NAME \
      --project=$PROJECT
    
  3. 为逻辑槽同步配置灾难恢复副本。

    gcloud sql instances patch $DR_REPLICA_NAME \
      --database-flags="^:^cloudsql.logical_decoding=on:hot_standby_feedback=on:sync_replication_slots=on:cloudsql.logical_slot_sync_dbname=postgres" \
      --project=$PROJECT
    
  4. 配置主实例和灾难恢复副本之间的同步复制。

    为防止在主实例突然中断和后续副本故障切换的情况下逻辑订阅者可能丢失数据,我们建议您配置主实例和灾难恢复副本之间的同步复制。

    在主实例上设置 cloudsql.synchronized_standby_replicas 会强制主实例的逻辑复制预写式日志 (WAL) 发送方等待灾难恢复副本接收并刷新给定事务的 WAL,然后再将该事务发送给逻辑订阅者。这样可确保灾难恢复副本的状态始终领先于或等于逻辑订阅者的状态。

    gcloud sql instances patch $PRIMARY_INSTANCE_NAME \
      --database-flags="^:^cloudsql.logical_decoding=on:cloudsql.synchronized_standby_replicas=$DR_REPLICA_NAME" \
      --project=$PROJECT
    

创建和设置逻辑订阅者实例

  1. 创建订阅者实例。

    gcloud sql instances create $SUBSCRIBER_INSTANCE_NAME \
      --database-version=POSTGRES_17 \
      --tier=db-perf-optimized-N-2 \
      --region=$SUBSCRIBER_REGION \
      --no-assign-ip \
      --network=projects/$PROJECT/global/networks/$NETWORK_NAME \
      --project=$PROJECT
    
  2. 在订阅者上启用逻辑解码。

    gcloud sql instances patch $SUBSCRIBER_INSTANCE_NAME \
      --database-flags=cloudsql.logical_decoding=on \
      --project=$PROJECT
    

创建逻辑复制订阅

  1. 检索主实例的专用服务访问通道写入端点。

    gcloud sql instances describe $PRIMARY_INSTANCE_NAME \
      --format="value(replicationCluster.psaWriteEndpoint)" \
      --project=$PROJECT
    

    复制并保留写入端点。

  2. 连接到订阅者实例。

    1. 更新订阅者实例的 postgres 用户的密码。

      gcloud sql users set-password postgres \
        --instance=$SUBSCRIBER_INSTANCE_NAME \
        --password="$POSTGRES_PASSWORD" \
        --project=$PROJECT
      
    2. 检索订阅者实例的专用 IP 地址。

      gcloud sql instances describe $SUBSCRIBER_INSTANCE_NAME \
        --format="value(ipAddresses[0].ipAddress)" \
        --project=$PROJECT
      

      复制并保留专用 IP 地址。

    3. 通过 SSH 连接到堡垒虚拟机。

      gcloud compute ssh $BASTION_VM_NAME \
        --zone=$VM_ZONE \
        --project=$PROJECT
      
    4. 从堡垒虚拟机通过 PostgreSQL 连接到订阅者实例。

      psql -h SUBSCRIBER_PRIVATE_IP -U postgres
      

      SUBSCRIBER_PRIVATE_IP 替换为此过程 中复制的订阅者实例的专用 IP,该 IP 在 第 2.b 步 中复制。

    5. 系统提示输入密码时,输入变量 $POSTGRES_PASSWORD

  3. 创建订阅。

    CREATE SUBSCRIPTION my_subscription
    CONNECTION 'host=DR_CLUSTER_PSA_DNS_WRITE_ENDPOINT port=5432 dbname=postgres user=postgres password=PASSWORD'
    PUBLICATION my_publication
    WITH (failover = true);
    

    替换以下内容:

    • DR_CLUSTER_PSA_DNS_WRITE_ENDPOINT:您在此过程的第 1 步 中复制的专用服务 访问通道写入端点。
    • PASSWORD:变量 ${POSTGRES_PASSWORD} 的值。
  4. 退出 PostgreSQL 和堡垒虚拟机 SSH 会话。

  5. 可选。验证灾难恢复副本上的槽持久性。

    此转换到持久性 (temporary = false) 通常会快速发生,如果主实例活动较少,通常会在几秒钟内发生。在主实例上写入负载较重的情况下,此过程可能需要更长时间,通常约为一分钟。 完成这些手动命令后,槽应该是持久性的。

    1. 获取灾难恢复副本的专用 IP 地址。

      gcloud sql instances describe $DR_REPLICA_NAME \
        --format="value(ipAddresses[0].ipAddress)" \
        --project=$PROJECT
      

      复制并保留灾难恢复副本的专用 IP 地址。

    2. 通过 SSH 连接到堡垒虚拟机。

      gcloud compute ssh $BASTION_VM_NAME \
        --zone=$VM_ZONE \
        --project=$PROJECT
      
    3. 从堡垒虚拟机连接到灾难恢复副本。

      psql -h DR_REPLICA_PRIVATE_IP -U postgres
      

      DR_REPLICA_PRIVATE_IP 替换为此过程第 5.a 步 中检索到的灾难恢复副本的专用 IP 地址。

    4. 系统提示输入密码时,输入变量 $POSTGRES_PASSWORD

    5. 检查槽状态。

      SELECT slot_name, slot_type, temporary, failover, synced
      FROM pg_replication_slots
      WHERE slot_type = 'logical' AND failover = true;
      

      等待 temporary 列变为 f。这通常需要不到一分钟的时间。

    6. 退出 PostgreSQL 和堡垒虚拟机 SSH 会话。

执行切换或副本故障切换

根据您的场景选择要执行的操作:

  • 切换(计划的角色反转):当主实例在线且运行状况良好时,选择此选项进行计划维护、 灾难恢复测试或切换角色。此操作可确保物理复制不会丢失任何数据。

    gcloud sql instances switchover $DR_REPLICA_NAME \
      --project=$PROJECT
    
  • 副本故障切换(灾难恢复):当主实例 不可用或无响应时,选择此选项。此操作会将灾难恢复副本升级为主实例。为最大限度地降低逻辑订阅者丢失数据的风险,请确保在主实例上设置了 cloudsql.synchronized_standby_replicas,如创建和指定灾难恢复副本(灾难恢复副本)中所建议的那样。

    gcloud sql instances promote-replica $DR_REPLICA_NAME \
      --failover \
      --project=$PROJECT
    

    $DR_REPLICA_NAME 的升级会快速发生。但是,原始主实例 ($PRIMARY_INSTANCE_NAME) 只有在恢复在线状态后才会重新配置为新主实例的副本。您可以在操作日志中查找 $PRIMARY_INSTANCE_NAME 上完成的 RECONFIGURE_OLD_PRIMARY 操作来跟踪此过程。运行以下命令:

    gcloud sql operations list --instance=$PRIMARY_INSTANCE_NAME --project=$PROJECT --limit=10`
    

    只有在此阶段完成后,灾难恢复设置才会完全恢复。

在任一操作之后,订阅者都会通过专用服务访问通道写入端点自动重新连接到新的主实例 $DR_REPLICA_NAME

标志管理

在切换和副本故障切换操作期间和之后,Cloud SQL 的工作流会自动管理灾难恢复集群中两个实例上的必要数据库标志,包括以下内容:

  • 确保成为新副本的实例上的逻辑槽同步标志 (cloudsql.logical_decoding, hot_standby_feedback, sync_replication_slots, cloudsql.logical_slot_sync_dbname) 正确无误。
  • 成为新主实例的实例上的 cloudsql.synchronized_standby_replicas 标志会自动更新,以指向新的灾难恢复副本的名称。

您无需在切换或副本故障切换操作后手动重新应用或更改这些标志。Cloud SQL 会维护主实例和副本角色的正确配置。

验证复制

  1. 检查订阅者的状态。

    1. 从堡垒虚拟机运行以下命令。

      psql -h SUBSCRIBER_PRIVATE_IP -U postgres
      

      SUBSCRIBER_PRIVATE_IP 替换为订阅者实例的专用 IP 地址。

    2. 在订阅者实例上,运行以下命令。

      SELECT subname, pid IS NOT NULL AS is_active
      FROM pg_stat_subscription;
      

      状态应为 streaming

  2. 检查新主实例的复制槽的状态。新主实例是之前的灾难恢复副本 ($DR_REPLICA_NAME)。

    1. 获取新主实例的专用 IP 地址。

      gcloud sql instances describe $DR_REPLICA_NAME \
        --project=$PROJECT --format="value(ipAddresses[0].ipAddress)"
      

      复制并保留新主实例的专用 IP 地址。

    2. 通过 SSH 连接到堡垒虚拟机。

      psql -h NEW_PRIMARY_PRIVATE_IP -U postgres
      

      NEW_PRIMARY_PRIVATE_IP 替换为您在上一步中复制的新主实例的专用 IP 地址。

    3. 在新主实例上,运行以下命令。

      SELECT
          slot_name,
          slot_type,
          active,
          synced,
          active_pid,
          pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), confirmed_flush_lsn)) AS replication_lag
      FROM pg_replication_slots
      WHERE slot_type = 'logical';
      

      槽(例如 my_subscription)应为 active = t

      SELECT
          application_name,
          state
      FROM pg_stat_replication;
      

      pg_stat_replication 应显示订阅者已连接。

清理新副本上的孤立复制槽

切换和故障切换操作完成后,原始主实例 ($PRIMARY_INSTANCE_NAME) 现在是一个副本。此新副本实例仍在其磁盘上保留名为 my_subscription 的原始逻辑复制槽。此 my_subscription 槽现在是孤立的,因为订阅者应通过专用服务访问通道写入端点连接到新的主实例 ($DR_REPLICA_NAME)。

Cloud SQL 不会自动从新副本中移除此孤立槽。这是因为 Cloud SQL 无法确定订阅者是否配置为使用实例的 IP 地址而不是专用服务访问通道写入端点。在手动更改订阅之前,订阅者可能仍会尝试连接到新副本上的此旧槽。 自动删除槽可能会破坏此类配置。

新副本 ($PRIMARY_INSTANCE_NAME) 上存在此孤立槽会导致此实例上的 slotsync 工作器进程在日志中生成错误。您可能会在新副本的 postgres.log 中看到类似以下内容的错误消息。由于 slotsync 工作器不断尝试,此错误会不断重复。

ERROR: exiting from slot synchronization because same name slot "my_subscription" already exists on the standby

为防止此类错误并让 slotsync 工作器在此副本上正确建立 my_subscription 槽的新同步版本,您需要手动删除孤立槽。这样可确保如果您将来打算切换回,此实例已做好充分准备。

  1. 检索新副本 ($PRIMARY_INSTANCE_NAME) 的专用 IP 地址。

    gcloud sql instances describe $PRIMARY_INSTANCE_NAME \
      --project=$PROJECT --format="value(ipAddresses[0].ipAddress)"
    

    复制并保留新副本的专用 IP 地址。

  2. 通过 SSH 连接到堡垒虚拟机。

    gcloud compute ssh $BASTION_VM_NAME \
      --zone=$VM_ZONE \
      --project=$PROJECT
    
  3. 从堡垒虚拟机连接到新副本。

    psql -h NEW_REPLICA_IP -U postgres
    

    NEW_REPLICA_IP 替换为此过程第 1 步 中复制的新 副本的 IP 地址。

  4. 系统提示输入密码时,输入变量 $POSTGRES_PASSWORD

  5. 在新副本 ($PRIMARY_INSTANCE_NAME) 上,删除孤立槽。

    SELECT slot_name, slot_type, temporary, failover, synced, active
    FROM pg_replication_slots
    WHERE slot_name = 'my_subscription';
    

    确认槽存在,且 synced = falseactive = false,然后将其删除。

    SELECT pg_drop_replication_slot('my_subscription');
    

    孤立槽已移除。

自动槽重新同步

删除孤立槽后,新副本 ($PRIMARY_INSTANCE_NAME) 上的 slotsync 工作器会在下一个周期中自动连接到新的主实例 ($DR_REPLICA_NAME)。它会创建一个新的本地 my_subscription 槽,该槽与新主实例的活跃槽同步。

您可以在新副本的 postgres.log 中看到指示成功的消息,类似于以下内容:

LOG: newly created slot "my_subscription" is sync-ready now

新的同步槽具有 failover=true,最终会变为持久性 (temporary=false),确保如果您稍后切换回,此实例已做好准备。

可选:执行切换回

  1. 现在,切换回,使 $PRIMARY_INSTANCE_NAME 再次成为主实例。

    gcloud sql instances switchover $PRIMARY_INSTANCE_NAME \
      --project=$PROJECT
    
  2. 在切换回后执行验证。

    1. 检查订阅者实例的状态。

      SELECT subname, pid IS NOT NULL AS is_active
      FROM pg_stat_subscription;
      

      订阅应仍处于活跃状态,即 is_active = t

    2. 检查新主实例 ($PRIMARY_INSTANCE_NAME) 的槽的状态。

      SELECT slot_name, active FROM pg_replication_slots WHERE slot_type = 'logical';
      SELECT * FROM pg_stat_replication;
      

      槽应处于活跃状态,并且订阅者已连接。

问题排查

问题 问题排查

切换后,新副本(即旧的主实例)上出现错误:

"exiting from slot synchronization because same name slot already exists on the standby"

按照 清理新副本上的孤立复制槽中的步骤操作。