本方案针对跨地域研发场景,基于 CP 原则构建了强一致性同步体系。核心采用“意向锁定”机制,通过数据库原子操作确保全局唯一提交锁(G-Lock),解决并发提交冲突。
1. 物理拓扑图
架构组件说明
1. 研发中心节点(Regional R&D Centers)
- Local SVN Instance: 存储本地代码镜像,通过
pre-commit和post-commit钩子脚本触发同步逻辑。 - Sync Agent (同步代理) :
-
- 通信职责:作为客户端通过 HTTPS (RESTful API) 与中央仲裁服务交互。
- 协议选型:推荐使用 RESTful API,其在跨地域、高延迟网络(200ms+)下具有更好的兼容性,且易于穿透企业级防火墙。
- 版本控制:执行
svnlook youngest获取本地物理版本,并执行svnrdump进行增量数据的导出与导入。
2. 网络安全边界(Firewall / DMZ)
- 网络隔离带:各研发中心与中央仲裁中心之间存在严格的网络隔离。
- 加密传输 (TLS) :所有跨地域流量必须经过 TLS 1.2/1.3 加密,确保代码增量包在公网或专线传输过程中的安全性。
- 策略配置:防火墙仅放行 Agent 所在 IP 段对中央仲裁服务 443 端口的访问。
3. 中央仲裁中心(Central Arbitration Center)
- Metadata DB (MySQL/PostgreSQL) :
-
- 存储全局版本号
group_revision、意向版本intent_revision及分布式锁信息。 - 记录
sync_task同步任务列表及各节点的版本存证current_revision。
- 存储全局版本号
| 交互动作 | 发起方 | 接收方 | 协议 | 主要数据 |
|---|---|---|---|---|
| 请求锁 (Pre-commit) | Agent | 仲裁服务 | REST/HTTPS | node_id, repo_id, local_rev |
| 确认提交 (Post-commit) | Agent | 仲裁服务 | REST/HTTPS | new_rev, task_trigger=true |
| 数据同步 (Sync) | 从节点 Agent | 主节点 Agent | SVN/HTTPS | svnrdump流式增量数据 |
| 状态巡检 (Anti-Entropy) | Agent | 仲裁服务 | REST/HTTPS | heartbeat, svnlook_rev |
2. 核心实体设计
2.1 SVN 仓库节点 (svn_repo)
| 属性 | 类型 | 说明 |
|---|---|---|
| repo_id | BIGINT | 全局唯一 ID,原子生成。 |
| group_id | BIGINT | 所属分布式群组 ID。 |
| repo_name | VARCHAR | 节点名称。 |
| repo_url | VARCHAR | 本地 SVN 访问地址。 |
| current_revision | BIGINT | 节点实际达到的版本号(用于追赶对齐)。 |
| repo_status | ENUM | ONLINE / OFFLINE。 |
| agent_addr | VARCHAR | 同步代理地址 (IP:Port),执行 svnrdump任务。 |
2.2 分布式群组 (svn_group)
| 属性 | 类型 | 说明 |
|---|---|---|
| group_id | BIGINT | 全局唯一群组 ID。 |
| group_revision | BIGINT | 已确认的全局最高版本号 (Source of Truth)。 |
| intent_revision | BIGINT | 预分配版本号:pre-commit时写入,用于解决断电冲突。 |
| lock_status | ENUM | 0(无占用), 1(锁定中,存在未确认意向)。 |
| lock_holder_id | BIGINT | 当前持有提交锁的节点 ID。 |
| lock_expire_at | DATETIME | 租约到期时间,用于超时自动处理。 |
| status | ENUM | 0-READY, 1-BROKEN(需要人工介入)。 |
2.3 同步任务 (sync_task)
| 属性 | 类型 | 说明 |
|---|---|---|
| task_id | BIGINT | 任务唯一标识。 |
| target_repo_id | BIGINT | 待同步的目标节点 ID。 |
| rev_range | VARCHAR | 版本区间(如 r100:r101)。 |
| status | ENUM | 0-CREATED, 1-RUNNING, 2-SUCCESS, 3-FAILED。 |
2.4 数据库建表 SQL
CREATE TABLE `svn_group` (
`group_id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`group_name` VARCHAR(100) NOT NULL,
`group_revision` BIGINT DEFAULT 0,
`intent_revision` BIGINT DEFAULT NULL COMMENT '预分配待确认的版本号',
`lock_status` ENUM('CLEAN', 'DIRTY') DEFAULT 'CLEAN',
`lock_holder_id` BIGINT DEFAULT NULL,
`lock_expire_at` DATETIME(3) DEFAULT NULL,
`status` ENUM('READY', 'SYNCING', 'BROKEN') DEFAULT 'READY',
`update_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB;
CREATE TABLE `svn_repo` (
`repo_id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`group_id` BIGINT NOT NULL,
`repo_name` VARCHAR(100) NOT NULL,
`repo_url` VARCHAR(255) NOT NULL,
`current_revision` BIGINT DEFAULT 0,
`repo_status` ENUM('ONLINE', 'OFFLINE') DEFAULT 'ONLINE',
`agent_addr` VARCHAR(50),
FOREIGN KEY (`group_id`) REFERENCES `svn_group`(`group_id`)
) ENGINE=InnoDB;
CREATE TABLE `sync_task` (
`task_id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`target_repo_id` BIGINT NOT NULL,
`from_rev` BIGINT NOT NULL,
`to_rev` BIGINT NOT NULL,
`status` ENUM('CREATED', 'RUNNING', 'SUCCESS', 'FAILED') DEFAULT 'CREATED',
`retry_count` INT DEFAULT 0,
`create_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB;
3. 核心流程与极端场景自愈设计
状态迁移和决策矩阵
| 场景 | 本地状态 (svnlook) | 数据库状态 (group_rev / lock_status) | 持有者 (lock_holder) | Agent 决策动作 |
|---|---|---|---|---|
| 1. 正常提交前 | / CLEAN | 无 | 抢占锁:将状态改为 DIRTY,记录 intent_rev=N+1。 | |
| 2. 正常提交后 | / DIRTY | 自己 | 释放锁:推进 group_rev=N+1,状态改为 CLEAN。 | |
| 3. 正常同步后 | / CLEAN | 无 | 追赶:执行 svnrdump load,将本地更新至 。 | |
| 4. 提交中途断电 | / DIRTY | 自己 | 自愈确认:重启后发现 svnlook已达意向版本,强制上报确认。 | |
| 5. 抢锁时冲突 | / DIRTY | 他人 | 等待/报错:当前有其他 Master 正在操作,禁止本地提交。 | |
| 6. 抢锁时落后 | / CLEAN | 无 | 拒绝:提示“请先执行 svn update”,本地版本已落后。 |
自身 == 群组 ,但锁被占用的三种情况:
1. 毫秒级的并发竞争 (Race Condition)
这是最常见的正常业务场景。
- 过程:
-
- 节点 A 和 节点 B 几乎同时发起了
svn commit。 - 此时数据库中
group_revision是 ,lock_status是CLEAN。 - 节点 A 的请求早到了 1 毫秒,成功执行了
UPDATE语句,将锁抢占,此时lock_status变为DIRTY,lock_holder变为节点 A。 - 紧接着 1 毫秒后,节点 B 的请求到达。此时节点 B 本地的版本确实还是 (与群组一致),但它看到的锁已经是
DIRTY了。
- 节点 A 和 节点 B 几乎同时发起了
- 结论:这说明别人抢先了一步,你必须等待。
2. 占坑成功但尚未物理写入 (The Gap)
在分布式 SVN 流程中,从“抢锁成功”到“物理写入成功”之间存在一个时间差。
- 过程:
-
- 节点 A 已经抢锁成功(
lock_status=DIRTY),此时群组版本还是 。 - 节点 A 正在执行本地的
svn commit磁盘写入(可能需要 2-5 秒)。 - 此时你(节点 B)来查询。你会发现群组版本确实是 ,和你一样,但节点 A 已经把坑占住了,正在准备变成 。
- 节点 A 已经抢锁成功(
- 结论:这种状态是**意向锁定(Intent-to-Commit)**的正常表现,保证了全局不会有两个节点同时尝试生成 。
3. “僵尸锁”残留 (Zombie Lock)
这属于异常兜底场景。
- 过程:
-
- 节点 A 抢锁成功,准备提交 。
- 突然:节点 A 还没来得及写 SVN 磁盘,整个服务器就直接断电宕机了。
- 此时,数据库里留下的记录是:
group_rev=100,lock_status=DIRTY,lock_holder=Node_A。 - 由于节点 A 根本没写成功,它的本地版本和群组版本依然都是 。
- 结论:如果你在节点 A 挂掉期间来抢锁,就会遇到这种情况。此时需要依赖
lock_expire_at(租约过期) 机制来清理这个僵尸锁。
| 判定结果 | 处理建议 |
|---|---|
若 lock_expire_at未过期 | 排队等待。说明 Master 正在忙碌,建议每隔 1-2 秒重试一次。 |
若 lock_expire_at已过期 | 触发自愈/报警。说明 Master 可能挂了。如果是你本人,则尝试重置;如果是别人,则提示管理员介入或由系统置为 BROKEN。 |
3.1 意向锁定提交流程 (Standard Workflow)
- Pre-commit (准备阶段) :
-
- 节点 A 向仲裁中心请求锁。
- 仲裁中心执行原子操作:
SQL
UPDATE svn_group SET
lock_status = 'DIRTY',
lock_holder_id = Node_A_ID,
intent_revision = group_revision + 1,
lock_expire_at = NOW() + INTERVAL 30 SECOND
WHERE lock_status = 'CLEAN' AND group_revision = Node_A_Local_Rev;
-
- 只有上述更新成功,仲裁中心才返回“允许提交”。
- Local Commit (执行阶段) :
-
- 节点 A 在本地 SVN 库执行真实写入。
- Post-commit (确认阶段) :
-
- 节点 A 成功后调用确认接口。
- 仲裁中心执行事务更新:
-
-
- 将
group_revision更新为intent_revision。 - 将
lock_status重置为CLEAN,释放锁。 - 生成其他节点的
sync_task记录。
- 将
-
3.2 针对“主节点提交成功后断电”的兜底设计
- 现象:节点 A 本地 SVN 版本已变 ,但数据库
group_revision仍为 ,lock_status为DIRTY。 - 自愈逻辑:
-
- 锁竞争拦截:当节点 B 尝试申请锁时,仲裁中心发现
lock_status == 'DIRTY'。 - 状态探测:仲裁中心检查
lock_expire_at。若已过期,尝试连接节点 A 的 Agent。
- 锁竞争拦截:当节点 B 尝试申请锁时,仲裁中心发现
-
-
- 若 Node A 在线且已更新:Agent 上报本地版本为 ,仲裁中心自动补填
group_revision为 ,释放脏锁。 - 若 Node A 离线:系统将群组状态设为
BROKEN并封锁所有提交。
- 若 Node A 在线且已更新:Agent 上报本地版本为 ,仲裁中心自动补填
-
-
- 强一致性保障(CP) :禁止在脏锁未清除的情况下由其他节点产生新的版本。这确保了绝不会出现两个节点同时拥有不同 版本的情况。
3.3 弱网环境下的同步补偿
- 指数退避重试:
sync_task失败后按 1min, 5min, 15min 间隔重试。 - 版本拉取对齐:每个节点的 Agent 定时检查
local_revision与 DB 的group_revision。若落后,主动通过svnrdump load从 master 节点拉取增量包,不依赖post-commit的单次触发。
4. 设计总结
本方案通过将 SVN 的外部操作耦合到数据库的“意向状态”中,解决了分布式系统中“外部执行结果”与“中心元数据”状态不一致的问题。
- 断电保护:
intent_revision记录了正在进行的动作,重启后可对齐。 - 弱网稳定性:
sync_task的持久化保证了只要网络恢复,增量包终将送达。 - 绝对一致性:
DIRTY锁状态牺牲了节点 A 宕机期间的可用性(Availability),换取了安防行业最看重的代码数据绝对强一致性(Consistency)。