14 KiB
基于 FTP 中转的配置双向同步工具设计方案
1. 文档目的
本文档用于说明一套基于 FTP 中转的配置同步工具设计方案,满足以下目标:
- 开发环境定时从 Git 拉取新配置,并通过生产环境
push接口推送到生产 - 生产环境定时从
pull接口拉取新配置,并同步回开发环境 Git - 在开发环境与生产环境不能直接互通时,通过 FTP 服务作为中转通道完成双向同步
2. 已知约束
2.1 技术约束
- JDK:
1.8 - Spring Boot:
2.7.18 - 轻量数据库:
H2或同类开源可商用数据库 - 其他依赖必须为开源可商用组件
2.2 网络与部署约束
- 无法登录 FTP 所在服务器主机
- 只能访问 FTP 服务:
IP + 端口 + 用户名/密码 - 网络拓扑如下:
开发环境 <----> FTP A <----> 生产环境
说明:
- 开发环境可以访问 FTP A
- 生产环境可以访问 FTP A
- 开发与生产不假设可以直接互通
3. 设计原则
- 同一套程序,按不同
profile部署在开发和生产两端 - 通过 FTP 传递标准化同步包,避免环境间直接通信依赖
- 使用本地状态库记录同步任务、检查点、应答信息,保证可追踪、可恢复
- 同步流程必须具备幂等控制,避免重复推送、重复提交
- 开发到生产、生产回开发必须隔离处理,避免双向同步形成死循环
4. 总体方案
推荐采用“双端代理 + FTP 中转 + 本地状态库”架构:
Sync-Agent-Dev:部署在开发环境Sync-Agent-Prod:部署在生产环境FTP A:作为唯一中转通道H2:记录同步状态、任务、检查点、重试信息
整体结构如下:
开发环境
Sync-Agent-Dev
|- 拉取 Git
|- 上传/下载 FTP A
|- 写入 Git
生产环境
Sync-Agent-Prod
|- 调用生产 pull 接口
|- 调用生产 push 接口
|- 上传/下载 FTP A
中转
FTP A
|- dev-to-prod/
|- prod-to-dev/
|- ack/
|- failed/
5. 部署模式
建议只维护一套代码,通过 Spring Profile 控制角色:
dev-agent:启用开发侧能力prod-agent:启用生产侧能力
5.1 开发侧职责
- 定时拉取 Git 指定分支的新配置
- 判断是否存在新的有效版本
- 打包配置并上传到 FTP
- 下载生产侧回传的同步包
- 将生产侧回传配置写入 Git
- 提交并推送到远端仓库
5.2 生产侧职责
- 轮询 FTP,获取开发侧上传的配置包
- 校验后调用生产
push接口导入配置 - 定时调用生产
pull接口拉取最新配置 - 打包并上传回 FTP,供开发侧消费
6. 技术选型
| 分类 | 选型 | 说明 |
|---|---|---|
| 运行时 | JDK 1.8 | 满足约束 |
| 框架 | Spring Boot 2.7.18 | 主体框架 |
| 调度 | Spring Scheduling | 实现定时任务 |
| 重试 | Spring Retry | 失败重试 |
| 数据库 | H2 File Mode | 轻量、嵌入式、可持久化 |
| Git 操作 | JGit | 纯 Java 实现 |
| FTP 操作 | Apache Commons Net | 主流 FTP 客户端 |
| JSON | Jackson | 标准序列化组件 |
| 日志 | SLF4J + Logback | 默认日志能力 |
6.1 数据库模式建议
虽然需求提到“类似 H2 的轻量化内存数据库”,但本场景不建议纯内存模式,原因如下:
- 服务重启后需要保留同步检查点
- 失败任务需要支持补偿和人工追踪
- 需要记录包处理状态,避免重复消费
因此建议使用:
H2 File Mode
即本地文件数据库,仍然轻量,但支持状态持久化。
7. 核心业务流程
系统包含两条主链路。
7.1 链路一:开发 Git -> 生产 push 接口
用途:将开发环境 Git 中的新配置推送到生产环境。
流程如下:
dev-agent定时拉取 Git 指定分支- 判断 Git 最新提交是否为新的有效配置版本
- 将配置目录打包为标准同步包
- 上传至 FTP 路径
dev-to-prod/out/ prod-agent轮询 FTP,发现新包后下载- 校验包完整性、幂等键和来源信息
- 调用生产环境
push接口导入配置 - 成功后生成
ack文件上传到 FTP dev-agent读取ack,将任务状态更新为成功
建议时序图如下:
sequenceDiagram
participant G as Git(开发)
participant D as Sync-Agent-Dev
participant F as FTP A
participant P as Sync-Agent-Prod
participant API as 生产Push接口
D->>G: 定时 pull 配置
D->>D: 检查是否有新版本
D->>D: 打包 zip + manifest
D->>F: 上传 dev-to-prod/out/
P->>F: 轮询并下载新包
P->>P: 校验 hash/traceId
P->>API: 调用 push 接口
API-->>P: 返回处理结果
P->>F: 上传 ack
D->>F: 读取 ack
D->>D: 更新状态为成功
7.2 链路二:生产 pull 接口 -> 开发 Git
用途:将生产环境当前配置回传到开发环境,形成配置镜像或审计记录。
流程如下:
prod-agent定时调用生产pull接口- 将返回配置标准化后计算版本标识或内容哈希
- 如果与上次同步结果不同,则打包上传到 FTP
prod-to-dev/out/ dev-agent轮询 FTP 并下载新包- 解包后写入本地 Git 工作目录
- 提交 commit 并推送到远端 Git
- 成功后写回
ack
建议时序图如下:
sequenceDiagram
participant API as 生产Pull接口
participant P as Sync-Agent-Prod
participant F as FTP A
participant D as Sync-Agent-Dev
participant G as Git(开发)
P->>API: 定时调用 pull 接口
API-->>P: 返回当前配置
P->>P: 标准化并计算 hash
P->>F: 上传 prod-to-dev/out/
D->>F: 轮询并下载新包
D->>D: 解包并写入工作区
D->>G: commit + push
D->>F: 上传 ack
8. 标准同步包设计
为保证跨环境处理一致,建议所有同步内容封装为统一格式的压缩包。
8.1 包结构
package.zip
|- manifest.json
|- config/
|- sha256.txt
8.2 manifest 字段建议
{
"traceId": "uuid",
"direction": "DEV_TO_PROD",
"sourceEnv": "DEV",
"sourceVersion": "gitCommitId",
"contentHash": "sha256",
"createdAt": "2026-04-15T10:00:00+08:00"
}
8.3 字段说明
traceId:本次同步唯一流水号direction:同步方向,例如DEV_TO_PROD、PROD_TO_DEVsourceEnv:来源环境sourceVersion:来源版本号,开发侧通常为 Git Commit IDcontentHash:配置内容哈希,便于判断重复包createdAt:包生成时间
9. FTP 目录规划
建议在 FTP A 上使用如下目录结构:
/dev-to-prod/out/
/dev-to-prod/ack/
/prod-to-dev/out/
/prod-to-dev/ack/
/failed/
目录说明:
/dev-to-prod/out/:开发侧发往生产侧的同步包/dev-to-prod/ack/:生产侧返回的处理应答/prod-to-dev/out/:生产侧发往开发侧的同步包/prod-to-dev/ack/:开发侧返回的处理应答/failed/:失败包归档目录
9.1 上传规范
为避免消费端读取到半截文件,建议采用临时文件上传策略:
- 先上传为
.tmp - 上传完成后重命名为正式
.zip - 消费端只处理
.zip文件
10. Git 分支策略
这是方案中的关键设计点。
不建议将“开发配置推生产”和“生产配置回传开发”写到同一个 Git 分支,否则极易形成循环同步。
建议拆分为两个分支:
config-dev-main:开发主配置分支config-prod-snapshot:生产配置镜像分支
同步规则:
DEV -> PROD只消费config-dev-mainPROD -> DEV只写入config-prod-snapshot
10.1 这样设计的好处
- 避免双向同步形成闭环
- 生产回传配置不会覆盖开发主线
- 便于审计“生产当前实际配置”
10.2 机器人提交标记
建议同步工具在 commit message 中增加固定前缀,例如:
sync(prod->git): traceId=xxx version=xxx
开发侧扫描 Git 时应忽略同步机器人生成的提交,进一步降低环路风险。
11. 本地状态库设计
建议至少建立以下 3 张表。
11.1 sync_checkpoint
用于记录各方向的最后一次成功检查点。
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| direction | varchar | 同步方向 |
| last_success_version | varchar | 最后成功版本 |
| last_success_hash | varchar | 最后成功内容哈希 |
| updated_at | timestamp | 更新时间 |
11.2 sync_task
用于记录每次同步任务生命周期。
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| trace_id | varchar | 流水号 |
| direction | varchar | 同步方向 |
| source_version | varchar | 来源版本 |
| package_name | varchar | 包文件名 |
| status | varchar | 状态 |
| retry_count | int | 重试次数 |
| error_msg | clob | 错误信息 |
| created_at | timestamp | 创建时间 |
| updated_at | timestamp | 更新时间 |
11.3 sync_ack
用于记录应答信息。
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| trace_id | varchar | 流水号 |
| ack_side | varchar | 应答方 |
| ack_status | varchar | 应答状态 |
| ack_time | timestamp | 应答时间 |
| remark | varchar | 备注 |
12. 幂等与一致性设计
12.1 幂等键建议
建议以如下组合作为幂等键:
direction + sourceVersion + contentHash
约束效果:
- 已经处理过的包不能重复推送
- 已经提交过的生产快照不能重复写 Git
12.2 一致性策略
本方案属于跨系统、跨网络的异步同步,不适合做强一致事务。
建议采用:
- “本地落库 + 外部调用 + 最终一致”模式
- 每一步记录状态
- 失败后允许自动重试或人工补偿
13. 失败处理与补偿机制
13.1 自动重试
以下场景建议自动重试:
- FTP 上传失败
- FTP 下载失败
- 生产
push接口调用失败 - 生产
pull接口调用失败 - Git push 失败
建议策略:
- 最大重试次数:
3 ~ 5 - 重试间隔:指数退避,例如
30s / 60s / 120s
13.2 失败归档
连续失败后建议:
- 将包移动到 FTP 的
/failed/ - 将任务状态置为
FAILED - 记录完整错误信息
- 触发告警
13.3 人工补偿
后续可以增加一个管理接口,支持:
- 按
traceId重新执行 - 重置任务状态
- 查看失败原因
14. 安全设计
14.1 传输安全
优先级建议如下:
- 优先使用
FTPS - 如果只能使用普通 FTP,建议对同步包内容做 AES 加密
14.2 凭据管理
以下信息不得写死在代码中:
- FTP 地址、端口、用户名、密码
- Git 用户名、密码或 Token
- 生产接口认证信息
建议通过以下方式外置:
application-*.properties- 环境变量
- 启动参数
14.3 审计日志
建议记录:
- 谁发起了同步
- 同步方向
- 来源版本
- 包名
- 接口调用结果
- 异常原因
15. 项目结构建议
有两种实现方式。
15.1 方案 A:单工程 + Profile 切换
适用于项目规模较小、交付快的场景。
sync-tool
|- src/main/java
| |- config
| |- ftp
| |- git
| |- job
| |- package
| |- repository
| |- service
| |- web
|- src/main/resources
| |- application.properties
| |- application-dev-agent.properties
| |- application-prod-agent.properties
15.2 方案 B:多模块拆分
适用于后续可能演化较多、职责更清晰的场景。
sync-tool
|- common
|- dev-agent
|- prod-agent
当前建议优先采用:
方案 A:单工程 + Profile
理由:
- 实现成本低
- 运维简单
- 早期更适合快速打通链路
16. 核心模块划分
建议按职责拆分以下模块:
GitService- 拉取仓库
- 检查最新提交
- 提交并推送生产回传配置
FtpService- 上传、下载、重命名、目录扫描
PackageService- 生成 zip
- 生成 manifest
- 校验 hash
SyncTaskService- 任务创建
- 状态变更
- 检查点维护
ProdPushService- 调用生产
push接口
- 调用生产
ProdPullService- 调用生产
pull接口
- 调用生产
AckService- 生成和消费 ack 文件
JobScheduler- 各类定时任务调度
17. 定时任务建议
17.1 开发侧任务
GitPullJob- 周期拉取 Git 并检查是否有新配置
UploadDevPackageJob- 将待同步配置上传到 FTP
ConsumeProdPackageJob- 下载生产回传包并写入 Git
AckScanJob- 扫描生产侧 ack 并更新任务状态
17.2 生产侧任务
ConsumeDevPackageJob- 下载开发侧同步包并调用生产
push
- 下载开发侧同步包并调用生产
PullProdConfigJob- 定时调用生产
pull接口
- 定时调用生产
UploadProdPackageJob- 将拉取结果上传到 FTP
AckScanJob- 扫描开发侧 ack 并更新任务状态
18. 一期 MVP 建议
建议按最小可交付版本分阶段实施。
阶段 1:打通主链路
- 建立 Spring Boot 工程
- 集成 H2、JGit、FTP
- 实现开发到生产的全量包同步
- 实现生产
push接口调用
阶段 2:打通回传链路
- 接入生产
pull接口 - 实现生产到开发的 FTP 回传
- 实现开发侧写入 Git 并推送
阶段 3:增强稳定性
- 增加重试
- 增加 ack 机制
- 增加失败归档
- 增加告警与审计日志
19. 风险与注意事项
19.1 最大风险:双向同步闭环
如果生产回传配置写入开发主分支,再被开发侧识别为“新配置”,会再次推送到生产,形成无限循环。
规避措施:
- 使用独立镜像分支
- 识别机器人提交
- 使用幂等键
19.2 配置冲突风险
如果开发和生产都会修改同一份配置,且要求双向合并,则不能简单用文件覆盖方式处理。
当前建议:
- 将生产回传定义为“镜像/审计”
- 不直接回写开发主配置分支
19.3 FTP 能力限制
如果 FTP 不支持原子重命名、目录权限受限或稳定性较差,需要额外做兼容与重试。
20. 结论
在当前网络条件下,推荐采用“开发代理 + 生产代理 + FTP 中转 + H2 状态库”的双端部署方案。
该方案具备以下特点:
- 不依赖开发与生产直接互通
- 满足开发到生产、生产到开发的双向同步需求
- 支持状态记录、失败重试、幂等控制和审计追踪
- 适合使用
Java 1.8 + Spring Boot 2.7.18快速落地
21. 后续可继续细化内容
后续可以基于本方案继续输出:
application.properties配置项设计- H2 建表 SQL
- 核心类图与接口设计
- 各定时任务的时序与状态流转
- Spring Boot 工程骨架