|
@@ -0,0 +1,385 @@
|
|
|
|
|
+# 数据源连接池与凭据安全设计
|
|
|
|
|
+
|
|
|
|
|
+日期:2026-07-18
|
|
|
|
|
+状态:已完成讨论,等待书面规格审阅
|
|
|
|
|
+
|
|
|
|
|
+## 1. 目标
|
|
|
|
|
+
|
|
|
|
|
+为 DataOps 平台接入的外部业务数据源建立统一、安全、可观测的连接池框架。
|
|
|
|
|
+第一阶段走通 PostgreSQL 和 MySQL,部署边界为单台服务器、单个后端实例,
|
|
|
|
|
+并为后续多副本部署保留配置与接口扩展能力。
|
|
|
|
|
+
|
|
|
|
|
+本设计明确区分两类数据库连接:
|
|
|
|
|
+
|
|
|
|
|
+- 平台控制库:DataOps 自身使用的 PostgreSQL,保存用户、订单、流程版本、
|
|
|
|
|
+ 工作台、知识库等平台数据。继续由 Flask-SQLAlchemy 管理,不建设额外的
|
|
|
|
|
+ 数据源连接池管理层。
|
|
|
|
|
+- 业务数据源:通过 `DataSource` 定义接入的外部数据库。元数据采集、数据预览、
|
|
|
|
|
+ 质量检查和 DataFlow 执行通过本设计的连接池管理器访问。
|
|
|
|
|
+
|
|
|
|
|
+## 2. 范围
|
|
|
|
|
+
|
|
|
|
|
+### 2.1 第一阶段范围
|
|
|
|
|
+
|
|
|
|
|
+- PostgreSQL 数据源,驱动为 `postgresql+psycopg2`。
|
|
|
|
|
+- MySQL 数据源,驱动为 `mysql+pymysql`。
|
|
|
|
|
+- 按数据源延迟创建和复用连接池。
|
|
|
|
|
+- 空闲池回收、配置变更失效、删除时排空。
|
|
|
|
|
+- 数据源凭据加密保存和历史明文凭据迁移。
|
|
|
|
|
+- 故障隔离、熔断、指标和管理员诊断。
|
|
|
|
|
+- 全本地隔离 Docker 验收环境。
|
|
|
|
|
+
|
|
|
|
|
+### 2.2 暂不进入第一阶段
|
|
|
|
|
+
|
|
|
|
|
+- 多后端副本间的全局连接预算和共享状态。
|
|
|
|
|
+- PgBouncer 或其他外部连接代理。
|
|
|
|
|
+- Oracle、SQL Server、DB2、Sybase 等驱动的正式验收。
|
|
|
|
|
+- 跨实例分布式连接池、分布式锁或集中式连接代理。
|
|
|
|
|
+- 任意 SQLAlchemy driver name 和任意 `connect_args` 透传。
|
|
|
|
|
+
|
|
|
|
|
+## 3. 总体架构
|
|
|
|
|
+
|
|
|
|
|
+```text
|
|
|
|
|
+平台控制面
|
|
|
|
|
+Flask-SQLAlchemy ──> 平台 PostgreSQL
|
|
|
|
|
+仅使用框架连接管理,不进入 DataSource Pool Registry
|
|
|
|
|
+
|
|
|
|
|
+业务数据面
|
|
|
|
|
+DataSource API / 元数据采集 / 数据预览 / DataFlow
|
|
|
|
|
+ │
|
|
|
|
|
+ ▼
|
|
|
|
|
+ DataSourceConnectionManager
|
|
|
|
|
+ ├── DataSourceDefinitionRepository
|
|
|
|
|
+ ├── CredentialProvider
|
|
|
|
|
+ ├── PostgreSQLAdapter
|
|
|
|
|
+ ├── MySQLAdapter
|
|
|
|
|
+ ├── PoolRegistry
|
|
|
|
|
+ ├── CircuitBreaker
|
|
|
|
|
+ └── PoolMetrics
|
|
|
|
|
+ │
|
|
|
|
|
+ ┌─────────┴─────────┐
|
|
|
|
|
+ ▼ ▼
|
|
|
|
|
+ PostgreSQL 数据源池 MySQL 数据源池
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+业务模块只能通过 `DataSourceConnectionManager` 获取外部数据库连接,不得直接
|
|
|
|
|
+读取数据源密码并调用 `create_engine()`。连接测试是唯一允许创建临时 Engine 的
|
|
|
|
|
+入口,且必须使用 `NullPool` 并在完成后执行 `dispose()`。
|
|
|
|
|
+
|
|
|
|
|
+## 4. 数据源身份与连接池注册
|
|
|
|
|
+
|
|
|
|
|
+每个数据源必须具有稳定 UUID,并以 `data_source_uid` 作为业务身份。池注册键由
|
|
|
|
|
+以下内容组成:
|
|
|
|
|
+
|
|
|
|
|
+```text
|
|
|
|
|
+data_source_uid
|
|
|
|
|
+credential_version
|
|
|
|
|
+connection_config_fingerprint
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+连接配置指纹由数据库类型、主机、端口、数据库名、Schema 和白名单安全选项生成,
|
|
|
|
|
+不包含用户名、密码、连接 URL 或密文。
|
|
|
|
|
+
|
|
|
|
|
+连接池按需创建:
|
|
|
|
|
+
|
|
|
|
|
+- 第一次采集、预览或 DataFlow 使用时创建。
|
|
|
|
|
+- 同一 Gunicorn Worker 内,同一注册键复用一个 SQLAlchemy Engine。
|
|
|
|
|
+- 数据源定义、凭据版本或安全配置变化后,旧注册键失效。
|
|
|
|
|
+- 数据源删除后,禁止新连接借出并排空旧池。
|
|
|
|
|
+- 空闲 15 分钟的池执行回收。
|
|
|
|
|
+- 每个 Worker 默认最多缓存 20 个空闲池。
|
|
|
|
|
+- 正在执行任务的池进入 `draining`,连接归还后再释放。
|
|
|
|
|
+
|
|
|
|
|
+Gunicorn Worker 是独立进程。即使只有一个后端实例,各 Worker 仍拥有独立的
|
|
|
|
|
+Pool Registry,第一阶段不尝试跨进程共享数据库连接。
|
|
|
|
|
+
|
|
|
|
|
+## 5. 默认容量
|
|
|
|
|
+
|
|
|
|
|
+单个数据源、单个 Worker 的默认参数:
|
|
|
|
|
+
|
|
|
|
|
+```text
|
|
|
|
|
+pool_size=2
|
|
|
|
|
+max_overflow=3
|
|
|
|
|
+pool_timeout=10
|
|
|
|
|
+pool_recycle=1800
|
|
|
|
|
+pool_pre_ping=true
|
|
|
|
|
+pool_use_lifo=true
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+采用 4 个 Worker 时,一个持续繁忙的数据源理论最大连接数为 20,常驻连接数
|
|
|
|
|
+最大为 8。数据源可以配置受控的池大小覆盖值,但服务端必须限制上下限,不能直接
|
|
|
|
|
+接受用户提交的任意连接数量。
|
|
|
|
|
+
|
|
|
|
|
+后续多副本阶段使用以下公式重新计算单数据源连接预算:
|
|
|
|
|
+
|
|
|
|
|
+```text
|
|
|
|
|
+副本数 × Worker 数 × (pool_size + max_overflow)
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## 6. 驱动适配
|
|
|
|
|
+
|
|
|
|
|
+Adapter 负责:
|
|
|
|
|
+
|
|
|
|
|
+- 校验数据源类型和连接字段。
|
|
|
|
|
+- 构建 SQLAlchemy URL。
|
|
|
|
|
+- 生成驱动专属白名单参数。
|
|
|
|
|
+- 创建 Engine。
|
|
|
|
|
+- 执行 `SELECT 1` 探测。
|
|
|
|
|
+- 将驱动异常转换为稳定的平台错误。
|
|
|
|
|
+- 提供数据库标识符引用和只读事务能力。
|
|
|
|
|
+
|
|
|
|
|
+PostgreSQL 默认启用 TCP keepalive:
|
|
|
|
|
+
|
|
|
|
|
+```text
|
|
|
|
|
+connect_timeout=5
|
|
|
|
|
+keepalives=1
|
|
|
|
|
+keepalives_idle=30
|
|
|
|
|
+keepalives_interval=10
|
|
|
|
|
+keepalives_count=3
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+MySQL 默认设置:
|
|
|
|
|
+
|
|
|
|
|
+```text
|
|
|
|
|
+connect_timeout=5
|
|
|
|
|
+read_timeout=30
|
|
|
|
|
+write_timeout=30
|
|
|
|
|
+charset=utf8mb4
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+SSL 和证书参数只能从明确白名单读取,不允许任意驱动参数透传。前端传入的
|
|
|
|
|
+`type` 必须映射到平台内部 Adapter,不能直接成为 SQLAlchemy driver name。
|
|
|
|
|
+
|
|
|
|
|
+## 7. 凭据模型
|
|
|
|
|
+
|
|
|
|
|
+平台 PostgreSQL 新增 `datasource_credentials`:
|
|
|
|
|
+
|
|
|
|
|
+```text
|
|
|
|
|
+id
|
|
|
|
|
+data_source_uid
|
|
|
|
|
+credential_version
|
|
|
|
|
+encrypted_payload
|
|
|
|
|
+nonce
|
|
|
|
|
+key_version
|
|
|
|
|
+status
|
|
|
|
|
+created_at
|
|
|
|
|
+retired_at
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+`encrypted_payload` 包含用户名、密码和必要的认证选项。Neo4j `DataSource` 节点
|
|
|
|
|
+只保存:
|
|
|
|
|
+
|
|
|
|
|
+```text
|
|
|
|
|
+uid
|
|
|
|
|
+type
|
|
|
|
|
+host
|
|
|
|
|
+port
|
|
|
|
|
+database
|
|
|
|
|
+schema
|
|
|
|
|
+credential_ref
|
|
|
|
|
+credential_version
|
|
|
|
|
+业务描述和治理属性
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+凭据使用 AES-256-GCM 加密:
|
|
|
|
|
+
|
|
|
|
|
+- 主密钥由 `DATASOURCE_CREDENTIAL_MASTER_KEY` 环境变量注入。
|
|
|
|
|
+- 每条凭据使用独立随机 nonce。
|
|
|
|
|
+- `data_source_uid + credential_version` 作为附加认证数据。
|
|
|
|
|
+- `key_version` 为后续主密钥轮换保留。
|
|
|
|
|
+- 主密钥不写入代码、数据库、镜像或日志。
|
|
|
|
|
+- 缺少主密钥或解密失败时,数据源功能失败关闭,但平台控制面仍可启动。
|
|
|
|
|
+
|
|
|
|
|
+API、日志、异常、Neo4j 查询结果和诊断接口均不得返回用户名、密码、密文、
|
|
|
|
|
+带凭据的 URL 或完整连接串。数据源列表只返回 `credential_configured` 等布尔状态。
|
|
|
|
|
+
|
|
|
|
|
+## 8. 数据源变更流程
|
|
|
|
|
+
|
|
|
|
|
+### 8.1 创建
|
|
|
|
|
+
|
|
|
|
|
+1. 校验类型,只允许 PostgreSQL 或 MySQL。
|
|
|
|
|
+2. 校验主机、端口、数据库名和安全选项。
|
|
|
|
|
+3. 使用 `NullPool` 临时 Engine 测试连接,随后关闭。
|
|
|
|
|
+4. 将凭据加密写入平台 PostgreSQL。
|
|
|
|
|
+5. 在 Neo4j 保存不含密码的数据源定义及凭据引用。
|
|
|
|
|
+6. 成功后允许 Pool Registry 按 UID 延迟创建连接池。
|
|
|
|
|
+
|
|
|
|
|
+### 8.2 修改
|
|
|
|
|
+
|
|
|
|
|
+1. 创建不可变的新凭据版本。
|
|
|
|
|
+2. 更新 Neo4j 数据源定义和 `credential_version`。
|
|
|
|
|
+3. 将旧连接池标记为 `draining`。
|
|
|
|
|
+4. 后续请求使用新注册键创建连接池。
|
|
|
|
|
+5. 旧凭据标记为 `retired`。
|
|
|
|
|
+
|
|
|
|
|
+修改失败时,数据源继续引用原有有效版本。PostgreSQL 与 Neo4j 之间使用现有
|
|
|
|
|
+Outbox 和补偿巡检实现最终一致性。
|
|
|
|
|
+
|
|
|
|
|
+### 8.3 删除
|
|
|
|
|
+
|
|
|
|
|
+1. 禁止新连接借出。
|
|
|
|
|
+2. 等待已借出连接在限定时间内归还。
|
|
|
|
|
+3. 到期后强制释放 Engine。
|
|
|
|
|
+4. 删除或归档 Neo4j 数据源定义。
|
|
|
|
|
+5. 将凭据标记为 `revoked`。
|
|
|
|
|
+6. 记录不含敏感数据的审计事件。
|
|
|
|
|
+
|
|
|
|
|
+## 9. 连接获取接口
|
|
|
|
|
+
|
|
|
|
|
+业务模块使用统一的上下文接口:
|
|
|
|
|
+
|
|
|
|
|
+```python
|
|
|
|
|
+with data_source_manager.connect(
|
|
|
|
|
+ data_source_uid,
|
|
|
|
|
+ purpose="metadata_preview",
|
|
|
|
|
+) as connection:
|
|
|
|
|
+ ...
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+管理器依次完成定义读取、凭据获取、Adapter 选择、池注册、连接借出和归还。
|
|
|
|
|
+业务模块不持有 Engine,不负责连接池配置,也不能访问凭据明文。
|
|
|
|
|
+
|
|
|
|
|
+`purpose` 使用平台枚举,第一阶段至少包括:
|
|
|
|
|
+
|
|
|
|
|
+- `connection_test`
|
|
|
|
|
+- `metadata_collection`
|
|
|
|
|
+- `metadata_preview`
|
|
|
|
|
+- `quality_check`
|
|
|
|
|
+- `dataflow_read`
|
|
|
|
|
+- `dataflow_write`
|
|
|
|
|
+
|
|
|
|
|
+除 `dataflow_write` 外默认使用只读事务。写入任务必须显式提交,异常时回滚。
|
|
|
|
|
+
|
|
|
|
|
+## 10. 故障隔离与熔断
|
|
|
|
|
+
|
|
|
|
|
+每个数据源独立维护状态:
|
|
|
|
|
+
|
|
|
|
|
+```text
|
|
|
|
|
+healthy
|
|
|
|
|
+degraded
|
|
|
|
|
+open
|
|
|
|
|
+draining
|
|
|
|
|
+closed
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+- 单个数据源连接耗尽或宕机,不影响平台控制库和其他数据源。
|
|
|
|
|
+- 连续 3 次建连失败后熔断 30 秒。
|
|
|
|
|
+- 熔断结束后只允许一个半开放探测请求。
|
|
|
|
|
+- 探测成功恢复 `healthy`,失败继续熔断。
|
|
|
|
|
+- 借出等待超过 10 秒返回 `DATASOURCE_POOL_TIMEOUT`。
|
|
|
|
|
+- 事务中断必须回滚,不自动重放数据源写操作。
|
|
|
|
|
+- 失效连接不再返回池中。
|
|
|
|
|
+
|
|
|
|
|
+主要稳定错误码:
|
|
|
|
|
+
|
|
|
|
|
+- `DATASOURCE_NOT_FOUND`
|
|
|
|
|
+- `DATASOURCE_TYPE_UNSUPPORTED`
|
|
|
|
|
+- `DATASOURCE_CREDENTIAL_UNAVAILABLE`
|
|
|
|
|
+- `DATASOURCE_CONNECTION_FAILED`
|
|
|
|
|
+- `DATASOURCE_POOL_TIMEOUT`
|
|
|
|
|
+- `DATASOURCE_CIRCUIT_OPEN`
|
|
|
|
|
+- `DATASOURCE_QUERY_TIMEOUT`
|
|
|
|
|
+- `DATASOURCE_READ_ONLY_VIOLATION`
|
|
|
|
|
+
|
|
|
|
|
+错误响应不包含驱动原始连接 URL 或凭据。
|
|
|
|
|
+
|
|
|
|
|
+## 11. 监控与管理
|
|
|
|
|
+
|
|
|
|
|
+管理员可以查看单个数据源的池摘要:
|
|
|
|
|
+
|
|
|
|
|
+```text
|
|
|
|
|
+data_source_uid
|
|
|
|
|
+database_type
|
|
|
|
|
+pool_state
|
|
|
|
|
+pool_size
|
|
|
|
|
+checked_out
|
|
|
|
|
+checked_in
|
|
|
|
|
+overflow
|
|
|
|
|
+checkout_wait_ms
|
|
|
|
|
+last_used_at
|
|
|
|
|
+consecutive_failures
|
|
|
|
|
+circuit_open_until
|
|
|
|
|
+credential_version
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+平台健康检查仅返回聚合数据:
|
|
|
|
|
+
|
|
|
|
|
+```text
|
|
|
|
|
+active_pool_count
|
|
|
|
|
+degraded_pool_count
|
|
|
|
|
+open_circuit_count
|
|
|
|
|
+checked_out_total
|
|
|
|
|
+pool_timeout_total
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+指标至少覆盖:
|
|
|
|
|
+
|
|
|
|
|
+- 连接创建成功和失败次数。
|
|
|
|
|
+- Checkout 等待时间。
|
|
|
|
|
+- 当前占用、空闲和溢出连接数。
|
|
|
|
|
+- 池超时、连接失效和重建次数。
|
|
|
|
|
+- 查询耗时。
|
|
|
|
|
+- 空闲池回收次数。
|
|
|
|
|
+- 凭据版本导致的池重建次数。
|
|
|
|
|
+
|
|
|
|
|
+所有日志使用数据源 UID 和错误分类;异常文本在写日志前统一脱敏。
|
|
|
|
|
+
|
|
|
|
|
+## 12. 历史凭据迁移
|
|
|
|
|
+
|
|
|
|
|
+迁移必须分两步:
|
|
|
|
|
+
|
|
|
|
|
+1. 只读报告:统计含明文凭据、缺少 UID 或配置不完整的数据源,只输出 UID 和问题
|
|
|
|
|
+ 分类。
|
|
|
|
|
+2. 显式迁移:补齐 UID、加密凭据、写入凭据引用、验证可解密后删除 Neo4j 明文字段。
|
|
|
|
|
+
|
|
|
|
|
+生产执行前必须单独备份 Neo4j 和平台 PostgreSQL。迁移过程不得在日志、报告或
|
|
|
|
|
+异常中输出明文密码。迁移失败的数据源保持原状态并进入人工核查清单。
|
|
|
|
|
+
|
|
|
|
|
+现有数据源保存、验证和连接测试接口必须先完成请求日志脱敏。数据源列表接口必须
|
|
|
|
|
+在凭据迁移前就停止返回密码。
|
|
|
|
|
+
|
|
|
|
|
+## 13. 本地隔离验收
|
|
|
|
|
+
|
|
|
|
|
+Docker 测试栈新增两个业务数据源服务:
|
|
|
|
|
+
|
|
|
|
|
+```text
|
|
|
|
|
+platform-postgres 平台控制库
|
|
|
|
|
+source-postgres PostgreSQL 验收数据源
|
|
|
|
|
+source-mysql MySQL 验收数据源
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+验收标准:
|
|
|
|
|
+
|
|
|
|
|
+1. PostgreSQL、MySQL 均可保存、测试、建池和执行查询。
|
|
|
|
|
+2. 同一数据源并发请求复用连接且不超过池上限。
|
|
|
|
|
+3. 50–100 个并发请求下无连接泄漏。
|
|
|
|
|
+4. 配置或密码修改后旧池失效,新池使用新版本。
|
|
|
|
|
+5. 空闲池到期释放,再次访问可重建。
|
|
|
|
|
+6. MySQL 停机不影响 PostgreSQL 数据源和平台功能。
|
|
|
|
|
+7. 数据源恢复后可通过半开放探测恢复。
|
|
|
|
|
+8. 删除数据源后连接池和凭据不可继续使用。
|
|
|
|
|
+9. API、日志、Neo4j 和异常中不存在明文密码。
|
|
|
|
|
+10. 后端重启后连接池按需重建。
|
|
|
|
|
+
|
|
|
|
|
+测试包括:
|
|
|
|
|
+
|
|
|
|
|
+- AES-GCM 加密、篡改检测和密钥版本测试。
|
|
|
|
|
+- PostgreSQL、MySQL Adapter 合约测试。
|
|
|
|
|
+- Pool Registry 并发、回收和失效测试。
|
|
|
|
|
+- 熔断状态机测试。
|
|
|
|
|
+- 历史明文凭据迁移测试。
|
|
|
|
|
+- Docker 数据源宕机和恢复测试。
|
|
|
|
|
+- 前后端数据源管理接口验收。
|
|
|
|
|
+
|
|
|
|
|
+## 14. 后续扩展
|
|
|
|
|
+
|
|
|
|
|
+进入多副本阶段后再评估:
|
|
|
|
|
+
|
|
|
|
|
+- 按副本数重新计算连接预算。
|
|
|
|
|
+- PgBouncer。
|
|
|
|
|
+- 跨实例池状态聚合。
|
|
|
|
|
+- 数据源任务按 UID 路由。
|
|
|
|
|
+- Oracle、SQL Server 等额外 Adapter。
|
|
|
|
|
+
|
|
|
|
|
+第一阶段的 Adapter、CredentialProvider、Pool Registry 和指标接口保持稳定,
|
|
|
|
|
+多副本阶段不改变业务模块的连接获取接口。
|