跳转至

Ch 32 跨账号批量同步:双桶桥接架构

面包屑

本书主页Part V 平台演进 › Ch 32

项目第 2-3 年 · 扩展与迁移期——跨账号安全同步


本章你将学到

  • 跨账号 Redshift 数据同步的难题与约束
  • 双桶桥接架构设计:源卸载→源桶→桥接→目标桶→目标加载
  • 三层凭证模型:最小权限与跨账号安全
  • DDL 自动克隆与执行通道双模式设计

迁移完 SQL Server(Ch 31)后,又一个没想到的挑战出现了:Aurora 内部有多个 AWS 账号(不同业务线的独立账号),需要把账号 A 的 Redshift 数据批量同步到账号 B 的 Redshift。

"同步数据嘛,UNLOAD 出来再 COPY 进去不就行了?"——我一开始也这么想。但越看越不是那么回事:Redshift 的 UNLOAD 只能写同账号的 S3 桶,不能直接卸载到对方账号的桶。给 Redshift 集群跨账号 S3 权限?安全团队第一个不答应——集群一旦被入侵,影响面就跨账号了。

这个问题我们纠结了一周。有次白板讨论上,"双桶桥接"的方案成型——不是最简单的方案,但是最安全的。这一章就讲这个设计过程。


32.1 跨账号数据同步的难题与约束

场景:Aurora 有两个 AWS 账号(如不同业务线的独立账号),需要把账号 A 的 Redshift 数据批量同步到账号 B 的 Redshift。

%%{init: {'theme':'base','themeVariables':{'primaryColor':'#edf5ff','primaryTextColor':'#161616','primaryBorderColor':'#0f62fe','lineColor':'#697077','secondaryColor':'#d9fbfb','tertiaryColor':'#f2f4f8','fontSize':'14px'}}}%%
flowchart LR
 subgraph 难题["跨账号同步难题"]
 P1[ Redshift UNLOAD 只能写同账号 S3<br/>不能直接卸载到对方账号桶]
 P2[ 不想给 Redshift 跨账号 S3 权限<br/>安全风险大]
 P3[ 不想用 STS AssumeRole<br/>凭证管理复杂]
 P4[ 需要表结构自动克隆<br/>避免手动建表]
 P5[ 需要断点续传和幂等性]
 end
classDef bpProcess fill:#edf5ff,stroke:#0f62fe,stroke-width:2px,color:#161616
class P1,P2,P3,P4,P5 bpProcess
linkStyle default stroke:#697077,stroke-width:2px

图 32-1 跨账号数据同步的难题与约束

Trade-off

最简单的方案是"给账号 A 的 Redshift 一个跨账号 S3 权限,直接 UNLOAD 到账号 B 的桶"。但这违背了最小权限原则——Redshift 集群获得了跨账号写入能力,一旦被入侵,影响面巨大。我们选择了更安全但更复杂的"双桶桥接"方案。


32.2 双桶架构设计:源卸载→源桶→桥接→目标桶→目标加载

C4Container
 title 双桶桥接跨账号同步架构 — Container Diagram

 Container_Boundary(acct_a, "AWS 账号 A(源)", "源数据所在 AWS 账号——Redshift 只有同账号 S3 权限") {
 ContainerDb(rs_a, "Redshift A", "Redshift Provisioned", "源数据仓库:持有完整表结构和业务数据,通过 UNLOAD 将数据导出为 Parquet")
 ContainerDb(bucket_a, "源 S3 桶", "S3 Standard", "同账号 S3 桶:Redshift A 的 UNLOAD 目标——Redshift 无需跨账号权限即可写入")
 Container(unload, "UNLOAD 作业", "Glue Python Shell", "编排 UNLOAD SQL:选择表→分区 UNLOAD→生成 Parquet 到源桶。支持断点续传和并行分区导出")
 }

 Container_Boundary(bridge, "跨账号桥接层", "安全域边界——只有桥接程序持有双账号凭证,Redshift 两端均无跨账号权限") {
 Container(rclone, "rclone 桥接程序", "rclone + EC2/Glue", "从源桶增量同步 Parquet 到目标桶:支持断点续传、增量校验、大文件分片、带宽限制")
 Container(cred_mgr, "凭证管理", "Secrets Manager", "集中存储双账号 AK/SK:源桶只读凭证 + 目标桶只写凭证。自动轮转,日志脱敏。最小权限原则")
 }

 Container_Boundary(acct_b, "AWS 账号 B(目标)", "目标 AWS 账号——Redshift 从同账号 S3 桶 COPY 加载") {
 ContainerDb(bucket_b, "目标 S3 桶", "S3 Standard", "同账号 S3 桶:桥接程序写入 Parquet 后,Redshift B 通过 COPY 命令加载到目标表")
 ContainerDb(rs_b, "Redshift B", "Redshift Provisioned", "目标数据仓库:表结构由 DDL 自动克隆(读取源 DDL→重写标识符→依赖排序→幂等执行)")
 Container(copy_job, "COPY 作业", "Glue Python Shell", "编排 COPY SQL:从目标桶加载 Parquet 到 Redshift B→校验行数→记录元数据")
 Container(ddl_clone, "DDL 克隆", "Glue Python Shell", "自动读取源表结构→标识符重写→外键依赖排序→在目标执行 CREATE TABLE")
 }

 Rel(unload, rs_a, "执行 UNLOAD:按分区导出表数据为 Parquet", "Redshift SQL (JDBC)")
 Rel(unload, bucket_a, "写入 Parquet 文件到同账号 S3 桶", "S3 PUT (同账号 IAM Role)")
 Rel(rclone, bucket_a, "读取 Parquet 文件——增量比对已同步文件", "S3 GET (跨账号 AK/SK)")
 Rel(rclone, bucket_b, "写入 Parquet 文件——断点续传支持分片上传", "S3 PUT (跨账号 AK/SK)")
 Rel(rclone, cred_mgr, "获取双账号临时凭证——源只读/目标只写", "Secrets Manager API")
 Rel(copy_job, bucket_b, "读取 Parquet——校验文件完整性和行数", "S3 GET (同账号 IAM Role)")
 Rel(copy_job, rs_b, "COPY 加载——从 Parquet 写入目标表", "Redshift SQL (JDBC)")
 Rel(ddl_clone, rs_a, "读取源表 DDL——pg_catalog / SVV_TABLE_INFO", "Redshift SQL (JDBC)")
 Rel(ddl_clone, rs_b, "在目标执行 CREATE TABLE——依赖排序后幂等执行", "Redshift SQL (JDBC)")

 UpdateElementStyle(rs_a, $bgColor="#d9fbfb", $fontColor="#161616", $borderColor="#007d79")
 UpdateElementStyle(bucket_a, $bgColor="#d9fbfb", $fontColor="#161616", $borderColor="#007d79")
 UpdateElementStyle(unload, $bgColor="#edf5ff", $fontColor="#161616", $borderColor="#0f62fe")
 UpdateElementStyle(rclone, $bgColor="#fcf4d6", $fontColor="#161616", $borderColor="#f1c21b")
 UpdateElementStyle(cred_mgr, $bgColor="#f6f2ff", $fontColor="#161616", $borderColor="#8a3ffc")
 UpdateElementStyle(bucket_b, $bgColor="#d9fbfb", $fontColor="#161616", $borderColor="#007d79")
 UpdateElementStyle(rs_b, $bgColor="#d9fbfb", $fontColor="#161616", $borderColor="#007d79")
 UpdateElementStyle(copy_job, $bgColor="#edf5ff", $fontColor="#161616", $borderColor="#0f62fe")
 UpdateElementStyle(ddl_clone, $bgColor="#defbe6", $fontColor="#161616", $borderColor="#198038")

 UpdateRelStyle(unload, rs_a, $textColor="#007d79", $lineColor="#007d79")
 UpdateRelStyle(rclone, bucket_a, $textColor="#f1c21b", $lineColor="#f1c21b")
 UpdateRelStyle(rclone, bucket_b, $textColor="#f1c21b", $lineColor="#f1c21b")
 UpdateRelStyle(rclone, cred_mgr, $textColor="#8a3ffc", $lineColor="#8a3ffc")

 UpdateLayoutConfig($c4ShapeInRow="5", $c4BoundaryInRow="2")

图 32-2 双桶架构设计:源卸载→源桶→桥接→目标桶→目标加载

数据流详解

步骤 操作 账号 关键点
① UNLOAD Redshift A 卸载数据到同账号 S3 桶 A Redshift 无需跨账号权限
② 桥接复制 rclone 从源桶复制到目标桶 A→B 桥接程序持双账号凭证
③ COPY Redshift B 从同账号 S3 桶加载数据 B Redshift 无需跨账号权限

表 32-1 数据流详解

跨账号同步时序

C4Dynamic
 title 双桶桥接 — Dynamic Diagram (UNLOAD → rclone → COPY 时序)

 ContainerDb(rs_a, "Redshift A", "Redshift", "源数据仓库")
 ContainerDb(bucket_a, "源 S3 桶", "S3", "同账号写入")
 Container(rclone, "rclone 桥接", "rclone", "跨账号复制引擎")
 ContainerDb(bucket_b, "目标 S3 桶", "S3", "桥接写入目标")
 ContainerDb(rs_b, "Redshift B", "Redshift", "目标数据仓库")

 RelIndex(1, rs_a, bucket_a, "1. UNLOAD:Redshift A 导出 Parquet 到同账号源 S3 桶", "Redshift SQL → S3 PUT")
 RelIndex(2, bucket_a, rclone, "2. rclone 从源桶跨账号读取 Parquet 文件列表", "S3 GET (跨账号 AK/SK)")
 RelIndex(3, rclone, bucket_b, "3. rclone 跨账号写入 Parquet 到目标 S3 桶", "S3 PUT (跨账号 AK/SK)")
 RelIndex(4, bucket_b, rs_b, "4. COPY:Redshift B 从同账号目标 S3 桶加载 Parquet", "S3 GET → Redshift COPY")
 RelIndex(5, rs_b, bucket_b, "5. 校验:比对源/目标行数 → 记录元数据", "Redshift SQL 校验")

 UpdateElementStyle(rs_a, $bgColor="#d9fbfb", $fontColor="#161616", $borderColor="#007d79")
 UpdateElementStyle(bucket_a, $bgColor="#d9fbfb", $fontColor="#161616", $borderColor="#007d79")
 UpdateElementStyle(rclone, $bgColor="#fcf4d6", $fontColor="#161616", $borderColor="#f1c21b")
 UpdateElementStyle(bucket_b, $bgColor="#d9fbfb", $fontColor="#161616", $borderColor="#007d79")
 UpdateElementStyle(rs_b, $bgColor="#d9fbfb", $fontColor="#161616", $borderColor="#007d79")

 UpdateRelStyle(bucket_a, rclone, $textColor="#f1c21b", $lineColor="#f1c21b")
 UpdateRelStyle(rclone, bucket_b, $textColor="#f1c21b", $lineColor="#f1c21b")

 UpdateLayoutConfig($c4ShapeInRow="4", $c4BoundaryInRow="2")

图 32-3 跨账号同步时序

为什么用 rclone 做桥接

%%{init: {'theme':'base','themeVariables':{'primaryColor':'#edf5ff','primaryTextColor':'#161616','primaryBorderColor':'#0f62fe','lineColor':'#697077','secondaryColor':'#d9fbfb','tertiaryColor':'#f2f4f8','fontSize':'14px'}}}%%
flowchart TB
 subgraph rclone桥接的优势["rclone 桥接的优势"]
 R1@{ icon: "logos:aws-redshift", form: "rounded", label:  Redshift 不需跨账号权限<br/>安全隔离, pos: "b", h: 40 }
 R2[ rclone 支持增量同步<br/>只传变化文件]
 R3[ rclone 支持断点续传<br/>大表可分批传]
 R4@{ icon: "logos:aws-redshift", form: "rounded", label:  桥接程序集中管控凭证<br/>Redshift 无感知, pos: "b", h: 40 }
 end
classDef bpProcess fill:#edf5ff,stroke:#0f62fe,stroke-width:2px,color:#161616
class R1,R2,R3,R4,Redshift,rclone bpProcess
linkStyle default stroke:#697077,stroke-width:2px

图 32-4 为什么用 rclone 做桥接

引申

双桶架构的思路是在两个账号之间插入一个中转层——源和目标都不需要跨账号权限,只有桥接程序持有双账号凭证。有点像国际贸易里的保税仓:出口方和进口方不直接交易,通过中间仓中转。安全性和解耦性都更好。


32.3 三层凭证模型:最小权限与跨账号安全

%%{init: {'theme':'base','themeVariables':{'primaryColor':'#edf5ff','primaryTextColor':'#161616','primaryBorderColor':'#0f62fe','lineColor':'#697077','secondaryColor':'#d9fbfb','tertiaryColor':'#f2f4f8','fontSize':'14px'}}}%%
flowchart TB
 subgraph 三层凭证["三层凭证模型"]
 L1@{ icon: "logos:aws-redshift", form: "rounded", label: 第一层:Redshift IAM Role<br/>用于 UNLOAD/COPY<br/>仅同账号 S3 权限, pos: "b", h: 40 }
 L2@{ icon: "logos:aws-secrets-manager", form: "rounded", label: 第二层:数据库凭证<br/>用于连接 Redshift<br/>存 Secrets Manager, pos: "b", h: 40 }
 L3@{ icon: "logos:aws-s3", form: "rounded", label: 第三层:计算凭证(AK/SK)<br/>用于 rclone 跨账号复制<br/>最小 S3 权限, pos: "b", h: 40 }
 end

 L1 --> RS[Redshift 集群]
 L2 --> RS
 L3 --> RCLONE[rclone 桥接程序]
classDef bpProcess fill:#edf5ff,stroke:#0f62fe,stroke-width:2px,color:#161616
class IAM,L1,L2,L3,Manager,RCLONE,RS,Redshift,Role,S3,Secrets,UNLOAD,rclone bpProcess
linkStyle default stroke:#697077,stroke-width:2px

图 32-5 三层凭证模型:最小权限与跨账号安全

凭证层 用途 权限范围 存储位置
Redshift IAM Role UNLOAD/COPY 到同账号 S3 仅同账号 S3 读写 IAM Role
数据库凭证 连接 Redshift 执行 SQL 数据库级权限 Secrets Manager
计算凭证(AK/SK) rclone 跨账号复制 双账号 S3 只读/只写 Secrets Manager

表 32-2 三层凭证模型:最小权限与跨账号安全

安全设计原则

%%{init: {'theme':'base','themeVariables':{'primaryColor':'#edf5ff','primaryTextColor':'#161616','primaryBorderColor':'#0f62fe','lineColor':'#697077','secondaryColor':'#d9fbfb','tertiaryColor':'#f2f4f8','fontSize':'14px'}}}%%
flowchart LR
 subgraph 安全原则["三层凭证安全原则"]
 S1@{ icon: "logos:aws-iam", form: "rounded", label:  从不使用 STS AssumeRole<br/>避免临时凭证管理, pos: "b", h: 40 }
 S2@{ icon: "logos:aws-redshift", form: "rounded", label:  每层最小权限<br/>Redshift 不能跨账号, pos: "b", h: 40 }
 S3@{ icon: "logos:aws-secrets-manager", form: "rounded", label:  凭证集中管理<br/>全部存 Secrets Manager, pos: "b", h: 40 }
 S4@{ icon: "codicon:shield", form: "rounded", label:  日志脱敏<br/>密钥/密码自动 REDACTED, pos: "b", h: 40 }
 end
classDef bpProcess fill:#edf5ff,stroke:#0f62fe,stroke-width:2px,color:#161616
class S1,S2,S3,S4 bpProcess
linkStyle default stroke:#697077,stroke-width:2px

图 32-6 安全设计原则

Trade-off

三层凭证比"一个 Role 管所有"复杂不少,但安全性提升是实在的。Redshift 集群始终只有同账号权限——即使被入侵,攻击者也没法直接访问对方账号。桥接程序的 AK/SK 权限最小化(只读写特定桶),日志中自动脱敏。


32.4 DDL 自动克隆与结构迁移

%%{init: {'theme':'base','themeVariables':{'primaryColor':'#edf5ff','primaryTextColor':'#161616','primaryBorderColor':'#0f62fe','lineColor':'#697077','secondaryColor':'#d9fbfb','tertiaryColor':'#f2f4f8','fontSize':'14px'}}}%%
flowchart LR
 subgraph DDL克隆["DDL 自动克隆流程"]
 READ[读取源表 DDL] --> TRANSFORM[DDL 转换<br/>重写标识符/适配目标]
 TRANSFORM --> EXEC[在目标执行 DDL<br/>创建同结构表]
 end
classDef bpProcess fill:#edf5ff,stroke:#0f62fe,stroke-width:2px,color:#161616
class DDL,EXEC,READ,TRANSFORM bpProcess
linkStyle default stroke:#697077,stroke-width:2px

图 32-7 DDL 自动克隆与结构迁移

设计要点 说明
源 DDL 读取 通过系统视图查询源表完整 DDL
标识符重写 按规则重写表名/schema 名(如加环境前缀)
外键排序 有外键依赖的表按依赖顺序创建(被引用表先建)
幂等性 目标表已存在则跳过或重建(按配置)

表 32-3 DDL 自动克隆与结构迁移

引申

DDL 克隆的难点不是"读取 DDL",是"处理依赖关系"。外键约束要求被引用表先于引用表创建。平台通过依赖排序策略(Ch 26 介绍过类似思路)自动计算创建顺序,避免外键冲突。

外键依赖排序这个需求,我第一次跑 DDL 克隆时踩了坑才意识到。当时让脚本按字母序创建表——dim_hospitalfact_prescription 前面创建,看起来没问题。但 fact_prescription 有外键引用 dim_doctor,而 dim_doctor 按字母序排在 dim_hospital 之后、fact_prescription 之前——创建 fact_prescriptiondim_doctor 还不存在,外键约束失败了。从那以后我把依赖排序改成了拓扑排序——读取所有外键约束,按依赖图拓扑排序,无依赖的表可并行创建。这个拓扑排序逻辑和 Ch 26 的表加载顺序排序完全一样——依赖排序是通用模式,不管是 DDL 创建还是数据加载


32.5 执行通道双模式设计:直连 vs 异步 API

%%{init: {'theme':'base','themeVariables':{'primaryColor':'#edf5ff','primaryTextColor':'#161616','primaryBorderColor':'#0f62fe','lineColor':'#697077','secondaryColor':'#d9fbfb','tertiaryColor':'#f2f4f8','fontSize':'14px'}}}%%
flowchart TB
 SQL[执行 SQL] --> MODE{执行模式}
 MODE -->|直连模式|DIRECT[数据库驱动直连<br/>同步执行<br/>适合短查询]
 MODE -->|异步 API|API[Redshift Data API<br/>异步执行<br/>适合长查询]

 DIRECT -->|超时风险|WARN[长查询可能超时]
 API -->|重试机制|RETRY[两层重试<br/>查询重试 + 结果等待重试]
classDef bpProcess fill:#edf5ff,stroke:#0f62fe,stroke-width:2px,color:#161616
class API,DIRECT,Data,MODE,RETRY,SQL,WARN bpProcess
linkStyle default stroke:#697077,stroke-width:2px

图 32-8 执行通道双模式设计:直连 vs 异步 API

模式 机制 适合场景 风险
直连模式 数据库驱动同步连接 短查询(DDL/小表 COPY) 长查询超时断连
异步 API Redshift Data API 异步提交 长查询(大表 UNLOAD/COPY) 需轮询结果

表 32-4 执行通道双模式设计:直连 vs 异步 API

双模式的自动选择

%%{init: {'theme':'base','themeVariables':{'primaryColor':'#edf5ff','primaryTextColor':'#161616','primaryBorderColor':'#0f62fe','lineColor':'#697077','secondaryColor':'#d9fbfb','tertiaryColor':'#f2f4f8','fontSize':'14px'}}}%%
flowchart TD
 Q1{预计执行时间?}
 Q1 -->|<5 分钟|DIRECT[直连模式]
 Q1 -->|>5 分钟|API[异步 API 模式]
 Q1 -->|不确定|API[默认异步 API<br/>更安全]
classDef bpProcess fill:#edf5ff,stroke:#0f62fe,stroke-width:2px,color:#161616
class API,DIRECT,Q1 bpProcess
linkStyle default stroke:#697077,stroke-width:2px

图 32-9 双模式的自动选择

Trade-off

直连模式简单直接,但长查询可能因网络超时断连——10TB 级 UNLOAD 可能跑数小时,直连几乎必然超时。异步 API 模式无超时风险,但多了"提交→轮询→获取结果"这一层复杂度。我们的设计是"默认异步 API,短查询可选直连"。


本章小结

  • 跨账号同步难题:Redshift 不能直接跨账号 UNLOAD;不想给集群跨账号权限
  • 双桶桥接架构:源 UNLOAD→源桶→rclone 复制→目标桶→目标 COPY——Redshift 始终无跨账号权限
  • 三层凭证模型:Redshift IAM Role(同账号 S3)+ DB 凭证 + 计算 AK/SK(跨账号)——从不 AssumeRole
  • DDL 自动克隆:读取源 DDL→标识符重写→依赖排序→幂等执行
  • 执行双模式:直连(短查询)vs 异步 API(长查询,两层重试)——默认异步更安全

下一章

Ch 33 自研 DAG 调度器与任务编排 —— 跨账号同步涉及复杂任务依赖,接下来看平台为什么自研了一个轻量 DAG 调度器。

评论