Files
ai_site/docs/数据同步-中间件.md
2026-07-31 10:19:22 +08:00

101 lines
3.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 跨库数据同步中间件
支持 **SQLite ↔ MySQL ↔ Postgres**,不要求两端同一种数据库。变更经 **outbox 队列** 近实时投递;冲突进 **冲突队列**
## 权限与隔离
| 项 | 说明 |
|----|------|
| 谁可配 | 仅公司**顶级权限(管理员)**,权限名「数据同步」 |
| 谁不可 | 编辑 / 只读、智能体账号(即使有「发布模块」) |
| 数据隔离 | 通道与冲突带 `tenant_id`;公司 A 看不到公司 B 的通道/DSN |
| 多服务器 | 同一公司可建多条通道,分别填 B、C 等库的 DSN |
## 典型场景A / B / C
| 端 | 角色 |
|----|------|
| **A** | 线上库(用户增删改) |
| **B** | 本地库(本机业务 + 接收 C |
| **C** | 额外数据源Excel / API / 导入),只写入 **B** |
推荐配置:
1. 建一条通道:`local` = B`remote` = A**方向 `bidirectional`**,冲突策略 `queue`(或 LWW
2. C 的数据用 **ingest API**(或业务直接写 B写入本地触发器进 outbox再推到 A。
3. A 上用户改的数据经 outbox 拉回 B。
4. 怀疑漏数时点 **对账**,或等双向通道约每分钟自动对账。
如何保证**不漏、不多**
| 手段 | 防什么 |
|------|--------|
| 表触发器 → `_ajz_sync_outbox` | 漏(本地/线上变更必入队) |
| 应用远端时 `WithApplying`(触发器不写 outbox | 多A↔B 回声环) |
| 目标 meta 版本相等则跳过 | 多(重复投递) |
| 目标版本更新 → 冲突队列 / LWW | 并发改同一行 |
| 主键对账 reconcile | 漏(存量差、触发器未装前的行) |
| C→B upsert 同主键 | 多(重复灌入) |
```
C ──ingest/写库──► B (local) ◄──bidirectional outbox──► A (remote)
```
## 能力
| 项 | 说明 |
|----|------|
| 方言 | `sqlite` / `mysql` / `postgres` |
| 实时性 | 表触发器写 `_ajz_sync_outbox`worker 默认每 500ms 拉取 |
| 方向 | 本地→线上 / 线上→本地 / **双向**A↔B 场景用这个) |
| 冲突 | `queue`(入队)/ `lww_source` / `lww_target` |
| 对账 | `POST .../reconcile`;双向运行中约每分钟自动一次 |
| 外部源 | `POST .../ingest`C → B再同步到 A |
| 配置 | 控制台「数据同步」页;可改线上 DSN |
配置与冲突持久化:`data/dbsync/channels.json``conflicts.json`Docker`.runtime/dbsync`)。
## 控制台用法
1. 登录 → **数据同步****新建通道**
2. 本地 B`sqlite` + `file:./data/local.db`,表名逗号分隔
3. 线上 A`mysql` + `user:pass@tcp(host:3306)/db?parseTime=true`
4. 方向选 **双向****测试连接****保存****启动**
5. 需要补漏时点 **对账**C 数据走业务写 B 或调用 ingest API
## API需公司顶级权限「数据同步」/ 管理员)
| 方法 | 路径 |
|------|------|
| GET/POST | `/api/v1/admin/sync/channels` |
| GET/PUT/DELETE | `/api/v1/admin/sync/channels/{id}` |
| POST | `/api/v1/admin/sync/test` |
| POST | `/api/v1/admin/sync/channels/{id}/prepare\|start\|stop` |
| POST | `/api/v1/admin/sync/channels/{id}/reconcile` |
| POST | `/api/v1/admin/sync/channels/{id}/ingest` |
| GET | `/api/v1/admin/sync/conflicts` |
| POST | `/api/v1/admin/sync/conflicts/{id}/resolve` |
### ingest 示例
```json
POST /api/v1/admin/sync/channels/{id}/ingest
{
"table": "article",
"source": "excel",
"rows": [
{ "id": "c-001", "title": "来自 C" }
]
}
```
按主键 upsert 写入本地 B触发器入 outboxworker 再推到线上 A。
## 注意
- 两端业务表结构需兼容(同名列);主键默认 `id`,可用 `pk_columns` 覆盖。
- MySQL 需账号有建触发器权限。
- 密钥在 DSN 中;列表页会打码显示。
- 「实时」为亚秒级轮询 + 触发器,非 MySQL binlog CDC同机延迟通常 <1s。
- 对账按**主键集合**补缺行,不做逐字段内容 diff同 PK 内容冲突仍靠版本 / 冲突队列。