diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..e6edc808b --- /dev/null +++ b/docs/README.md @@ -0,0 +1,312 @@ +# 响应式ETL框架文档中心 + +## 📚 文档导航 + +### 核心设计文档 + +#### 1. [系统架构设计](reactive-etl-framework-design.md) +完整的系统架构设计文档,包含: +- 系统整体架构 +- 核心模块设计(Job、StreamGraph、JobGraph、Scheduler、Executor等) +- 关键流程时序图 +- 监控运维方案 +- 最佳实践 + +**推荐阅读顺序**: ⭐️⭐️⭐️⭐️⭐️ 必读 + +--- + +#### 2. [数据库设计](database-design.md) +数据库表结构设计文档(单机版),包含: +- 13张核心表的详细设计 +- 表关系ER图 +- 索引策略 +- 分区方案 +- 数据保留策略 + +**推荐阅读顺序**: ⭐️⭐️⭐️⭐️⭐️ 必读 + +--- + +#### 3. [数据库建表脚本](database-schema.sql) +可直接执行的SQL脚本,包含: +- 所有表的CREATE TABLE语句 +- 索引定义 +- 初始化数据(内置连接器、系统配置、告警规则) +- 便捷查询视图 + +**使用方式**: +```bash +mysql -u root -p etl_framework < database-schema.sql +``` + +--- + +### StreamGraph配置文档 + +#### 4. [StreamGraph定义结构说明](graph-definition-examples.md) +详细的StreamGraph配置说明,包含: +- 完整的JSON结构定义 +- 所有节点类型详解(Source、Operator、Sink) +- 配置参数说明 +- 可视化流程图 +- 最佳实践建议 + +**推荐阅读顺序**: ⭐️⭐️⭐️⭐️ 开发必读 + +--- + +#### 5. [JSON配置示例](graph-definition-json-examples.json) +7个完整的、可直接使用的JSON配置示例: +1. **简单ETL** - Kafka到MySQL +2. **实时统计** - 窗口聚合 +3. **数据清洗** - 去重和转换 +4. **多分支处理** - 日志分流 +5. **API数据采集** - HTTP定期拉取 +6. **文件处理** - CSV到JSON +7. **数据关联** - JOIN操作 + +**使用方式**: 直接复制粘贴到你的任务配置中 + +--- + +#### 6. [JSON示例使用指南](json-examples-guide.md) +JSON示例的详细使用说明,包含: +- 每个示例的场景说明 +- 数据流程图 +- 适用场景 +- 配置说明 +- 常见问题解答 + +**推荐阅读顺序**: ⭐️⭐️⭐️⭐️ 快速上手 + +--- + +## 🚀 快速开始 + +### 第一步:了解系统架构 +阅读 [系统架构设计](reactive-etl-framework-design.md),理解系统的整体设计理念。 + +### 第二步:初始化数据库 +```bash +# 创建数据库 +mysql -u root -p +CREATE DATABASE etl_framework DEFAULT CHARACTER SET utf8mb4; + +# 执行建表脚本 +mysql -u root -p etl_framework < database-schema.sql +``` + +### 第三步:查看示例 +打开 [JSON配置示例](graph-definition-json-examples.json),选择一个最接近你需求的示例。 + +### 第四步:创建任务 +参考 [JSON示例使用指南](json-examples-guide.md),修改配置并创建你的第一个ETL任务。 + +--- + +## 📖 按角色阅读 + +### 架构师 +1. [系统架构设计](reactive-etl-framework-design.md) - 了解整体架构 +2. [数据库设计](database-design.md) - 了解数据模型 + +### 开发人员 +1. [系统架构设计](reactive-etl-framework-design.md) - 核心模块章节 +2. [StreamGraph定义结构说明](graph-definition-examples.md) - 节点类型详解 +3. [JSON示例使用指南](json-examples-guide.md) - 快速上手 + +### 运维人员 +1. [系统架构设计](reactive-etl-framework-design.md) - 监控运维章节 +2. [数据库设计](database-design.md) - 索引和分区优化 +3. [数据库建表脚本](database-schema.sql) - 执行初始化 + +### 产品经理 +1. [系统架构设计](reactive-etl-framework-design.md) - 概述和特性 +2. [JSON示例使用指南](json-examples-guide.md) - 场景示例 + +--- + +## 🎯 按场景查找 + +### 场景1: 实时数据采集 +- **Kafka数据采集**: 查看示例1和示例2 +- **API数据拉取**: 查看示例5 +- **文件监控采集**: 查看示例6 + +### 场景2: 数据转换清洗 +- **简单转换**: 查看示例1(MAP + FILTER) +- **去重**: 查看示例3(DEDUPLICATE) +- **数组展开**: 查看示例5(FLATMAP) + +### 场景3: 实时统计聚合 +- **窗口聚合**: 查看示例2(WINDOW + AGGREGATE) +- **分组统计**: 查看示例2(GROUP BY) + +### 场景4: 数据关联 +- **JOIN操作**: 查看示例7 +- **维度补全**: 查看示例7 + +### 场景5: 多目标输出 +- **分支处理**: 查看示例4(多Filter + 多Sink) +- **双写**: 查看示例2(MySQL + Redis) + +--- + +## 🔧 配置速查 + +### 常用Source配置 + +```json +// Kafka Source +{ + "operator_type": "KAFKA_SOURCE", + "config": { + "datasource_id": "kafka-prod", + "topics": ["topic-name"], + "group_id": "consumer-group" + } +} + +// JDBC Source +{ + "operator_type": "JDBC_SOURCE", + "config": { + "datasource_id": "mysql-prod", + "query": "SELECT * FROM table WHERE ...", + "fetch_size": 1000 + } +} +``` + +### 常用Operator配置 + +```json +// MAP +{ + "operator_type": "MAP", + "config": { + "function_class": "com.example.YourFunction" + } +} + +// FILTER +{ + "operator_type": "FILTER", + "config": { + "predicate_expression": "field > 100" + } +} + +// AGGREGATE +{ + "operator_type": "AGGREGATE", + "config": { + "group_by_fields": ["city"], + "aggregations": [ + {"field": "amount", "function": "SUM"} + ] + } +} +``` + +### 常用Sink配置 + +```json +// JDBC Sink +{ + "operator_type": "JDBC_SINK", + "config": { + "datasource_id": "mysql-warehouse", + "table": "target_table", + "batch_size": 100, + "write_mode": "INSERT" + } +} + +// Kafka Sink +{ + "operator_type": "KAFKA_SINK", + "config": { + "datasource_id": "kafka-prod", + "topic": "output-topic", + "batch_size": 100 + } +} +``` + +--- + +## 📊 表结构速查 + +### 核心表(13张) + +| 表名 | 说明 | 关键字段 | +| --- | --- | --- | +| etl_job | 任务定义 | job_id, job_status | +| etl_job_instance | 运行实例 | instance_id, job_id | +| etl_job_schedule | 调度配置 | schedule_type, cron_expression | +| etl_stream_graph | 流图定义 | graph_id, graph_definition | +| etl_connector | 连接器注册 | connector_id, connector_type | +| etl_datasource | 数据源配置 | datasource_id, connection_config | +| etl_checkpoint | 检查点 | checkpoint_id, instance_id | +| etl_job_metrics | 运行指标 | job_id, metric_time | +| etl_system_config | 系统配置 | config_key, config_value | +| etl_alert_rule | 告警规则 | rule_id, rule_type | +| etl_alert_record | 告警记录 | alert_id, alert_time | +| etl_user | 用户 | user_id, username | +| etl_operation_log | 操作日志 | operation_type, resource_type | + +--- + +## ❓ 常见问题 + +### Q1: 数据源配置在哪里? +在`etl_datasource`表中配置,然后在graph_definition中通过`datasource_id`引用。 + +### Q2: 如何添加自定义算子? +在nodes配置中指定你的`function_class`,框架会通过反射加载。 + +### Q3: 支持哪些数据源? +内置支持:JDBC、Kafka、HTTP、File、Redis、Elasticsearch。可通过SPI机制扩展。 + +### Q4: 如何配置检查点? +在`etl_job`表的`checkpoint_enabled`字段或graph_definition的`global_config`中配置。 + +### Q5: 如何监控任务运行? +查看`etl_job_instance`和`etl_job_metrics`表,或使用Prometheus等监控系统。 + +--- + +## 🔗 相关资源 + +### 技术栈 +- [Project Reactor](https://projectreactor.io/) - 响应式编程框架 +- [Apache Kafka](https://kafka.apache.org/) - 消息队列 +- [MySQL](https://www.mysql.com/) - 关系型数据库 +- [Elasticsearch](https://www.elastic.co/) - 搜索引擎 + +### 参考项目 +- [Apache Flink](https://flink.apache.org/) - 分布式流处理框架 +- [Spring Cloud Data Flow](https://spring.io/projects/spring-cloud-dataflow) - 数据流编排 + +--- + +## 📝 文档版本 + +| 版本 | 日期 | 说明 | +| --- | --- | --- | +| v1.0 | 2025-11-09 | 初始版本 | +| v2.0 | 2025-11-09 | 简化为单机版架构 | + +--- + +## 👥 贡献者 + +ETL Framework Team + +--- + +## 📧 联系方式 + +如有问题或建议,请联系项目维护者。 diff --git a/docs/database-design.md b/docs/database-design.md new file mode 100644 index 000000000..03989c74a --- /dev/null +++ b/docs/database-design.md @@ -0,0 +1,666 @@ +# 响应式ETL框架 - 数据库设计文档(单机版) + +## 1. 概述 + +本文档描述了响应式ETL框架的数据库表结构设计。该框架采用**单机执行模式**,即一个Job作为最小执行单元,在单个实例上完整运行,不涉及分布式算子调度。 + +### 1.1 设计原则 + +- **单机执行**: 每个Job在一个实例上完整执行,不会将算子分散到不同节点 +- **简洁高效**: 去除分布式相关的复杂设计,保持表结构简洁 +- **易于管理**: 降低运维复杂度,适合中小规模数据处理 +- **完整功能**: 支持任务调度、检查点、监控告警等核心功能 + +### 1.2 数据库选型 + +- **数据库**: MySQL 8.0+ +- **字符集**: utf8mb4 +- **存储引擎**: InnoDB +- **时区**: 统一使用UTC或Asia/Shanghai + +### 1.3 表分类概览 + +```mermaid +graph TB + DB[ETL Database
单机版] + + DB --> JOB[任务管理
3张表] + DB --> GRAPH[图结构
1张表] + DB --> CONN[连接器
2张表] + DB --> CP[检查点
1张表] + DB --> METRICS[监控指标
1张表] + DB --> SYS[系统配置
3张表] + DB --> USER[用户审计
2张表] + + JOB --> J1[etl_job
任务定义] + JOB --> J2[etl_job_instance
运行实例] + JOB --> J3[etl_job_schedule
调度配置] + + GRAPH --> G1[etl_stream_graph
流图定义] + + CONN --> C1[etl_connector
连接器注册] + CONN --> C2[etl_datasource
数据源配置] + + CP --> CP1[etl_checkpoint
检查点] + + METRICS --> M1[etl_job_metrics
运行指标] + + SYS --> S1[etl_system_config
系统配置] + SYS --> S2[etl_alert_rule
告警规则] + SYS --> S3[etl_alert_record
告警记录] + + USER --> U1[etl_user
用户] + USER --> U2[etl_operation_log
操作日志] +``` + +## 2. 任务管理相关表 + +### 2.1 etl_job - 任务定义表 + +**用途**: 存储ETL任务的定义信息和配置 + +**核心设计**: +- 一个Job包含完整的Source → Operators → Sink处理链 +- 使用JSON字段存储Source、Operators、Sink配置,灵活且易于扩展 +- 不需要并行度、分区等分布式概念 + +**关键字段说明**: + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| job_id | VARCHAR(64) | 任务唯一标识,建议UUID | +| job_type | VARCHAR(32) | STREAMING(流式)/BATCH(批处理) | +| job_status | VARCHAR(32) | CREATED/SCHEDULED/RUNNING/PAUSED/COMPLETED/FAILED/CANCELLED | +| stream_graph_id | VARCHAR(64) | 关联的StreamGraph ID | +| source_config | JSON | Source配置,包含连接器类型、数据源ID、读取参数等 | +| operators_config | JSON | Operators配置数组,按顺序执行 | +| sink_config | JSON | Sink配置,包含连接器类型、目标数据源、写入参数等 | +| restart_strategy | VARCHAR(32) | 重启策略: FIXED_DELAY/EXPONENTIAL_BACKOFF/NO_RESTART | +| checkpoint_enabled | TINYINT | 是否启用检查点 | + +**配置示例**: + +```json +{ + "source_config": { + "connector_type": "kafka", + "datasource_id": "kafka-prod", + "topics": ["user-events"], + "group_id": "etl-consumer", + "poll_timeout_ms": 1000 + }, + "operators_config": [ + { + "operator_type": "MAP", + "name": "parse-json", + "function": "com.example.ParseJsonFunction" + }, + { + "operator_type": "FILTER", + "name": "filter-active", + "predicate": "user.isActive == true" + }, + { + "operator_type": "AGGREGATE", + "name": "count-by-city", + "window_size": "5m", + "group_by": "city" + } + ], + "sink_config": { + "connector_type": "jdbc", + "datasource_id": "mysql-warehouse", + "table": "user_stats", + "batch_size": 100, + "flush_interval_ms": 5000 + } +} +``` + +### 2.2 etl_job_instance - 任务实例表 + +**用途**: 记录每次Job运行的实例信息 + +**核心设计**: +- 一个Job可以有多次运行实例 +- 记录运行主机、进程ID等信息,便于定位问题 +- 记录核心指标:读取、处理、写入记录数 + +**关键字段说明**: + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| instance_id | VARCHAR(64) | 实例唯一标识 | +| job_id | VARCHAR(64) | 所属任务ID | +| instance_status | VARCHAR(32) | RUNNING/COMPLETED/FAILED/CANCELLED | +| host_address | VARCHAR(128) | 运行主机地址,如 192.168.1.100 | +| process_id | VARCHAR(64) | 进程PID | +| start_time | DATETIME | 开始时间 | +| end_time | DATETIME | 结束时间 | +| duration_ms | BIGINT | 执行时长(毫秒) | +| records_read | BIGINT | 读取记录数 | +| records_processed | BIGINT | 处理记录数 | +| records_written | BIGINT | 写入记录数 | +| last_checkpoint_id | VARCHAR(64) | 最后检查点ID,用于故障恢复 | + +**使用场景**: +- 任务执行历史查询 +- 故障排查和问题定位 +- 性能分析和统计报表 + +### 2.3 etl_job_schedule - 任务调度配置表 + +**用途**: 管理任务的调度策略 + +**核心设计**: +- 支持立即执行、定时执行、手动执行三种模式 +- 一个Job对应一个调度配置(1:1关系) +- 简化了依赖调度和事件触发(可在应用层实现) + +**关键字段说明**: + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| schedule_type | VARCHAR(32) | IMMEDIATE(立即)/CRON(定时)/MANUAL(手动) | +| cron_expression | VARCHAR(128) | Cron表达式,如 "0 0 * * * ?" | +| next_fire_time | DATETIME | 下次触发时间 | +| fire_count | BIGINT | 已触发次数 | + +**Cron表达式示例**: +- `0 0 * * * ?` - 每小时执行 +- `0 0 1 * * ?` - 每天凌晨1点执行 +- `0 */5 * * * ?` - 每5分钟执行 + +## 3. 图结构相关表 + +### 3.1 etl_stream_graph - StreamGraph定义表 + +**用途**: 存储任务的数据流图定义 + +**核心设计**: +- StreamGraph是逻辑执行图,描述Source → Operators → Sink的数据流向 +- 使用JSON完整存储图结构,包括节点和边 +- 单机模式下不需要JobGraph优化,直接使用StreamGraph执行 + +**关键字段说明**: + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| graph_id | VARCHAR(64) | 图唯一标识 | +| job_id | VARCHAR(64) | 关联的任务ID | +| graph_definition | JSON | 完整的图定义 | + +**图定义JSON结构**: + +```json +{ + "nodes": [ + { + "node_id": "source-1", + "node_type": "SOURCE", + "operator_type": "KAFKA_SOURCE", + "config": {...} + }, + { + "node_id": "map-1", + "node_type": "OPERATOR", + "operator_type": "MAP", + "config": {...} + }, + { + "node_id": "sink-1", + "node_type": "SINK", + "operator_type": "JDBC_SINK", + "config": {...} + } + ], + "edges": [ + { + "source": "source-1", + "target": "map-1" + }, + { + "source": "map-1", + "target": "sink-1" + } + ] +} +``` + +**设计简化**: +- 去除了并行度、分区策略等分布式概念 +- 不需要算子链优化(Operator Chain) +- 不需要资源分配和调度 + +## 4. 连接器配置相关表 + +### 4.1 etl_connector - 连接器注册表 + +**用途**: 注册系统支持的所有连接器 + +**核心设计**: +- 内置连接器随系统初始化 +- 支持自定义连接器通过SPI机制注册 +- 一个连接器可以同时支持Source和Sink + +**内置连接器**: + +| 连接器类型 | 支持Source | 支持Sink | 说明 | +| --- | --- | --- | --- | +| JDBC | ✓ | ✓ | 关系型数据库 | +| KAFKA | ✓ | ✓ | 消息队列 | +| HTTP | ✓ | ✓ | REST API | +| FILE | ✓ | ✓ | 文件系统 | +| REDIS | ✓ | ✓ | 缓存 | +| ELASTICSEARCH | ✓ | ✓ | 搜索引擎 | + +### 4.2 etl_datasource - 数据源配置表 + +**用途**: 存储具体的数据源连接配置 + +**核心设计**: +- 一个连接器可以配置多个数据源实例 +- 数据源配置可以在多个Job间共享 +- 敏感信息(密码)需要加密存储 + +**配置示例**: + +```json +{ + "connection_config": { + "url": "jdbc:mysql://localhost:3306/test", + "username": "root", + "password": "encrypted_password", + "driver": "com.mysql.cj.jdbc.Driver", + "pool": { + "maxSize": 20, + "maxIdleTime": "30m" + } + } +} +``` + +## 5. 检查点相关表 + +### 5.1 etl_checkpoint - 检查点表 + +**用途**: 记录检查点信息,用于故障恢复 + +**核心设计**: +- 周期性自动创建检查点或手动触发 +- 小状态直接存储在数据库(state_snapshot字段) +- 大状态存储在文件系统,数据库记录路径 + +**关键字段说明**: + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| checkpoint_id | VARCHAR(64) | 检查点唯一标识 | +| instance_id | VARCHAR(64) | 所属实例ID | +| checkpoint_type | VARCHAR(32) | AUTO(自动)/MANUAL(手动) | +| state_size_bytes | BIGINT | 状态大小 | +| storage_path | VARCHAR(512) | 大状态存储路径 | +| state_snapshot | JSON | 小状态直接存储 | + +**使用场景**: +- Job失败后从最近的检查点恢复 +- 手动保存点用于版本升级 +- 状态迁移和备份 + +**保留策略**: +- 默认保留最近5个检查点 +- 定期清理过期检查点 + +## 6. 监控指标相关表 + +### 6.1 etl_job_metrics - 任务运行指标表 + +**用途**: 记录任务运行时的监控指标 + +**核心设计**: +- 单机模式只需要Job级别指标,不需要算子级别指标 +- 定期采集(如每10秒)存储一条记录 +- 用于实时监控和历史趋势分析 + +**关键指标**: + +| 指标类别 | 字段 | 说明 | +| --- | --- | --- | +| 吞吐量 | records_read_rate | 读取速率(记录/秒) | +| 吞吐量 | records_write_rate | 写入速率(记录/秒) | +| 延迟 | processing_latency_ms | 处理延迟(毫秒) | +| 错误 | error_count | 错误次数 | +| 背压 | backpressure_count | 背压次数 | +| 资源 | jvm_heap_used_mb | JVM堆内存使用 | +| 资源 | cpu_usage_percent | CPU使用率 | +| 资源 | thread_count | 线程数 | + +**数据保留**: +- 详细指标保留30天 +- 可以聚合后长期保存 + +## 7. 系统配置和告警 + +### 7.1 etl_system_config - 系统配置表 + +**用途**: 存储系统全局配置 + +**配置分组**: + +| 分组 | 配置项 | 说明 | +| --- | --- | --- | +| EXECUTOR | thread.pool.core.size | 线程池核心大小 | +| EXECUTOR | thread.pool.max.size | 线程池最大大小 | +| CHECKPOINT | checkpoint.interval.seconds | 检查点间隔 | +| CHECKPOINT | checkpoint.retention.count | 保留检查点数量 | +| METRICS | metrics.collect.interval.seconds | 指标采集间隔 | + +### 7.2 etl_alert_rule - 告警规则表 + +**用途**: 定义监控告警规则 + +**支持的告警类型**: + +| 告警类型 | 说明 | 条件示例 | +| --- | --- | --- | +| JOB_FAILED | 任务失败 | instance_status == FAILED | +| JOB_TIMEOUT | 任务超时 | duration_ms > 3600000 | +| HIGH_ERROR_RATE | 高错误率 | error_count / records_read_total > 0.01 | +| CHECKPOINT_FAILED | 检查点失败 | checkpoint_status == FAILED | + +**通知渠道**: +- EMAIL: 邮件通知 +- SMS: 短信通知 +- WEBHOOK: Webhook回调 +- DINGTALK: 钉钉机器人 + +### 7.3 etl_alert_record - 告警记录表 + +**用途**: 记录触发的告警 + +**核心功能**: +- 告警历史追溯 +- 告警状态管理(已解决/未解决) +- 通知发送状态跟踪 + +## 8. 表关系ER图 + +```mermaid +erDiagram + etl_job ||--o{ etl_job_instance : "1:N 一个任务多次运行" + etl_job ||--|| etl_job_schedule : "1:1 一个任务一个调度" + etl_job ||--|| etl_stream_graph : "1:1 一个任务一个图" + + etl_job_instance ||--o{ etl_checkpoint : "1:N 一次运行多个检查点" + etl_job_instance ||--o{ etl_job_metrics : "1:N 一次运行多条指标" + + etl_connector ||--o{ etl_datasource : "1:N 一个连接器多个数据源" + + etl_alert_rule ||--o{ etl_alert_record : "1:N 一个规则多条记录" + + etl_user ||--o{ etl_operation_log : "1:N 一个用户多条日志" +``` + +## 9. 核心视图 + +### 9.1 v_job_instance_stats - 任务实例统计视图 + +**用途**: 快速查询任务的执行统计信息 + +```sql +SELECT * FROM v_job_instance_stats WHERE job_id = 'xxx'; +``` + +**返回字段**: +- total_runs: 总运行次数 +- success_runs: 成功次数 +- failed_runs: 失败次数 +- avg_duration_ms: 平均执行时长 +- last_run_time: 最后运行时间 + +### 9.2 v_running_jobs - 当前运行任务视图 + +**用途**: 查看当前正在运行的任务 + +```sql +SELECT * FROM v_running_jobs ORDER BY start_time DESC; +``` + +**返回字段**: +- instance_id: 实例ID +- job_name: 任务名称 +- running_seconds: 已运行秒数 +- records_read/processed/written: 实时统计 + +## 10. 索引策略 + +### 10.1 主键索引 +所有表使用自增主键`id`,提供快速行定位。 + +### 10.2 唯一索引 +业务唯一标识字段: +- job_id, instance_id, checkpoint_id等 +- 保证数据唯一性,避免重复 + +### 10.3 查询索引 + +**高频查询字段**: +```sql +-- 任务状态查询 +KEY `idx_job_status` (`job_status`) + +-- 时间范围查询 +KEY `idx_start_time` (`start_time`) + +-- 关联查询 +KEY `idx_job_id` (`job_id`) +``` + +**组合索引**(根据实际查询优化): +```sql +-- 任务实例查询 +ALTER TABLE etl_job_instance +ADD INDEX idx_job_status_time (job_id, instance_status, start_time); + +-- 指标查询 +ALTER TABLE etl_job_metrics +ADD INDEX idx_instance_metric_time (instance_id, metric_time); +``` + +## 11. 分区策略 + +对于数据量大的表,建议按时间分区: + +### 11.1 指标表分区 + +```sql +ALTER TABLE etl_job_metrics +PARTITION BY RANGE (TO_DAYS(metric_time)) ( + PARTITION p202501 VALUES LESS THAN (TO_DAYS('2025-02-01')), + PARTITION p202502 VALUES LESS THAN (TO_DAYS('2025-03-01')), + PARTITION p202503 VALUES LESS THAN (TO_DAYS('2025-04-01')), + PARTITION p_future VALUES LESS THAN MAXVALUE +); +``` + +### 11.2 日志表分区 + +```sql +ALTER TABLE etl_operation_log +PARTITION BY RANGE (TO_DAYS(operation_time)) ( + PARTITION p202501 VALUES LESS THAN (TO_DAYS('2025-02-01')), + PARTITION p202502 VALUES LESS THAN (TO_DAYS('2025-03-01')), + PARTITION p_future VALUES LESS THAN MAXVALUE +); +``` + +### 11.3 分区维护 + +```sql +-- 添加新分区 +ALTER TABLE etl_job_metrics +ADD PARTITION (PARTITION p202504 VALUES LESS THAN (TO_DAYS('2025-05-01'))); + +-- 删除旧分区(保留6个月) +ALTER TABLE etl_job_metrics DROP PARTITION p202410; +``` + +## 12. 数据保留策略 + +| 表名 | 保留时长 | 清理策略 | +| --- | --- | --- | +| etl_job | 永久(软删除) | 定期归档已删除任务 | +| etl_job_instance | 6个月 | 归档旧数据或删除 | +| etl_checkpoint | 最近5个 | 自动清理旧检查点 | +| etl_job_metrics | 30天 | 删除或聚合存储 | +| etl_alert_record | 6个月 | 归档历史告警 | +| etl_operation_log | 1年 | 归档审计日志 | + +## 13. 性能优化建议 + +### 13.1 查询优化 +- 避免SELECT *,只查询需要的字段 +- 合理使用LIMIT限制结果集 +- 索引覆盖查询,避免回表 +- 大表JOIN使用索引字段 + +### 13.2 写入优化 +- 批量插入代替单条插入 +- 使用LOAD DATA INFILE导入大量数据 +- 异步写入指标和日志 +- 定期执行OPTIMIZE TABLE + +### 13.3 JSON字段使用 +- 不要在JSON字段上建索引 +- 避免在WHERE条件中使用JSON函数 +- 考虑将高频查询字段提取为独立列 + +### 13.4 连接池配置 +```properties +# HikariCP推荐配置 +maximumPoolSize=20 +minimumIdle=5 +connectionTimeout=30000 +idleTimeout=600000 +maxLifetime=1800000 +``` + +## 14. 安全考虑 + +### 14.1 敏感数据加密 +```java +// 密码加密示例 +String encrypted = AESUtil.encrypt(password, secretKey); + +// BCrypt密码哈希 +String hashed = BCrypt.hashpw(password, BCrypt.gensalt()); +``` + +### 14.2 SQL注入防护 +- 使用PreparedStatement +- 参数化查询 +- 输入验证和过滤 + +### 14.3 访问控制 +- 应用层使用专用数据库账号 +- 最小权限原则 +- 定期审计数据库访问日志 + +## 15. 备份恢复 + +### 15.1 备份策略 + +**全量备份(每日)**: +```bash +mysqldump -u root -p --single-transaction \ + --master-data=2 \ + etl_framework > backup_$(date +%Y%m%d).sql +``` + +**增量备份(实时)**: +```bash +# 开启binlog +[mysqld] +log-bin=mysql-bin +binlog_format=ROW +expire_logs_days=7 +``` + +### 15.2 恢复演练 + +**恢复全量备份**: +```bash +mysql -u root -p etl_framework < backup_20250109.sql +``` + +**恢复到指定时间点**: +```bash +mysqlbinlog --start-datetime="2025-01-09 10:00:00" \ + --stop-datetime="2025-01-09 11:00:00" \ + mysql-bin.000001 | mysql -u root -p etl_framework +``` + +## 16. 初始化步骤 + +### 步骤1: 创建数据库 + +```sql +CREATE DATABASE etl_framework +DEFAULT CHARACTER SET utf8mb4 +COLLATE utf8mb4_unicode_ci; +``` + +### 步骤2: 执行建表脚本 + +```bash +mysql -u root -p etl_framework < docs/database-schema.sql +``` + +### 步骤3: 验证初始化 + +```sql +-- 查看表数量(应该是13张表) +SELECT COUNT(*) FROM information_schema.tables +WHERE table_schema = 'etl_framework'; + +-- 查看内置连接器 +SELECT connector_id, connector_name, connector_type +FROM etl_connector WHERE is_builtin = 1; + +-- 查看系统配置 +SELECT config_key, config_value, config_group +FROM etl_system_config; +``` + +## 17. 常见问题 + +### Q1: 为什么不使用分布式架构? +**A**: 单机架构更简单,适合中小规模数据处理。降低了系统复杂度,更容易运维和调试。对于大规模数据处理,可以通过水平扩展多个独立实例实现。 + +### Q2: 如何实现Job的水平扩展? +**A**: 可以部署多个ETL实例,每个实例运行不同的Job。通过调度器分配Job到不同实例,实现简单的负载均衡。 + +### Q3: 检查点数据存储在哪里? +**A**: +- 小状态(<1MB): 直接存储在数据库的state_snapshot字段 +- 大状态(>1MB): 存储在文件系统,数据库记录路径 + +### Q4: 如何处理Job失败? +**A**: +1. 根据restart_strategy自动重启 +2. 从最后一个成功的checkpoint恢复 +3. 触发告警通知相关人员 +4. 记录详细的错误信息和堆栈 + +### Q5: 表结构如何升级? +**A**: +1. 使用版本控制管理SQL脚本 +2. 使用Flyway或Liquibase进行数据库迁移 +3. 保持向后兼容,使用ALTER TABLE而非DROP TABLE +4. 在测试环境充分验证后再上生产 + +--- + +**文档版本**: v2.0(单机版) +**最后更新**: 2025-11-09 +**维护者**: ETL Framework Team diff --git a/docs/database-schema.sql b/docs/database-schema.sql new file mode 100644 index 000000000..ca5fb1ab0 --- /dev/null +++ b/docs/database-schema.sql @@ -0,0 +1,420 @@ +-- ============================================= +-- 响应式ETL框架 - 数据库表结构设计(单机版) +-- 版本: v2.0 +-- 创建日期: 2025-11-09 +-- 说明: 单机执行模式,一个Job作为最小执行单元 +-- ============================================= + +-- ============================================= +-- 1. 任务管理相关表 +-- ============================================= + +-- 1.1 任务定义表 +CREATE TABLE `etl_job` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `job_id` VARCHAR(64) NOT NULL COMMENT '任务唯一标识', + `job_name` VARCHAR(128) NOT NULL COMMENT '任务名称', + `job_type` VARCHAR(32) NOT NULL COMMENT '任务类型: STREAMING/BATCH', + `job_status` VARCHAR(32) NOT NULL DEFAULT 'CREATED' COMMENT '任务状态: CREATED/SCHEDULED/RUNNING/PAUSED/COMPLETED/FAILED/CANCELLED', + `description` TEXT COMMENT '任务描述', + `stream_graph_id` VARCHAR(64) COMMENT 'StreamGraph ID', + `restart_strategy` VARCHAR(32) DEFAULT 'FIXED_DELAY' COMMENT '重启策略: FIXED_DELAY/EXPONENTIAL_BACKOFF/NO_RESTART', + `restart_attempts` INT DEFAULT 3 COMMENT '最大重启次数', + `restart_delay_seconds` INT DEFAULT 10 COMMENT '重启延迟(秒)', + `checkpoint_enabled` TINYINT DEFAULT 1 COMMENT '是否启用检查点: 0-否, 1-是', + `checkpoint_interval_seconds` INT DEFAULT 60 COMMENT '检查点间隔(秒)', + `source_config` JSON COMMENT 'Source配置(JSON)', + `operators_config` JSON COMMENT 'Operators配置列表(JSON)', + `sink_config` JSON COMMENT 'Sink配置(JSON)', + `job_config` JSON COMMENT '任务全局配置(JSON)', + `creator` VARCHAR(64) COMMENT '创建人', + `updater` VARCHAR(64) COMMENT '更新人', + `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `is_deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '是否删除: 0-否, 1-是', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_job_id` (`job_id`), + KEY `idx_job_name` (`job_name`), + KEY `idx_job_status` (`job_status`), + KEY `idx_create_time` (`create_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='ETL任务定义表'; + +-- 1.2 任务实例表(记录每个Job的运行实例) +CREATE TABLE `etl_job_instance` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `instance_id` VARCHAR(64) NOT NULL COMMENT '实例ID', + `job_id` VARCHAR(64) NOT NULL COMMENT '任务ID', + `job_name` VARCHAR(128) NOT NULL COMMENT '任务名称', + `instance_status` VARCHAR(32) NOT NULL COMMENT '实例状态: RUNNING/COMPLETED/FAILED/CANCELLED', + `host_address` VARCHAR(128) COMMENT '运行主机地址', + `process_id` VARCHAR(64) COMMENT '进程ID', + `start_time` DATETIME NOT NULL COMMENT '开始时间', + `end_time` DATETIME COMMENT '结束时间', + `duration_ms` BIGINT COMMENT '执行时长(毫秒)', + `records_read` BIGINT DEFAULT 0 COMMENT '读取记录数', + `records_processed` BIGINT DEFAULT 0 COMMENT '处理记录数', + `records_written` BIGINT DEFAULT 0 COMMENT '写入记录数', + `records_filtered` BIGINT DEFAULT 0 COMMENT '过滤记录数', + `records_failed` BIGINT DEFAULT 0 COMMENT '失败记录数', + `error_message` TEXT COMMENT '错误信息', + `error_stack_trace` TEXT COMMENT '错误堆栈', + `last_checkpoint_id` VARCHAR(64) COMMENT '最后检查点ID', + `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_instance_id` (`instance_id`), + KEY `idx_job_id` (`job_id`), + KEY `idx_status` (`instance_status`), + KEY `idx_start_time` (`start_time`), + KEY `idx_host` (`host_address`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='任务实例表'; + +-- 1.3 任务调度配置表 +CREATE TABLE `etl_job_schedule` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `schedule_id` VARCHAR(64) NOT NULL COMMENT '调度ID', + `job_id` VARCHAR(64) NOT NULL COMMENT '任务ID', + `schedule_type` VARCHAR(32) NOT NULL COMMENT '调度类型: IMMEDIATE/CRON/MANUAL', + `schedule_enabled` TINYINT NOT NULL DEFAULT 1 COMMENT '是否启用: 0-否, 1-是', + `cron_expression` VARCHAR(128) COMMENT 'Cron表达式', + `timezone` VARCHAR(64) DEFAULT 'Asia/Shanghai' COMMENT '时区', + `next_fire_time` DATETIME COMMENT '下次触发时间', + `last_fire_time` DATETIME COMMENT '上次触发时间', + `fire_count` BIGINT DEFAULT 0 COMMENT '触发次数', + `creator` VARCHAR(64) COMMENT '创建人', + `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_schedule_id` (`schedule_id`), + UNIQUE KEY `uk_job_id` (`job_id`), + KEY `idx_schedule_type` (`schedule_type`), + KEY `idx_next_fire_time` (`next_fire_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='任务调度配置表'; + +-- ============================================= +-- 2. 图结构相关表(简化) +-- ============================================= + +-- 2.1 StreamGraph表 +CREATE TABLE `etl_stream_graph` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `graph_id` VARCHAR(64) NOT NULL COMMENT '图ID', + `graph_name` VARCHAR(128) NOT NULL COMMENT '图名称', + `job_id` VARCHAR(64) COMMENT '关联任务ID', + `graph_definition` JSON NOT NULL COMMENT '图定义(完整的节点和边JSON)', + `description` TEXT COMMENT '描述', + `creator` VARCHAR(64) COMMENT '创建人', + `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_graph_id` (`graph_id`), + KEY `idx_job_id` (`job_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='StreamGraph定义表'; + +-- ============================================= +-- 3. 连接器配置相关表 +-- ============================================= + +-- 3.1 连接器注册表 +CREATE TABLE `etl_connector` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `connector_id` VARCHAR(64) NOT NULL COMMENT '连接器ID', + `connector_name` VARCHAR(128) NOT NULL COMMENT '连接器名称', + `connector_type` VARCHAR(64) NOT NULL COMMENT '连接器类型: JDBC/KAFKA/HTTP/FILE/REDIS/ELASTICSEARCH等', + `connector_class` VARCHAR(256) NOT NULL COMMENT '连接器实现类全限定名', + `version` VARCHAR(32) DEFAULT '1.0.0' COMMENT '版本号', + `description` TEXT COMMENT '描述', + `support_source` TINYINT DEFAULT 0 COMMENT '是否支持Source: 0-否, 1-是', + `support_sink` TINYINT DEFAULT 0 COMMENT '是否支持Sink: 0-否, 1-是', + `config_schema` JSON COMMENT '配置Schema定义(JSON Schema)', + `is_builtin` TINYINT DEFAULT 0 COMMENT '是否内置: 0-否, 1-是', + `is_enabled` TINYINT DEFAULT 1 COMMENT '是否启用: 0-否, 1-是', + `creator` VARCHAR(64) COMMENT '创建人', + `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_connector_id` (`connector_id`), + KEY `idx_connector_type` (`connector_type`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='连接器注册表'; + +-- 3.2 数据源配置表 +CREATE TABLE `etl_datasource` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `datasource_id` VARCHAR(64) NOT NULL COMMENT '数据源ID', + `datasource_name` VARCHAR(128) NOT NULL COMMENT '数据源名称', + `connector_id` VARCHAR(64) NOT NULL COMMENT '连接器ID', + `datasource_type` VARCHAR(64) NOT NULL COMMENT '数据源类型', + `connection_config` JSON NOT NULL COMMENT '连接配置(JSON)', + `description` TEXT COMMENT '描述', + `is_enabled` TINYINT DEFAULT 1 COMMENT '是否启用: 0-否, 1-是', + `creator` VARCHAR(64) COMMENT '创建人', + `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_datasource_id` (`datasource_id`), + KEY `idx_connector_id` (`connector_id`), + KEY `idx_datasource_name` (`datasource_name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='数据源配置表'; + +-- ============================================= +-- 4. 检查点相关表(简化) +-- ============================================= + +-- 4.1 检查点表 +CREATE TABLE `etl_checkpoint` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `checkpoint_id` VARCHAR(64) NOT NULL COMMENT '检查点ID', + `job_id` VARCHAR(64) NOT NULL COMMENT '任务ID', + `instance_id` VARCHAR(64) NOT NULL COMMENT '实例ID', + `checkpoint_type` VARCHAR(32) DEFAULT 'AUTO' COMMENT '检查点类型: AUTO/MANUAL', + `checkpoint_status` VARCHAR(32) NOT NULL COMMENT '状态: IN_PROGRESS/COMPLETED/FAILED', + `trigger_time` DATETIME NOT NULL COMMENT '触发时间', + `complete_time` DATETIME COMMENT '完成时间', + `duration_ms` BIGINT COMMENT '耗时(毫秒)', + `state_size_bytes` BIGINT COMMENT '状态大小(字节)', + `storage_path` VARCHAR(512) COMMENT '存储路径', + `state_snapshot` JSON COMMENT '状态快照(小状态直接存储)', + `error_message` TEXT COMMENT '错误信息', + `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_checkpoint_id` (`checkpoint_id`), + KEY `idx_job_id` (`job_id`), + KEY `idx_instance_id` (`instance_id`), + KEY `idx_trigger_time` (`trigger_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='检查点表'; + +-- ============================================= +-- 5. 监控指标相关表(简化) +-- ============================================= + +-- 5.1 任务运行指标表 +CREATE TABLE `etl_job_metrics` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `job_id` VARCHAR(64) NOT NULL COMMENT '任务ID', + `instance_id` VARCHAR(64) NOT NULL COMMENT '实例ID', + `metric_time` DATETIME NOT NULL COMMENT '指标时间', + `records_read_total` BIGINT DEFAULT 0 COMMENT '累计读取记录数', + `records_processed_total` BIGINT DEFAULT 0 COMMENT '累计处理记录数', + `records_written_total` BIGINT DEFAULT 0 COMMENT '累计写入记录数', + `records_read_rate` DECIMAL(20,2) DEFAULT 0 COMMENT '读取速率(记录/秒)', + `records_write_rate` DECIMAL(20,2) DEFAULT 0 COMMENT '写入速率(记录/秒)', + `processing_latency_ms` BIGINT DEFAULT 0 COMMENT '处理延迟(毫秒)', + `backpressure_count` INT DEFAULT 0 COMMENT '背压次数', + `error_count` INT DEFAULT 0 COMMENT '错误次数', + `checkpoint_count` INT DEFAULT 0 COMMENT '检查点次数', + `restart_count` INT DEFAULT 0 COMMENT '重启次数', + `jvm_heap_used_mb` DECIMAL(10,2) COMMENT 'JVM堆内存使用(MB)', + `jvm_heap_max_mb` DECIMAL(10,2) COMMENT 'JVM堆内存最大(MB)', + `cpu_usage_percent` DECIMAL(5,2) COMMENT 'CPU使用率(%)', + `thread_count` INT COMMENT '线程数', + `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`), + KEY `idx_job_id` (`job_id`), + KEY `idx_instance_id` (`instance_id`), + KEY `idx_metric_time` (`metric_time`), + KEY `idx_job_metric_time` (`job_id`, `metric_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='任务运行指标表'; + +-- ============================================= +-- 6. 系统配置和告警相关表 +-- ============================================= + +-- 6.1 系统配置表 +CREATE TABLE `etl_system_config` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `config_key` VARCHAR(128) NOT NULL COMMENT '配置Key', + `config_value` TEXT NOT NULL COMMENT '配置Value', + `config_type` VARCHAR(32) NOT NULL COMMENT '配置类型: STRING/INT/BOOLEAN/JSON', + `config_group` VARCHAR(64) COMMENT '配置分组: SYSTEM/EXECUTOR/CHECKPOINT/METRICS', + `description` TEXT COMMENT '描述', + `is_encrypted` TINYINT DEFAULT 0 COMMENT '是否加密: 0-否, 1-是', + `is_readonly` TINYINT DEFAULT 0 COMMENT '是否只读: 0-否, 1-是', + `updater` VARCHAR(64) COMMENT '更新人', + `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_config_key` (`config_key`), + KEY `idx_config_group` (`config_group`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统配置表'; + +-- 6.2 告警规则表 +CREATE TABLE `etl_alert_rule` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `rule_id` VARCHAR(64) NOT NULL COMMENT '规则ID', + `rule_name` VARCHAR(128) NOT NULL COMMENT '规则名称', + `rule_type` VARCHAR(32) NOT NULL COMMENT '规则类型: JOB_FAILED/JOB_TIMEOUT/HIGH_ERROR_RATE/CHECKPOINT_FAILED', + `job_id` VARCHAR(64) COMMENT '目标任务ID(空表示所有任务)', + `condition_expression` TEXT COMMENT '条件表达式', + `alert_level` VARCHAR(32) NOT NULL DEFAULT 'WARNING' COMMENT '告警级别: INFO/WARNING/ERROR/CRITICAL', + `notification_channels` VARCHAR(256) COMMENT '通知渠道(逗号分隔): EMAIL/SMS/WEBHOOK/DINGTALK', + `notification_config` JSON COMMENT '通知配置(JSON)', + `is_enabled` TINYINT DEFAULT 1 COMMENT '是否启用: 0-否, 1-是', + `creator` VARCHAR(64) COMMENT '创建人', + `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_rule_id` (`rule_id`), + KEY `idx_rule_type` (`rule_type`), + KEY `idx_job_id` (`job_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='告警规则表'; + +-- 6.3 告警记录表 +CREATE TABLE `etl_alert_record` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `alert_id` VARCHAR(64) NOT NULL COMMENT '告警ID', + `rule_id` VARCHAR(64) NOT NULL COMMENT '规则ID', + `rule_name` VARCHAR(128) NOT NULL COMMENT '规则名称', + `alert_level` VARCHAR(32) NOT NULL COMMENT '告警级别', + `job_id` VARCHAR(64) COMMENT '任务ID', + `instance_id` VARCHAR(64) COMMENT '实例ID', + `alert_time` DATETIME NOT NULL COMMENT '告警时间', + `alert_message` TEXT NOT NULL COMMENT '告警消息', + `alert_context` JSON COMMENT '告警上下文(JSON)', + `is_resolved` TINYINT DEFAULT 0 COMMENT '是否已解决: 0-否, 1-是', + `resolve_time` DATETIME COMMENT '解决时间', + `notification_status` VARCHAR(32) COMMENT '通知状态: PENDING/SENT/FAILED', + `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_alert_id` (`alert_id`), + KEY `idx_rule_id` (`rule_id`), + KEY `idx_job_id` (`job_id`), + KEY `idx_alert_time` (`alert_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='告警记录表'; + +-- ============================================= +-- 7. 用户和审计相关表 +-- ============================================= + +-- 7.1 用户表 +CREATE TABLE `etl_user` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `user_id` VARCHAR(64) NOT NULL COMMENT '用户ID', + `username` VARCHAR(64) NOT NULL COMMENT '用户名', + `password` VARCHAR(128) COMMENT '密码(加密)', + `email` VARCHAR(128) COMMENT '邮箱', + `phone` VARCHAR(32) COMMENT '手机号', + `real_name` VARCHAR(64) COMMENT '真实姓名', + `role` VARCHAR(32) DEFAULT 'USER' COMMENT '角色: ADMIN/USER', + `status` VARCHAR(32) DEFAULT 'ACTIVE' COMMENT '状态: ACTIVE/INACTIVE', + `last_login_time` DATETIME COMMENT '最后登录时间', + `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_user_id` (`user_id`), + UNIQUE KEY `uk_username` (`username`), + KEY `idx_email` (`email`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; + +-- 7.2 操作日志表 +CREATE TABLE `etl_operation_log` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `log_id` VARCHAR(64) NOT NULL COMMENT '日志ID', + `user_id` VARCHAR(64) COMMENT '用户ID', + `username` VARCHAR(64) COMMENT '用户名', + `operation_type` VARCHAR(64) NOT NULL COMMENT '操作类型: CREATE/UPDATE/DELETE/START/STOP/RESTART', + `resource_type` VARCHAR(32) NOT NULL COMMENT '资源类型: JOB/DATASOURCE/SCHEDULE', + `resource_id` VARCHAR(64) COMMENT '资源ID', + `operation_desc` TEXT COMMENT '操作描述', + `request_params` JSON COMMENT '请求参数(JSON)', + `operation_status` VARCHAR(32) NOT NULL COMMENT '操作状态: SUCCESS/FAILED', + `error_message` TEXT COMMENT '错误信息', + `ip_address` VARCHAR(64) COMMENT 'IP地址', + `operation_time` DATETIME NOT NULL COMMENT '操作时间', + `duration_ms` BIGINT COMMENT '耗时(毫秒)', + `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_log_id` (`log_id`), + KEY `idx_user_id` (`user_id`), + KEY `idx_resource` (`resource_type`, `resource_id`), + KEY `idx_operation_time` (`operation_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='操作日志表'; + +-- ============================================= +-- 初始化数据 +-- ============================================= + +-- 插入内置连接器 +INSERT INTO `etl_connector` (`connector_id`, `connector_name`, `connector_type`, `connector_class`, `version`, `description`, `support_source`, `support_sink`, `is_builtin`, `is_enabled`, `creator`) VALUES +('jdbc-connector', 'JDBC Connector', 'JDBC', 'com.etl.connector.jdbc.JdbcConnector', '1.0.0', 'JDBC数据库连接器,支持MySQL、PostgreSQL、Oracle等', 1, 1, 1, 1, 'system'), +('kafka-connector', 'Kafka Connector', 'KAFKA', 'com.etl.connector.kafka.KafkaConnector', '1.0.0', 'Apache Kafka消息队列连接器', 1, 1, 1, 1, 'system'), +('http-connector', 'HTTP Connector', 'HTTP', 'com.etl.connector.http.HttpConnector', '1.0.0', 'HTTP/HTTPS API连接器', 1, 1, 1, 1, 'system'), +('file-connector', 'File Connector', 'FILE', 'com.etl.connector.file.FileConnector', '1.0.0', '文件系统连接器,支持CSV、JSON、Parquet等格式', 1, 1, 1, 1, 'system'), +('redis-connector', 'Redis Connector', 'REDIS', 'com.etl.connector.redis.RedisConnector', '1.0.0', 'Redis缓存连接器', 1, 1, 1, 1, 'system'), +('elasticsearch-connector', 'Elasticsearch Connector', 'ELASTICSEARCH', 'com.etl.connector.es.ElasticsearchConnector', '1.0.0', 'Elasticsearch搜索引擎连接器', 1, 1, 1, 1, 'system'); + +-- 插入系统配置 +INSERT INTO `etl_system_config` (`config_key`, `config_value`, `config_type`, `config_group`, `description`) VALUES +('system.thread.pool.core.size', '10', 'INT', 'EXECUTOR', '执行器线程池核心大小'), +('system.thread.pool.max.size', '50', 'INT', 'EXECUTOR', '执行器线程池最大大小'), +('system.thread.pool.queue.capacity', '1000', 'INT', 'EXECUTOR', '线程池队列容量'), +('system.checkpoint.enabled', 'true', 'BOOLEAN', 'CHECKPOINT', '全局是否启用检查点'), +('system.checkpoint.interval.seconds', '60', 'INT', 'CHECKPOINT', '默认检查点间隔(秒)'), +('system.checkpoint.storage.path', '/data/checkpoints', 'STRING', 'CHECKPOINT', '检查点存储路径'), +('system.checkpoint.retention.count', '5', 'INT', 'CHECKPOINT', '保留检查点数量'), +('system.metrics.enabled', 'true', 'BOOLEAN', 'METRICS', '是否启用监控指标采集'), +('system.metrics.collect.interval.seconds', '10', 'INT', 'METRICS', '指标采集间隔(秒)'), +('system.scheduler.enabled', 'true', 'BOOLEAN', 'SYSTEM', '是否启用调度器'), +('system.restart.max.attempts', '3', 'INT', 'EXECUTOR', '默认最大重启次数'); + +-- 插入默认告警规则 +INSERT INTO `etl_alert_rule` (`rule_id`, `rule_name`, `rule_type`, `alert_level`, `condition_expression`, `is_enabled`, `creator`) VALUES +('alert-job-failed', '任务失败告警', 'JOB_FAILED', 'ERROR', 'instance_status == FAILED', 1, 'system'), +('alert-job-timeout', '任务超时告警', 'JOB_TIMEOUT', 'WARNING', 'duration_ms > 3600000', 1, 'system'), +('alert-high-error-rate', '高错误率告警', 'HIGH_ERROR_RATE', 'WARNING', 'error_count / records_read_total > 0.01', 1, 'system'), +('alert-checkpoint-failed', '检查点失败告警', 'CHECKPOINT_FAILED', 'WARNING', 'checkpoint_status == FAILED', 1, 'system'); + +-- 插入默认管理员用户(密码: admin123,需要BCrypt加密) +INSERT INTO `etl_user` (`user_id`, `username`, `password`, `email`, `real_name`, `role`, `status`) VALUES +('user-admin', 'admin', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iKTVKIUi', 'admin@example.com', '系统管理员', 'ADMIN', 'ACTIVE'); + +-- ============================================= +-- 视图定义(方便查询) +-- ============================================= + +-- 任务实例统计视图 +CREATE OR REPLACE VIEW `v_job_instance_stats` AS +SELECT + j.job_id, + j.job_name, + j.job_type, + j.job_status, + COUNT(i.id) as total_runs, + SUM(CASE WHEN i.instance_status = 'COMPLETED' THEN 1 ELSE 0 END) as success_runs, + SUM(CASE WHEN i.instance_status = 'FAILED' THEN 1 ELSE 0 END) as failed_runs, + AVG(i.duration_ms) as avg_duration_ms, + MAX(i.start_time) as last_run_time +FROM etl_job j +LEFT JOIN etl_job_instance i ON j.job_id = i.job_id +WHERE j.is_deleted = 0 +GROUP BY j.job_id, j.job_name, j.job_type, j.job_status; + +-- 当前运行任务视图 +CREATE OR REPLACE VIEW `v_running_jobs` AS +SELECT + i.instance_id, + i.job_id, + i.job_name, + i.instance_status, + i.host_address, + i.start_time, + TIMESTAMPDIFF(SECOND, i.start_time, NOW()) as running_seconds, + i.records_read, + i.records_processed, + i.records_written +FROM etl_job_instance i +WHERE i.instance_status = 'RUNNING' +ORDER BY i.start_time DESC; + +-- ============================================= +-- 索引优化建议(根据实际查询调整) +-- ============================================= +-- ALTER TABLE `etl_job_instance` ADD INDEX `idx_job_status_time` (`job_id`, `instance_status`, `start_time`); +-- ALTER TABLE `etl_job_metrics` ADD INDEX `idx_instance_metric_time` (`instance_id`, `metric_time`); + +-- ============================================= +-- 分区建议(数据量大时) +-- ============================================= +-- ALTER TABLE `etl_job_metrics` PARTITION BY RANGE (TO_DAYS(metric_time)) ( +-- PARTITION p202501 VALUES LESS THAN (TO_DAYS('2025-02-01')), +-- PARTITION p202502 VALUES LESS THAN (TO_DAYS('2025-03-01')), +-- PARTITION p_future VALUES LESS THAN MAXVALUE +-- ); diff --git a/docs/enterprise-cdp-design.md b/docs/enterprise-cdp-design.md new file mode 100644 index 000000000..3f18eb5b4 --- /dev/null +++ b/docs/enterprise-cdp-design.md @@ -0,0 +1,1499 @@ +# 企业级CDP(客户数据平台)设计方案 + +## 目录 + +- [一、方案概述](#一方案概述) + - [1.1 背景与目标](#11-背景与目标) + - [1.2 核心能力](#12-核心能力) +- [二、整体架构设计](#二整体架构设计) + - [2.1 架构分层](#21-架构分层) + - [2.2 核心数据流](#22-核心数据流) +- [三、数据采集平台](#三数据采集平台) + - [3.1 数据源接入](#31-数据源接入) + - [3.1.1 CRM系统接入](#311-crm系统接入) + - [3.1.2 企业微信接入](#312-企业微信接入) + - [3.1.3 第三方数据接入](#313-第三方数据接入) + - [3.2 数据采集架构](#32-数据采集架构) + - [3.3 数据采集SDK设计](#33-数据采集sdk设计) +- [四、OneID服务(统一身份识别)](#四oneid服务统一身份识别) + - [4.1 核心挑战](#41-核心挑战) + - [4.2 ID-Mapping策略](#42-id-mapping策略) + - [4.2.1 匹配规则](#421-匹配规则) + - [4.2.2 匹配算法流程](#422-匹配算法流程) + - [4.3 OneID数据模型](#43-oneid数据模型) + - [4.4 OneID服务能力](#44-oneid服务能力) +- [五、组织架构模型设计](#五组织架构模型设计) + - [5.1 数据模型](#51-数据模型) + - [5.1.1 客户企业层级模型](#511-客户企业层级模型) + - [5.1.2 联系人组织关系模型](#512-联系人组织关系模型) + - [5.2 组织关系查询能力](#52-组织关系查询能力) + - [5.2.1 核心查询场景](#521-核心查询场景) + - [5.2.2 图数据库优化](#522-图数据库优化) + - [5.3 组织架构可视化](#53-组织架构可视化) +- [六、数据建模与存储](#六数据建模与存储) + - [6.1 数据分层架构](#61-数据分层架构) + - [6.2 核心数据模型](#62-核心数据模型) + - [6.2.1 客户企业主题](#621-客户企业主题) + - [6.2.2 联系人主题](#622-联系人主题) + - [6.2.3 行为事件主题](#623-行为事件主题) + - [6.3 存储选型](#63-存储选型) +- [七、标签体系设计](#七标签体系设计) + - [7.1 标签分类](#71-标签分类) + - [7.1.1 客户企业标签](#711-客户企业标签) + - [7.1.2 联系人标签](#712-联系人标签) + - [7.1.3 组织关系标签](#713-组织关系标签) + - [7.2 标签计算引擎](#72-标签计算引擎) + - [7.2.1 标签类型](#721-标签类型) + - [7.2.2 标签计算流程](#722-标签计算流程) + - [7.3 标签存储方案](#73-标签存储方案) +- [八、圈人能力设计](#八圈人能力设计) + - [8.1 圈人场景](#81-圈人场景) + - [8.2 圈人条件](#82-圈人条件) + - [8.2.1 基础条件](#821-基础条件) + - [8.2.2 组织关系条件](#822-组织关系条件) + - [8.3 圈人引擎](#83-圈人引擎) + - [8.3.1 查询优化](#831-查询优化) + - [8.3.2 圈人服务能力](#832-圈人服务能力) + - [8.4 圈人结果应用](#84-圈人结果应用) +- [九、数据安全与合规](#九数据安全与合规) + - [9.1 合规要求](#91-合规要求) + - [9.2 数据脱敏策略](#92-数据脱敏策略) + - [9.2.1 脱敏字段](#921-脱敏字段) + - [9.2.2 加密存储](#922-加密存储) + - [9.3 权限控制](#93-权限控制) + - [9.3.1 RBAC模型](#931-rbac模型) + - [9.3.2 数据权限](#932-数据权限) + - [9.4 审计日志](#94-审计日志) +- [十、技术选型与架构](#十技术选型与架构) + - [10.1 技术栈选型](#101-技术栈选型) + - [10.1.1 后端技术栈](#1011-后端技术栈) + - [10.1.2 前端技术栈](#1012-前端技术栈) + - [10.2 部署架构](#102-部署架构) + - [10.2.1 云上部署(推荐)](#1021-云上部署推荐) + - [10.2.2 私有化部署](#1022-私有化部署) + - [10.3 核心服务设计](#103-核心服务设计) + - [10.3.1 服务拆分](#1031-服务拆分) + - [10.3.2 服务通信](#1032-服务通信) +- [十一、系统实施路线图](#十一系统实施路线图) + - [11.1 Phase 1: MVP版本 (2-3个月)](#111-phase-1-mvp版本-2-3个月) + - [11.2 Phase 2: 增强版本 (3-4个月)](#112-phase-2-增强版本-3-4个月) + - [11.3 Phase 3: 智能版本 (4-6个月)](#113-phase-3-智能版本-4-6个月) +- [十二、成功案例参考](#十二成功案例参考) + - [12.1 客户画像场景](#121-客户画像场景) + - [12.2 组织穿透场景](#122-组织穿透场景) + - [12.3 精准营销场景](#123-精准营销场景) +- [十三、总结与展望](#十三总结与展望) + - [13.1 企业级CDP与C端CDP的核心差异](#131-企业级cdp与c端cdp的核心差异) + - [13.2 关键成功要素](#132-关键成功要素) + - [13.3 未来演进方向](#133-未来演进方向) +- [附录](#附录) + - [附录A:关键指标定义](#附录a关键指标定义) + - [附录B:核心服务接口](#附录b核心服务接口) + +--- + +## 一、方案概述 + +[返回目录](#目录) + +### 1.1 背景与目标 + +本方案针对B2B场景下的企业级客户数据平台(CDP),核心特点是: +- **客户主体是企业**:不同于C端CDP,需要管理企业客户及其内部联系人 +- **组织架构关系**:支持企业客户的组织架构管理,可追溯上下级、同事等关系 +- **销售场景驱动**:销售人员通过企业微信等渠道与客户互动,需要完整的客户画像支持 + +### 1.2 核心能力 + +- **全渠道数据采集**:整合CRM、企业微信、第三方数据等多源数据 +- **OneID统一身份**:打通多渠道的客户身份,形成360度客户视图 +- **组织架构管理**:支持企业客户的组织层级关系,快速定位关键决策人 +- **智能标签体系**:基于行为、属性、组织关系的多维标签 +- **精准圈人能力**:支持基于标签、组织关系的客户筛选 +- **数据安全合规**:符合个人信息保护法、数据安全法等监管要求 + +--- + +## 二、整体架构设计 + +[返回目录](#目录) + +### 2.1 架构分层 + +整体架构采用经典的五层架构模式,从下至上分别为:数据采集层、数据存储层、数据处理层、服务层、应用层。 + +```mermaid +graph TB + subgraph 应用层 + A1[销售工作台] + A2[营销平台] + A3[BI分析] + A4[API网关] + end + + subgraph 服务层 + S1[圈人服务] + S2[标签服务] + S3[画像服务] + S4[组织服务] + S5[OneID服务] + S6[埋点服务] + S7[数据质量] + S8[权限服务] + end + + subgraph 数据处理层 + P1[实时计算-Flink] + P2[离线计算-Spark] + P3[标签计算] + P4[数据清洗] + end + + subgraph 数据存储层 + D1[MySQL-元数据] + D2[ES-搜索] + D3[HBase-宽表] + D4[Redis-缓存] + D5[Hive-数据湖] + D6[ClickHouse-OLAP] + D7[Neo4j-图数据库] + end + + subgraph 数据采集层 + C1[CRM数据同步] + C2[企业微信事件采集] + C3[第三方数据接入] + C4[行为埋点SDK] + end + + 应用层 --> 服务层 + 服务层 --> 数据处理层 + 数据处理层 --> 数据存储层 + 数据存储层 --> 数据采集层 +``` + +### 2.2 核心数据流 + +系统的核心数据流转包括以下四个主要流程: + +1. **数据采集流程**:CRM/企微/第三方 → 消息队列(Kafka) → 数据清洗 → 存储 +2. **OneID流程**:多源数据 → ID-Mapping → 统一客户视图 +3. **标签计算流程**:原始数据 → 规则引擎 → 标签生成 → 标签存储 +4. **圈人查询流程**:条件输入 → 查询引擎 → 结果集 → 导出/推送 + +--- + +## 三、数据采集平台 + +[返回目录](#目录) + +### 3.1 数据源接入 + +#### 3.1.1 CRM系统接入 + +**接入方式**: +- **全量同步**:定时任务(如每日凌晨),同步客户主数据、联系人、商机等 +- **增量同步**:实时或准实时(5-15分钟),基于时间戳或CDC(Change Data Capture) +- **API接口**:提供双向API,支持CRM系统实时推送数据变更 + +**数据内容**: +- **客户企业信息**:企业名称、行业、规模、年营收、地址、成立时间等 +- **联系人信息**:姓名、职位、部门、手机、邮箱、汇报关系等 +- **商机信息**:商机阶段、金额、预计成交时间、负责销售等 +- **活动记录**:拜访记录、通话记录、邮件往来等 + +#### 3.1.2 企业微信接入 + +**接入方式**: +- **企微API对接**:基于企业微信官方API,获取通讯录、客户、聊天记录等 +- **事件订阅**:订阅企微事件(添加客户、聊天、标签变更等),实时推送 +- **侧边栏应用**:在企微侧边栏嵌入CDP能力,销售可直接查看客户画像 + +**采集数据类型**: +- **通讯录数据**:企业组织架构、员工信息 +- **客户数据**:外部联系人、添加时间、添加来源 +- **互动数据**:聊天记录(需客户授权)、会话次数、回复时长 +- **群聊数据**:客户群信息、群成员、群消息 +- **雷达数据**:客户浏览行为(如浏览官网链接、产品手册) + +**数据示例**: +企业微信事件数据通常包含事件类型(如添加外部联系人)、事件时间、员工信息、外部联系人信息、添加方式、来源渠道等维度信息。 + +#### 3.1.3 第三方数据接入 + +**数据类型**: +- **企业征信数据**:企查查、天眼查等,企业工商信息、风险信息 +- **行业数据**:行业报告、竞品信息 +- **公开数据**:企业官网、招聘信息、新闻舆情 + +**接入方式**: +- **API调用**:定时或触发式调用第三方API +- **文件导入**:Excel/CSV批量导入 +- **爬虫采集**:合规前提下的数据抓取 + +### 3.2 数据采集架构 + +数据采集架构采用分层设计,确保数据从源系统到存储层的可靠传输和处理。 + +```mermaid +graph TB + subgraph 数据源层 + DS1[CRM系统] + DS2[企业微信] + DS3[第三方API] + DS4[行为埋点] + end + + subgraph 数据采集网关 + GW1[CRM Connector] + GW2[企微 Connector] + GW3[API Gateway] + GW4[SDK Collector] + end + + subgraph 消息队列 + MQ[Kafka Topics
crm-data | wework-event | 3rd-data | track] + end + + subgraph 数据预处理层 + FL[Flink Stream Processing
- 数据格式标准化
- 数据清洗去重
- 数据脱敏
- 数据校验] + end + + subgraph 数据落地层 + DL[ODS原始数据 → DWD明细数据 → DWS汇总数据] + end + + DS1 --> GW1 + DS2 --> GW2 + DS3 --> GW3 + DS4 --> GW4 + GW1 --> MQ + GW2 --> MQ + GW3 --> MQ + GW4 --> MQ + MQ --> FL + FL --> DL +``` + +**架构说明**: + +1. **数据采集网关**:负责数据格式转换、协议适配、限流熔断 +2. **消息队列Kafka**:作为数据缓冲层,支持高吞吐量的数据接入 +3. **数据预处理Flink**:实时流处理,完成数据标准化、清洗、脱敏、校验 +4. **数据落地**:按照数仓分层架构存储到不同层级 + +### 3.3 数据采集SDK设计 + +对于官网、营销页面等场景,提供JavaScript埋点SDK,支持以下核心能力: + +**SDK核心功能**: +- **初始化配置**:配置AppKey、服务端URL、自动埋点开关 +- **用户识别**:识别用户身份(userId、accountId)和基本属性 +- **事件追踪**:追踪页面浏览、按钮点击、表单提交等行为事件 +- **企业信息追踪**:追踪企业级别的属性信息 + +**SDK设计要点**: +- 采用异步上报,不阻塞页面渲染 +- 本地缓冲队列,批量上报提升性能 +- 断点续传,网络异常时自动重试 +- 数据加密传输,保障安全性 + +--- + +## 四、OneID服务(统一身份识别) + +[返回目录](#目录) + +### 4.1 核心挑战 + +在企业级CDP中,同一个联系人可能在多个系统中有不同的标识: +- CRM中有联系人ID +- 企业微信中有external_user_id +- 官网浏览时通过cookie识别 +- 第三方数据中通过手机号或邮箱标识 + +**OneID的目标**:将这些碎片化的身份统一为唯一的CDP ID,构建完整客户视图。 + +### 4.2 ID-Mapping策略 + +#### 4.2.1 匹配规则 + +**强匹配规则**(优先级从高到低): +1. **手机号**:中国手机号唯一性强,作为首要匹配字段 +2. **邮箱**:企业邮箱唯一性较强 +3. **证件号**:如身份证(需脱敏处理) +4. **企业微信unionid**:跨应用唯一标识 + +**弱匹配规则**: +- 姓名 + 公司名称 +- 姓名 + 部门 + 职位 +- 设备ID(移动端) + +#### 4.2.2 匹配算法流程 + +OneID匹配采用基于规则的算法流程: + +```mermaid +graph TD + A[新数据进入] --> B[提取匹配字段
手机/邮箱/unionid] + B --> C{查询ID-Mapping表
是否已存在匹配?} + C -->|存在| D[使用已有CDP ID
更新source映射关系] + C -->|不存在| E[生成新CDP ID
创建映射记录] + D --> F{是否有冲突?
一个source_id对应多个CDP ID} + F -->|有冲突| G[触发人工审核
或自动合并策略] + F -->|无冲突| H[完成匹配] + E --> H + G --> H +``` + +**ID-Mapping表设计要点**: +- 记录CDP统一ID与各来源系统ID的映射关系 +- 记录匹配字段(mobile/email/unionid)和匹配值 +- 记录匹配置信度(0.00-1.00) +- 支持按来源类型、来源ID、匹配字段索引查询 + +### 4.3 OneID数据模型 + +#### 统一客户视图 + +统一客户视图整合了来自多个数据源的客户信息,形成360度客户画像: + +**客户企业信息**: +- 基本信息:企业ID、企业名称、行业、规模、状态等 +- 经营信息:年营收、融资轮次、成立时间等 + +**联系人信息**: +- 基本信息:姓名、职位、部门、职级等 +- 联系方式:手机号(脱敏)、邮箱(脱敏) +- 决策角色:决策者、影响者、使用者等 + +**组织关系**: +- 汇报关系:直属上级CDP ID、上级姓名 +- 下属关系:直接下属列表 +- 组织路径:部门层级路径 + +**来源系统ID映射**: +- CRM联系人ID +- 企业微信external_user_id +- 第三方系统ID + +**客户标签**: +- 行为标签、属性标签、预测标签等 + +**活跃信息**: +- 最后活跃时间、创建时间等 + +### 4.4 OneID服务能力 + +OneID服务对外提供以下核心能力: + +**服务接口清单**: + +1. **根据来源ID获取CDP ID**:输入来源系统类型和来源ID,返回统一的CDP ID +2. **ID绑定/合并**:将新的来源ID与已有CDP ID进行绑定 +3. **获取统一客户视图**:根据CDP ID获取完整的客户360度视图 +4. **ID合并**:当发现多个CDP ID实际是同一人时,执行ID合并操作 + +**服务调用方式**: +- REST API:同步调用,适用于实时查询场景 +- Kafka消息:异步调用,适用于批量ID绑定场景 + +--- + +## 五、组织架构模型设计 + +[返回目录](#目录) + +### 5.1 数据模型 + +企业级CDP的核心特色在于**组织架构关系**,需要建立两层模型: + +#### 5.1.1 客户企业层级模型 + +**客户企业表**: +- 存储客户企业基本信息 +- 支持集团型企业的母子公司关系(parent_account_id) +- 区分企业层级:集团、子公司、分公司 +- 记录企业所属行业、规模、年营收等关键属性 +- 记录客户状态:潜在客户、商机、成交客户、流失 +- 记录客户负责人(owner_staff_id) + +**客户企业组织架构表**: +- 存储客户企业内部的组织架构信息 +- 支持多级组织结构(parent_org_id) +- 记录组织类型:部门、中心、小组等 +- 记录组织路径(如:/某公司/技术部/研发中心) +- 记录组织负责人的CDP ID + +#### 5.1.2 联系人组织关系模型 + +**联系人主表**: +- 存储联系人基本信息:姓名、职位、职级、资历等 +- 敏感信息加密存储:手机号、邮箱 +- 关联所属客户企业(account_id) +- 记录职级:员工/主管/经理/总监/VP/CXO +- 记录资历:初级/中级/高级/专家 + +**联系人组织关系表**: +- 存储联系人与组织的关联关系 +- 支持一个人在多个组织中(兼职、虚线汇报) +- 记录直属上级(report_to_cdp_id) +- 区分主要职位和兼职 +- 记录岗位类型:正式、兼职、虚线汇报 +- 支持时间有效性(start_date、end_date) + +### 5.2 组织关系查询能力 + +基于上述模型,提供强大的组织关系查询能力: + +#### 5.2.1 核心查询场景 + +组织服务对外提供以下核心查询能力: + +**向上查询**: +- **查询直属上级**:查询某人的直接汇报上级 +- **查询所有上级**:向上递归查询到CEO/总经理 + +**向下查询**: +- **查询直属下属**:查询某人的直接下属 +- **查询所有下属**:向下递归查询所有层级的下属 + +**平级查询**: +- **查询部门成员**:查询某个部门的所有成员 +- **支持级联查询**:可选择是否包含子部门 + +**关系查询**: +- **查询共同上级**:找到两个人的最近共同上级(LCA算法) +- **查询决策链**:找到某个部门的决策层人员 + +#### 5.2.2 图数据库优化 + +对于复杂的组织关系查询,建议使用**Neo4j图数据库**进行优化: + +**图数据库优势**: +- 天然适合处理关系数据 +- 支持多层级递归查询 +- 查询性能优于关系型数据库(尤其是深层递归) +- 支持复杂的路径查询和图算法 + +**节点设计**: +- **联系人节点(Contact)**:存储联系人基本信息 +- **组织节点(Organization)**:存储部门/团队信息 + +**关系设计**: +- **汇报关系(REPORT_TO)**:建立上下级关系 +- **所属关系(BELONG_TO)**:建立联系人与部门的关系 + +**典型查询场景**: +- 向上追溯所有上级 +- 向下追溯所有下属 +- 查询两人的共同上级(最短路径) +- 查询某人的同事(共同上级的下属) + +### 5.3 组织架构可视化 + +在销售工作台中,提供组织架构图可视化能力,帮助销售快速了解客户企业的组织结构。 + +**组织架构图示例**: + +```mermaid +graph TD + CEO[CEO - 王总
已触达] + + CEO --> VP_TECH[技术VP - 李总
未触达] + CEO --> VP_SALES[销售VP - 刘总
已触达] + + VP_TECH --> DIR_TECH[技术总监 - 张三
已触达] + VP_TECH --> DIR_PROD[产品总监 - 周六
未触达] + + VP_SALES --> DIR_KEY[大客户总监 - 赵七
已触达] + VP_SALES --> DIR_CHANNEL[渠道总监 - 孙八
未触达] + + style CEO fill:#90EE90 + style VP_SALES fill:#90EE90 + style DIR_TECH fill:#90EE90 + style DIR_KEY fill:#90EE90 + style VP_TECH fill:#FFB6C1 + style DIR_PROD fill:#FFB6C1 + style DIR_CHANNEL fill:#FFB6C1 +``` + +**可视化功能要点**: + +1. **节点信息展示**: + - 姓名、职位、部门 + - 联系方式(有权限时显示) + - 标签信息(如"决策者"、"高意向") + +2. **触达状态标识**: + - 已触达:绿色节点 + - 未触达:红色节点 + - 触达方式:企微、邮件、电话等 + +3. **交互功能**: + - 点击节点查看详细画像 + - 展开/收起子节点 + - 高亮显示某条汇报链路 + - 导出组织架构图 + +4. **前端技术选型**: + - **图可视化库**:AntV G6、D3.js、Cytoscape.js + - **布局算法**:树形布局、层次布局 + - **响应式设计**:支持大屏展示和移动端 + +--- + +## 六、数据建模与存储 + +[返回目录](#目录) + +### 6.1 数据分层架构 + +采用标准的数仓分层架构,确保数据质量和查询性能: + +```mermaid +graph LR + ODS[ODS
Operational Data Store
原始数据层] --> DWD[DWD
Data Warehouse Detail
明细数据层] + DWD --> DWS[DWS
Data Warehouse Summary
汇总数据层] + DWS --> ADS[ADS
Application Data Service
应用数据层] +``` + +**各层职责说明**: + +| 数据层 | 说明 | 数据处理 | 存储周期 | +|-------|------|---------|---------| +| **ODS** | 原始数据层 | 数据直接从源系统同步,保持原始格式 | 30-90天 | +| **DWD** | 明细数据层 | 数据清洗、标准化、关联OneID | 1-2年 | +| **DWS** | 汇总数据层 | 按天/周/月汇总统计 | 2-3年 | +| **ADS** | 应用数据层 | 面向具体业务场景的宽表 | 长期保存 | + +### 6.2 核心数据模型 + +#### 6.2.1 客户企业主题 + +**DWD: 客户企业明细表** + +存储客户企业的明细信息,包括: +- 企业基本信息:企业ID、企业名称、行业、规模、年营收 +- 客户状态:潜在客户、商机、成交客户、流失 +- 负责人信息:客户负责人(销售) +- 时间信息:首次接触日期、最近接触日期 +- 业务指标:商机总数、营收总额 +- 扩展字段:JSON格式存储灵活的扩展属性 + +**DWS: 客户企业汇总表** + +按天统计客户企业的行为汇总数据: +- 联系人数量 +- 互动次数(总计、企微聊天、邮件、会议) +- 商机数量和金额 +- 活跃度指标(近7天活跃天数、近30天活跃天数) + +#### 6.2.2 联系人主题 + +**DWD: 联系人明细表** + +存储联系人的明细信息: +- 基本信息:CDP ID、姓名、职位、部门、职级 +- 组织关系:所属企业、直属上级 +- 敏感信息:手机号(加密)、邮箱(加密) +- 来源系统ID:企业微信external_id等 +- 时间信息:首次添加时间、最后活跃时间 + +**DWS: 联系人行为汇总表** + +按天统计联系人的行为汇总数据: +- 浏览行为:页面浏览次数 +- 互动行为:企微聊天次数、回复次数、平均回复时长 +- 邮件行为:邮件打开次数、点击次数 +- 会议行为:会议参与次数 +- 文档行为:文档查看次数 +- 总互动次数和最后互动时间 + +#### 6.2.3 行为事件主题 + +**DWD: 行为事件明细表** + +存储用户的所有行为事件明细: +- 事件基本信息:事件ID、事件类型、事件时间 +- 事件主体:联系人CDP ID、客户企业ID +- 事件属性:JSON格式存储事件的详细属性 +- 数据来源:WEWORK/CRM/WEB等 +- 支持按日期分区,便于历史数据归档 + +**常见事件类型**: +- page_view:页面浏览 +- wework_chat:企微聊天 +- email_open:邮件打开 +- email_click:邮件点击 +- meeting_attend:会议参加 +- document_view:文档查看 +- form_submit:表单提交 + +### 6.3 存储选型 + +根据不同数据特点和访问模式,选择合适的存储方案: + +| 数据类型 | 存储方案 | 说明 | 典型应用场景 | +|---------|---------|------|-------------| +| **元数据** | MySQL | 账户信息、联系人信息、标签配置等 | 事务性查询、配置管理 | +| **行为事件** | ClickHouse | 海量事件数据,支持OLAP分析 | 用户行为分析、漏斗分析 | +| **实时画像** | HBase/Redis | 宽表存储,支持快速查询 | 客户360视图、实时标签查询 | +| **组织关系** | Neo4j | 图数据库,支持复杂关系查询 | 组织架构查询、关系路径分析 | +| **搜索** | Elasticsearch | 联系人搜索、企业搜索 | 全文检索、模糊搜索、聚合统计 | +| **数据湖** | Hive/Iceberg | 原始数据长期存储 | 历史数据归档、数据回溯 | +| **缓存** | Redis | 热点数据缓存,提升查询性能 | 高频访问数据、会话数据 | + +**存储选型原则**: +- **元数据用MySQL**:事务性强、数据量小、需要ACID保证 +- **行为数据用ClickHouse**:写多读少、列式存储、支持OLAP分析 +- **实时数据用Redis/HBase**:读多写多、低延迟、高并发 +- **关系数据用Neo4j**:复杂关系查询、图算法、路径分析 +- **搜索用ES**:全文检索、模糊匹配、聚合统计 +- **冷数据用Hive/Iceberg**:长期归档、成本优化、支持数据湖 + +--- + +## 七、标签体系设计 + +[返回目录](#目录) + +### 7.1 标签分类 + +企业级CDP的标签体系分为三大类: + +#### 7.1.1 客户企业标签 + +| 类别 | 标签示例 | +|-----|---------| +| **基础属性** | 行业、规模、地域、上市状态、成立时间 | +| **商业属性** | 年营收、融资轮次、客户生命周期阶段 | +| **业务状态** | 潜在客户、商机阶段、成交客户、流失客户 | +| **行为特征** | 活跃度、互动频次、决策周期 | +| **意向度** | 高意向、中意向、低意向、无意向 | +| **产品偏好** | 关注AI产品、关注SaaS产品、关注定制化服务 | + +#### 7.1.2 联系人标签 + +| 类别 | 标签示例 | +|-----|---------| +| **基础属性** | 姓名、职位、部门、职级、工作年限 | +| **决策角色** | 决策者、影响者、使用者、把关者 | +| **组织关系** | 部门负责人、核心团队成员、关键决策人 | +| **行为特征** | 活跃用户、沉默用户、高频互动、快速回复 | +| **兴趣偏好** | 关注技术细节、关注价格、关注案例、关注服务 | +| **互动渠道** | 企微活跃、邮件活跃、会议活跃 | + +#### 7.1.3 组织关系标签 + +| 类别 | 标签示例 | +|-----|---------| +| **层级标签** | 高层领导、中层管理、基层员工 | +| **影响力标签** | 核心决策人、部门话事人、意见领袖 | +| **关系标签** | 已触达高层、已触达决策人、仅触达执行层 | +| **团队标签** | 完整决策链、缺失决策人、单点联系 | + +### 7.2 标签计算引擎 + +#### 7.2.1 标签类型 + +标签按照计算方式分为三大类: + +**1. 规则类标签**(基于条件判断) + +通过SQL规则定义标签计算逻辑,适用于明确的业务规则场景。 + +**示例**:高意向客户 +- **规则定义**:近30天互动次数>=10次 AND 近7天有高层互动 +- **计算方式**:从DWS汇总表中查询符合条件的联系人 +- **更新频率**:每日计算 + +**2. 统计类标签**(基于聚合计算) + +通过统计聚合计算得出的标签,适用于需要汇总统计的场景。 + +**示例**:互动频次标签 +- **L7D_互动次数**:近7天的总互动次数 +- **L30D_互动次数**:近30天的总互动次数 +- **计算方式**:从行为汇总表中按时间范围聚合 +- **更新频率**:每日计算 + +**3. 预测类标签**(基于机器学习模型) + +通过机器学习模型预测得出的标签,适用于复杂的预测场景。 + +**示例**:成交概率预测 +- **特征工程**: + - 近30天互动次数 + - 决策人触达数量 + - 是否触达CXO + - 回复响应率 + - 会议参与次数 + - 文档查看次数 +- **模型算法**:随机森林、GBDT、神经网络等 +- **输出结果**:成交概率分数(0-1) +- **更新频率**:每周/每月重新训练模型 + +#### 7.2.2 标签计算流程 + +标签计算采用统一的流程框架: + +```mermaid +graph LR + A[标签定义
业务人员在平台配置规则] --> B[标签调度
定时任务触发计算] + B --> C[数据准备
从DWS层读取汇总数据] + C --> D[规则执行
执行SQL/Python脚本] + D --> E[结果写入
写入标签结果表] + E --> F[缓存更新
更新Redis缓存] +``` + +**流程说明**: + +1. **标签定义**:业务人员在标签管理平台配置标签规则 +2. **标签调度**:通过DolphinScheduler等调度工具,按照设定的频率(每日/每小时/实时)触发计算 +3. **数据准备**:从DWS汇总层读取所需的数据 +4. **规则执行**:根据标签类型,执行对应的计算逻辑(SQL查询、Python脚本、ML模型推理) +5. **结果写入**:将计算结果写入标签结果表(MySQL) +6. **缓存更新**:将热点标签数据更新到Redis,供实时查询使用 + +### 7.3 标签存储方案 + +标签系统需要两张核心表: + +**标签配置表**: +- 存储标签的元数据信息 +- 字段包括:标签ID、标签名称、标签分类、标签类型(RULE/STAT/ML) +- 记录目标类型:ACCOUNT(企业)/CONTACT(联系人) +- 记录计算逻辑:SQL语句或规则描述 +- 记录计算频率:DAILY/HOURLY/REALTIME +- 记录标签状态:是否启用 + +**标签结果表**: +- 存储标签的计算结果 +- 字段包括:目标ID(account_id或cdp_id)、目标类型、标签ID、标签值 +- 记录置信度:仅预测类标签需要(0.00-1.00) +- 记录有效期:start_time、end_time +- 支持标签的时效性管理 +- 按目标ID和标签ID建立联合主键 + +**查询优化**: +- 为标签结果表建立覆盖索引:(target_id, tag_id) +- 热点标签数据缓存到Redis,key格式:`tag:result:{target_id}` +- 支持批量查询某个客户的所有标签 + +--- + +## 八、圈人能力设计 + +[返回目录](#目录) + +### 8.1 圈人场景 + +基于标签和组织关系的客户筛选,典型场景: + +1. **精准营销**:圈选高意向的决策人,推送产品方案 +2. **销售分配**:圈选新增商机客户,分配给销售跟进 +3. **客户关怀**:圈选长期未互动的老客户,进行激活 +4. **交叉销售**:圈选已购买A产品的客户,推荐B产品 +5. **组织穿透**:圈选某客户企业的所有高层决策人 + +### 8.2 圈人条件 + +#### 8.2.1 基础条件 + +圈人条件支持多维度组合查询: + +**企业维度条件**: +- 行业:IN(互联网、金融、制造等) +- 规模:IN(500人以上、1000人以上等) +- 地域:IN(北京、上海、深圳等) +- 客户状态:IN(商机、成交客户等) + +**联系人维度条件**: +- 职级:IN(VP、CXO、总监等) +- 部门:IN(技术部、产品部等) +- 决策角色:IN(决策者、影响者等) + +**标签条件**: +- 标签存在性:HAS标签(如"高意向") +- 标签值:标签值等于/大于/小于某个值 + +**行为条件**: +- 互动次数:近30天互动次数 >= 10 +- 最后互动时间:在某个时间范围内 +- 页面浏览:浏览过特定页面 + +**逻辑关系**: +- 支持AND、OR组合 +- 支持条件分组 + +#### 8.2.2 组织关系条件 + +除了基础条件,还支持基于组织关系的圈人: + +**层级条件**: +- 职级:总监及以上 +- 层级:高层、中层、基层 + +**部门条件**: +- 所属部门:IN(技术部、产品部) +- 包含子部门:是/否 + +**汇报关系条件**: +- 某人的所有下属:向下N级 +- 某人的所有上级:向上到顶 +- 某人的同事:共同上级的下属 + +**组合查询示例**: +圈选某客户企业(A001)中,技术部或产品部的总监及以上人员,且是张三(CDP_000001)的下属(向下2级)。 + +### 8.3 圈人引擎 + +#### 8.3.1 查询优化 + +对于复杂的圈人条件,采用多阶段查询策略,逐步缩小候选集: + +```mermaid +graph TD + A[圈人条件输入] --> B[Stage 1: 基础条件过滤
MySQL/ES
行业、规模、地域等静态属性] + B --> C[Stage 2: 标签条件过滤
Redis/HBase
查询标签结果表] + C --> D[Stage 3: 行为条件过滤
ClickHouse
查询行为汇总数据] + D --> E[Stage 4: 组织关系过滤
Neo4j
查询组织关系图] + E --> F[Stage 5: 结果合并去重
返回最终CDP ID列表] +``` + +**查询优化策略**: + +1. **先过滤后关联**:先用过滤性强的条件缩小候选集 +2. **分阶段查询**:按数据源特点分阶段查询,避免全表扫描 +3. **索引优化**:为常用查询字段建立合适的索引 +4. **缓存结果**:对热点圈人条件缓存查询结果 +5. **异步计算**:复杂圈人任务异步执行,避免超时 + +#### 8.3.2 圈人服务能力 + +圈人服务对外提供以下核心能力: + +**服务接口清单**: + +1. **创建圈人任务**:提交圈人条件,创建圈人任务,返回任务ID +2. **查询圈人结果**:根据任务ID分页查询圈人结果 +3. **导出圈人结果**:将圈人结果导出为Excel/CSV文件 +4. **推送圈人结果**:将圈人结果推送到外部系统(企微、邮件、广告平台等) + +**任务状态管理**: +- 待执行:任务已创建,等待调度 +- 执行中:任务正在计算 +- 执行成功:任务完成,结果可查询 +- 执行失败:任务失败,记录错误信息 + +### 8.4 圈人结果应用 + +圈选结果可以应用到多种场景: + +1. **导出**:导出为Excel/CSV,提供给业务团队线下使用 +2. **推送**: + - 推送到企业微信:通过企微消息触达客户 + - 推送到邮件系统:批量发送营销邮件 + - 推送到短信平台:发送短信通知 +3. **广告投放**:同步到广告平台(如腾讯广告、字节广告),用于Lookalike扩量 +4. **任务分配**:自动创建销售任务,分配给对应销售跟进 +5. **实时触发**:基于用户行为实时触发营销动作(如自动化营销流程) + +--- + +## 九、数据安全与合规 + +[返回目录](#目录) + +### 9.1 合规要求 + +根据《个人信息保护法》、《数据安全法》等法规,CDP需要满足: + +1. **数据最小化**:只采集业务必需的数据,避免过度采集 +2. **明示同意**:用户明确授权后才能采集和使用个人信息 +3. **数据脱敏**:敏感数据加密存储和传输 +4. **访问控制**:基于角色的权限管理,最小权限原则 +5. **审计日志**:记录所有数据访问和操作,可追溯 +6. **数据删除**:用户有权要求删除个人数据,需提供删除机制 +7. **数据导出**:用户有权导出个人数据,需提供导出功能 +8. **安全评估**:定期进行数据安全风险评估 + +### 9.2 数据脱敏策略 + +#### 9.2.1 脱敏字段 + +针对不同类型的敏感信息,采用不同的脱敏策略: + +| 字段类型 | 脱敏方式 | 示例 | 适用场景 | +|---------|---------|------|---------| +| 手机号 | 中间4位打码 | 138****8000 | 列表展示、报表导出 | +| 邮箱 | 用户名部分打码 | zha***@example.com | 列表展示、报表导出 | +| 身份证 | 中间部分打码 | 110***********1234 | 身份验证场景 | +| 姓名 | 姓氏保留 | 张** | 低敏感场景 | +| 地址 | 详细地址模糊化 | 北京市朝阳区*** | 区域统计分析 | + +#### 9.2.2 加密存储 + +敏感字段采用AES加密存储: + +**加密方案**: +- 使用AES-256加密算法 +- 密钥管理:采用密钥管理系统(KMS),支持密钥轮转 +- 版本管理:记录加密密钥版本,支持密钥升级 +- 应用层解密:数据在应用层解密后使用,数据库层保持加密状态 + +**敏感字段表设计要点**: +- 单独存储敏感字段,与业务表分离 +- 使用VARBINARY类型存储加密后的数据 +- 记录加密密钥版本,便于密钥轮转 +- 严格控制解密权限,最小化明文暴露 + +### 9.3 权限控制 + +#### 9.3.1 RBAC模型 + +采用基于角色的访问控制(RBAC)模型: + +**核心概念**: +- **用户(User)**:系统使用者 +- **角色(Role)**:权限的集合,如"销售主管"、"数据分析师" +- **权限(Permission)**:具体的操作权限,如"查看客户信息"、"导出数据" + +**权限表设计要点**: + +**角色表**: +- 存储角色基本信息:角色ID、角色名称、角色描述 +- 支持角色的启用/禁用 + +**权限表**: +- 存储权限定义:权限ID、权限名称 +- 资源类型:ACCOUNT(企业)、CONTACT(联系人)、TAG(标签) +- 操作类型:READ(查看)、WRITE(编辑)、DELETE(删除)、EXPORT(导出) + +**角色权限关联表**: +- 建立角色与权限的多对多关系 + +**用户角色关联表**: +- 建立用户与角色的多对多关系 +- 一个用户可以拥有多个角色 + +**典型角色定义**: +- **销售**:查看自己负责的客户、编辑客户信息、圈人导出 +- **销售主管**:查看团队客户、分配客户、审批导出 +- **数据分析师**:查看所有数据、BI分析、无导出权限 +- **系统管理员**:全部权限 + +#### 9.3.2 数据权限 + +除了功能权限,还需要控制数据范围权限(行级权限): + +**数据权限表设计要点**: +- 用户ID:权限归属用户 +- 数据类型:ACCOUNT(企业)、CONTACT(联系人) +- 过滤条件:JSON格式存储数据过滤规则 + +**典型数据权限规则**: + +**示例1:销售只能看到自己负责的客户** +- 过滤条件:owner_staff_id = 当前用户ID + +**示例2:区域经理可以看到整个区域的客户** +- 过滤条件:region = '华北区' + +**示例3:销售VP可以看到所有客户** +- 过滤条件:无(全量数据) + +**数据权限实现方式**: +- 在应用层查询时自动拼接过滤条件 +- 使用数据库视图限制数据访问范围 +- 使用中间件拦截器统一处理数据权限 + +### 9.4 审计日志 + +完善的审计日志是数据安全合规的重要保障: + +**操作审计表设计要点**: +- 记录用户操作:用户ID、用户名 +- 记录操作类型:QUERY(查询)、EXPORT(导出)、UPDATE(更新)、DELETE(删除) +- 记录资源信息:资源类型、资源ID +- 记录操作详情:JSON格式存储详细操作内容 +- 记录访问信息:IP地址、User-Agent +- 记录操作时间:精确到秒 +- 支持按时间分区,便于历史数据归档 + +**审计日志应用场景**: +- **合规审计**:满足监管要求,提供操作记录 +- **安全分析**:检测异常操作,如批量导出、越权访问 +- **问题排查**:追溯数据变更历史,定位问题根源 +- **行为分析**:分析用户使用习惯,优化产品体验 + +**审计日志查询能力**: +- 按用户查询:查询某个用户的所有操作记录 +- 按时间查询:查询某个时间范围内的操作记录 +- 按操作类型查询:查询所有导出操作、删除操作等 +- 按资源查询:查询某个客户的所有操作历史 + +--- + +## 十、技术选型与架构 + +[返回目录](#目录) + +### 10.1 技术栈选型 + +#### 10.1.1 后端技术栈 + +| 技术领域 | 选型方案 | 说明 | +|---------|---------|------| +| 开发语言 | Java 11+ / Spring Boot | 成熟稳定,生态丰富 | +| 微服务框架 | Spring Cloud Alibaba | 国内主流,社区活跃 | +| API网关 | Spring Cloud Gateway | 统一入口,流量控制 | +| 注册中心 | Nacos | 服务注册与配置管理 | +| 消息队列 | Kafka | 高吞吐,支持流式处理 | +| 缓存 | Redis Cluster | 高性能缓存 | +| 关系数据库 | MySQL 8.0 | 元数据存储 | +| OLAP数据库 | ClickHouse | 行为事件分析 | +| 图数据库 | Neo4j | 组织关系查询 | +| 搜索引擎 | Elasticsearch | 全文搜索 | +| 实时计算 | Flink | 实时数据处理 | +| 离线计算 | Spark | 批量数据处理 | +| 任务调度 | DolphinScheduler | 数据任务编排 | +| 监控告警 | Prometheus + Grafana | 系统监控 | + +#### 10.1.2 前端技术栈 + +| 技术领域 | 选型方案 | +|---------|---------| +| 框架 | React / Vue 3 | +| UI组件库 | Ant Design / Element Plus | +| 图表库 | ECharts / AntV | +| 图可视化 | AntV G6 | +| 状态管理 | Redux / Pinia | +| 请求库 | Axios | +| 构建工具 | Vite / Webpack | + +### 10.2 部署架构 + +#### 10.2.1 云上部署(推荐) + +云上部署架构充分利用云平台的弹性伸缩、高可用、托管服务等优势: + +```mermaid +graph TB + LB[负载均衡 SLB] + + subgraph K8s集群 + GW[API网关集群] + + subgraph 应用服务集群 + APP1[圈人服务] + APP2[标签服务] + APP3[画像服务] + APP4[组织服务] + end + + subgraph 数据服务集群 + DATA1[数据采集服务] + DATA2[OneID服务] + DATA3[数据质量服务] + end + + subgraph 计算服务集群 + COMP1[Flink集群] + COMP2[Spark集群] + end + end + + subgraph 托管数据库 + RDS[RDS MySQL] + REDIS[Redis Cluster] + KAFKA[Kafka集群] + end + + subgraph 大数据存储 + CH[ClickHouse] + ES[Elasticsearch] + NEO[Neo4j] + end + + LB --> GW + GW --> APP1 + GW --> APP2 + GW --> APP3 + GW --> APP4 + GW --> DATA1 + GW --> DATA2 + GW --> DATA3 + + APP1 --> RDS + APP2 --> RDS + APP3 --> REDIS + APP4 --> NEO + + DATA1 --> KAFKA + DATA2 --> RDS + + KAFKA --> COMP1 + COMP1 --> CH + COMP2 --> CH +``` + +**云上部署优势**: +- **弹性伸缩**:根据负载自动调整资源,按需付费 +- **高可用**:跨可用区容灾,自动故障切换 +- **托管服务**:RDS、Kafka等托管服务,减少运维成本 +- **安全隔离**:VPC网络隔离,安全组访问控制 +- **监控告警**:云平台提供完善的监控和告警能力 + +**推荐云平台**: +- 阿里云:完整的大数据产品线,适合国内客户 +- 腾讯云:企微生态整合,适合重度使用企微的场景 +- AWS:全球部署,适合海外业务 + +#### 10.2.2 私有化部署 + +适用于对数据安全要求极高、需要数据主权的客户: + +```mermaid +graph TB + F5[硬件负载均衡 F5/Nginx] + + subgraph 应用服务器集群 + AS1[应用服务器1] + AS2[应用服务器2] + AS3[应用服务器3] + end + + subgraph 数据库服务器 + DB1[MySQL主库] + DB2[MySQL从库] + end + + subgraph 缓存服务器集群 + CACHE1[Redis节点1] + CACHE2[Redis节点2] + CACHE3[Redis节点3] + end + + subgraph 消息队列集群 + MQ1[Kafka节点1] + MQ2[Kafka节点2] + MQ3[Kafka节点3] + end + + subgraph 大数据集群 + BIG[ClickHouse/Flink/Spark集群
多节点部署] + end + + F5 --> AS1 + F5 --> AS2 + F5 --> AS3 + + AS1 --> DB1 + AS2 --> DB1 + AS3 --> DB1 + + DB1 --> DB2 + + AS1 --> CACHE1 + AS1 --> MQ1 + + MQ1 --> BIG +``` + +**私有化部署特点**: +- **数据主权**:数据完全掌控在客户手中 +- **网络隔离**:部署在客户内网,与外网物理隔离 +- **定制化强**:可根据客户需求深度定制 +- **运维成本高**:需要客户自行运维,或提供驻场服务 + +**硬件配置建议**: +- **应用服务器**:16核32G内存,至少3台 +- **数据库服务器**:32核64G内存,SSD存储,主从架构 +- **缓存服务器**:16核32G内存,至少3台组成集群 +- **消息队列**:16核32G内存,至少3台组成集群 +- **大数据集群**:根据数据量规划,至少5台起步 + +### 10.3 核心服务设计 + +#### 10.3.1 服务拆分 + +基于微服务架构,按照业务领域拆分服务: + +**服务清单**: + +| 服务名称 | 职责说明 | +|---------|---------| +| cdp-gateway-service | API网关,统一入口,路由转发、鉴权、限流 | +| cdp-user-service | 用户权限服务,用户管理、角色管理、权限控制 | +| cdp-collection-service | 数据采集服务,多源数据接入、数据清洗 | +| cdp-oneid-service | OneID服务,身份识别、ID-Mapping、客户视图 | +| cdp-organization-service | 组织架构服务,组织关系管理、关系查询 | +| cdp-tag-service | 标签服务,标签配置、标签计算、标签查询 | +| cdp-audience-service | 圈人服务,圈人条件解析、圈人执行、结果导出 | +| cdp-profile-service | 画像服务,客户360画像、画像查询 | +| cdp-data-quality-service | 数据质量服务,数据质量监控、数据清洗规则 | +| cdp-export-service | 数据导出服务,数据导出、推送到外部系统 | + +**服务拆分原则**: +- **高内聚低耦合**:同一业务领域的功能在一个服务内 +- **独立部署**:每个服务可以独立部署、独立扩展 +- **数据隔离**:每个服务有独立的数据库/Schema +- **接口稳定**:服务间通过稳定的API通信 + +#### 10.3.2 服务通信 + +服务间通信采用同步+异步结合的方式: + +**同步调用**: +- **协议**:HTTP/REST +- **框架**:OpenFeign +- **适用场景**:实时查询、事务性操作 +- **超时控制**:设置合理的超时时间(如3秒) +- **重试策略**:幂等接口支持重试 + +**异步调用**: +- **消息队列**:Kafka +- **适用场景**:数据同步、事件通知、批量处理 +- **消息格式**:JSON,包含消息ID、时间戳、业务数据 +- **消费模式**:支持广播和点对点 + +**服务降级**: +- **限流**:集成Sentinel,防止服务过载 +- **熔断**:快速失败,避免雪崩 +- **降级**:返回默认值或缓存数据 + +**链路追踪**: +- **APM工具**:SkyWalking +- **功能**:请求链路追踪、性能分析、异常排查 +- **Trace ID**:全链路跟踪请求 + +--- + +## 十一、系统实施路线图 + +[返回目录](#目录) + +### 11.1 Phase 1: MVP版本 (2-3个月) + +**核心目标**:快速上线基础功能,验证业务价值 + +**功能范围**: +- **数据采集**:CRM同步、企业微信接入 +- **OneID**:基本的ID-Mapping能力 +- **客户管理**:客户企业、联系人基础信息管理 +- **组织架构**:基础的上下级关系查询 +- **标签体系**:10-20个核心业务标签 +- **圈人能力**:基于标签的简单圈人 +- **销售工作台**:客户列表、客户详情、组织架构图 + +**技术实现**: +- 关系型数据库为主(MySQL) +- 简单的组织关系查询(SQL递归查询) +- 前端单体应用 + +**交付成果**: +- 可用的销售工作台 +- 基础的客户360画像 +- 简单的圈人导出功能 + +### 11.2 Phase 2: 增强版本 (3-4个月) + +**核心目标**:完善核心能力,支持更复杂的业务场景 + +**功能范围**: +- **数据采集**:第三方数据接入、Web埋点SDK +- **OneID**:多源数据深度打通、ID合并策略 +- **组织架构**:矩阵式组织、复杂关系查询(图数据库) +- **标签体系**:50+标签、自定义标签平台、标签自动更新 +- **圈人能力**:组织关系圈人、复杂条件圈人、圈人结果推送 +- **数据分析**:客户分析报表、销售漏斗、客户健康度 + +**技术升级**: +- 引入Neo4j图数据库,优化组织关系查询 +- 引入ClickHouse,支持海量行为数据分析 +- 引入Flink,支持实时数据处理 +- 微服务化改造 + +**交付成果**: +- 完整的标签平台 +- 强大的圈人能力 +- 组织穿透场景落地 + +### 11.3 Phase 3: 智能版本 (4-6个月) + +**核心目标**:引入AI能力,提升智能化水平 + +**功能范围**: +- **预测类标签**:成交概率、流失风险、客户价值评分 +- **智能推荐**:下一步行动建议、推荐联系人、推荐话术 +- **自然语言查询**:通过自然语言描述圈人条件 +- **异常检测**:数据质量监控、异常行为识别 +- **自动化营销**:基于触发条件的自动化流程 + +**技术升级**: +- 引入机器学习平台(如MLflow) +- 引入NLP模型(如BERT) +- 引入知识图谱 + +**交付成果**: +- 智能化的销售辅助能力 +- 自动化营销能力 +- 从"被动查询"到"主动洞察" + +--- + +## 十二、成功案例参考 + +[返回目录](#目录) + +### 12.1 客户画像场景 + +**业务场景**:销售拜访某客户前,快速了解客户全貌 + +**解决方案**: +1. 销售在企微侧边栏打开CDP客户画像 +2. 展示客户企业基本信息(行业、规模、经营状况) +3. 展示组织架构图,标注已触达联系人 +4. 展示关键决策人(CEO、CTO等),提示是否已触达 +5. 展示近期互动记录(聊天、邮件、会议) +6. 展示客户意向度、成交概率等智能标签 +7. 推荐下一步行动(如"建议约见技术VP李总") + +**业务价值**: +- 销售拜访准备时间从2小时缩短到30分钟 +- 触达关键决策人的概率提升40% +- 商机转化率提升25% + +### 12.2 组织穿透场景 + +**业务场景**:已经触达某客户的技术经理,希望触达其上级技术VP + +**解决方案**: +1. 在CDP中查询该技术经理的组织关系 +2. 系统自动展示其直属上级"技术VP-李总" +3. 展示李总的画像:职位、联系方式、近期活跃情况 +4. 展示李总的关注点:曾查看过产品方案、参加过线上会议 +5. 推荐触达策略:通过现有联系人引荐、或发送定向内容 + +**业务价值**: +- 决策链触达完整度从30%提升到70% +- 大单成交周期缩短20% + +### 12.3 精准营销场景 + +**业务场景**:新产品发布,需要圈选目标客户进行推广 + +**圈人条件**: +- 行业:互联网、金融 +- 规模:500人以上 +- 客户阶段:商机阶段 +- 联系人层级:VP/CXO +- 意向度:高意向 +- 互动情况:近30天有互动 +- 组织关系:已触达技术部门 + +**执行动作**: +- 圈选出500个目标联系人 +- 通过企业微信推送产品介绍 +- 邀请参加线上产品发布会 +- 销售跟进重点客户 + +**业务价值**: +- 营销触达精准度提升60% +- 产品发布会参会率提升3倍 +- 新产品试用转化率提升50% + +--- + +## 十三、总结与展望 + +[返回目录](#目录) + +### 13.1 企业级CDP与C端CDP的核心差异 + +| 维度 | C端CDP | 企业级CDP | +|-----|--------|-----------| +| 客户主体 | 个人消费者 | 企业+联系人 | +| 数据规模 | 千万-亿级用户 | 万-百万级企业 | +| 核心关系 | 用户画像 | 组织架构关系 | +| 决策模式 | 个人决策 | 多人协同决策 | +| 生命周期 | 较短(数月) | 较长(数年) | +| 典型场景 | 精准投放、个性化推荐 | 销售赋能、组织穿透 | + +### 13.2 关键成功要素 + +1. **数据质量**:GIGO(Garbage In, Garbage Out),数据质量是CDP的生命线 +2. **组织关系**:这是企业级CDP的核心竞争力,必须重点投入 +3. **OneID精准度**:直接影响客户画像的准确性 +4. **业务深度结合**:CDP不是数据仓库,必须与业务场景深度结合 +5. **数据安全合规**:红线问题,必须严格遵守 + +### 13.3 未来演进方向 + +1. **实时化**:从T+1向实时CDP演进,支持秒级的数据更新 +2. **智能化**:引入更多AI能力,从被动查询到主动洞察 +3. **开放化**:通过API、SDK开放CDP能力,构建数据生态 +4. **私有化**:支持私有化部署,满足大型企业的数据主权诉求 +5. **行业化**:针对不同行业(金融、教育、制造)提供行业解决方案 + +--- + +## 附录 + +[返回目录](#目录) + +### 附录A:关键指标定义 + +| 指标名称 | 定义 | 计算公式 | +|---------|------|---------| +| 客户覆盖率 | 已录入CDP的客户占总客户的比例 | CDP客户数 / 总客户数 | +| OneID准确率 | OneID匹配正确的比例 | 正确匹配数 / 总匹配数 | +| 标签覆盖率 | 有标签的客户占总客户的比例 | 有标签客户数 / 总客户数 | +| 标签平均数 | 每个客户平均拥有的标签数 | 总标签数 / 客户数 | +| 组织完整度 | 组织架构信息完整的客户比例 | 有组织信息客户数 / 总客户数 | +| 数据新鲜度 | 数据最近更新的时间 | 当前时间 - 最后更新时间 | + +### 附录B:核心服务接口 + +**OneID服务接口**: +- GET `/api/v1/oneid/customer/{cdpId}` - 获取客户画像 +- POST `/api/v1/oneid/bind` - 绑定ID +- POST `/api/v1/oneid/merge` - 合并ID + +**组织架构服务接口**: +- GET `/api/v1/org/manager/{cdpId}` - 查询上级 +- GET `/api/v1/org/reports/{cdpId}` - 查询下属 +- GET `/api/v1/org/tree/{accountId}` - 查询组织树 + +**标签服务接口**: +- GET `/api/v1/tag/list` - 标签列表 +- POST `/api/v1/tag/calculate` - 计算标签 +- GET `/api/v1/tag/customer/{cdpId}` - 查询客户标签 + +**圈人服务接口**: +- POST `/api/v1/audience/create` - 创建圈人任务 +- GET `/api/v1/audience/{audienceId}/result` - 查询圈人结果 +- POST `/api/v1/audience/{audienceId}/export` - 导出圈人结果 + +--- + +**文档版本**:v2.0 +**最后更新**:2024-10-28 +**作者**:CDP项目组 + +--- + +## 问题讨论与反馈 + +如您对本设计方案有任何疑问或建议,请联系项目组进行讨论。 + +**关键待确认事项**: +1. 数据源系统的详细对接规范(CRM系统接口文档、企微API权限) +2. 组织架构的复杂度(是否存在矩阵式管理、虚线汇报等特殊情况) +3. 性能要求(并发用户数、查询响应时间SLA) +4. 预算与人力资源(开发团队规模、预算范围) +5. 上线时间要求(是否有硬性deadline) + +[返回目录](#目录) diff --git a/docs/graph-definition-examples.md b/docs/graph-definition-examples.md new file mode 100644 index 000000000..3f1c2f534 --- /dev/null +++ b/docs/graph-definition-examples.md @@ -0,0 +1,899 @@ +# StreamGraph定义结构说明 + +## 1. graph_definition结构概述 + +`graph_definition`是JSON格式,存储StreamGraph的完整定义,包括节点(nodes)和边(edges)。 + +### 1.1 基本结构 + +```json +{ + "version": "1.0", + "nodes": [ + { + "node_id": "唯一节点ID", + "node_name": "节点名称", + "node_type": "SOURCE/OPERATOR/SINK", + "operator_type": "具体算子类型", + "config": { + "算子特定配置": "..." + } + } + ], + "edges": [ + { + "edge_id": "边ID", + "source_node_id": "源节点ID", + "target_node_id": "目标节点ID" + } + ], + "global_config": { + "全局配置": "..." + } +} +``` + +## 2. 节点类型详解 + +### 2.1 SOURCE节点 + +Source节点定义数据源。 + +```json +{ + "node_id": "source-kafka-001", + "node_name": "用户事件源", + "node_type": "SOURCE", + "operator_type": "KAFKA_SOURCE", + "config": { + "datasource_id": "kafka-prod-cluster", + "topics": ["user-events", "user-actions"], + "group_id": "etl-consumer-group-1", + "auto_offset_reset": "latest", + "poll_timeout_ms": 1000, + "max_poll_records": 500, + "enable_auto_commit": false, + "properties": { + "max.partition.fetch.bytes": "1048576" + } + } +} +``` + +**常见Source类型**: + +#### JDBC_SOURCE +```json +{ + "node_id": "source-mysql-001", + "node_name": "订单数据源", + "node_type": "SOURCE", + "operator_type": "JDBC_SOURCE", + "config": { + "datasource_id": "mysql-prod", + "query": "SELECT * FROM orders WHERE updated_at > ? AND updated_at <= ?", + "fetch_size": 1000, + "poll_interval_seconds": 60, + "timestamp_column": "updated_at", + "start_timestamp": "2025-01-01 00:00:00" + } +} +``` + +#### HTTP_SOURCE +```json +{ + "node_id": "source-api-001", + "node_name": "API数据源", + "node_type": "SOURCE", + "operator_type": "HTTP_SOURCE", + "config": { + "url": "https://api.example.com/data", + "method": "GET", + "headers": { + "Authorization": "Bearer ${token}", + "Content-Type": "application/json" + }, + "poll_interval_seconds": 30, + "timeout_seconds": 10, + "retry_times": 3 + } +} +``` + +#### FILE_SOURCE +```json +{ + "node_id": "source-file-001", + "node_name": "CSV文件源", + "node_type": "SOURCE", + "operator_type": "FILE_SOURCE", + "config": { + "path": "/data/input/*.csv", + "format": "CSV", + "charset": "UTF-8", + "delimiter": ",", + "has_header": true, + "watch_mode": "CONTINUOUS", + "scan_interval_seconds": 10 + } +} +``` + +### 2.2 OPERATOR节点 + +Operator节点定义数据转换操作。 + +#### MAP算子 +```json +{ + "node_id": "operator-map-001", + "node_name": "解析JSON", + "node_type": "OPERATOR", + "operator_type": "MAP", + "config": { + "function_class": "com.example.etl.function.ParseJsonFunction", + "function_config": { + "output_fields": ["user_id", "event_type", "timestamp", "properties"] + } + } +} +``` + +#### FILTER算子 +```json +{ + "node_id": "operator-filter-001", + "node_name": "过滤活跃用户", + "node_type": "OPERATOR", + "operator_type": "FILTER", + "config": { + "predicate_class": "com.example.etl.predicate.ActiveUserPredicate", + "predicate_expression": "user.is_active == true AND user.register_days > 7" + } +} +``` + +#### FLATMAP算子 +```json +{ + "node_id": "operator-flatmap-001", + "node_name": "拆分数组", + "node_type": "OPERATOR", + "operator_type": "FLATMAP", + "config": { + "function_class": "com.example.etl.function.SplitArrayFunction", + "source_field": "tags", + "output_field": "tag" + } +} +``` + +#### AGGREGATE算子(有状态) +```json +{ + "node_id": "operator-aggregate-001", + "node_name": "按城市聚合", + "node_type": "OPERATOR", + "operator_type": "AGGREGATE", + "config": { + "group_by_fields": ["city"], + "aggregations": [ + { + "field": "user_id", + "function": "COUNT", + "alias": "user_count" + }, + { + "field": "amount", + "function": "SUM", + "alias": "total_amount" + }, + { + "field": "amount", + "function": "AVG", + "alias": "avg_amount" + } + ], + "window": { + "type": "TUMBLING", + "size": "5m" + } + } +} +``` + +#### WINDOW算子(有状态) +```json +{ + "node_id": "operator-window-001", + "node_name": "5分钟滚动窗口", + "node_type": "OPERATOR", + "operator_type": "WINDOW", + "config": { + "window_type": "TUMBLING", + "window_size": "5m", + "allowed_lateness": "1m", + "trigger": "ON_TIME" + } +} +``` + +#### JOIN算子(有状态) +```json +{ + "node_id": "operator-join-001", + "node_name": "关联用户信息", + "node_type": "OPERATOR", + "operator_type": "JOIN", + "config": { + "join_type": "LEFT", + "left_key": "user_id", + "right_key": "id", + "right_source": { + "type": "CACHE", + "cache_name": "user_info_cache" + }, + "output_fields": ["*", "user.name", "user.age", "user.city"] + } +} +``` + +#### DEDUPLICATE算子(有状态) +```json +{ + "node_id": "operator-dedup-001", + "node_name": "去重", + "node_type": "OPERATOR", + "operator_type": "DEDUPLICATE", + "config": { + "key_fields": ["user_id", "event_id"], + "time_window": "1h", + "keep_first": true + } +} +``` + +### 2.3 SINK节点 + +Sink节点定义数据输出。 + +#### JDBC_SINK +```json +{ + "node_id": "sink-mysql-001", + "node_name": "写入MySQL", + "node_type": "SINK", + "operator_type": "JDBC_SINK", + "config": { + "datasource_id": "mysql-warehouse", + "table": "user_statistics", + "write_mode": "UPSERT", + "unique_key": ["date", "city"], + "batch_size": 100, + "flush_interval_ms": 5000, + "max_retries": 3, + "field_mapping": { + "stat_date": "date", + "city_name": "city", + "user_cnt": "user_count", + "total_amt": "total_amount" + } + } +} +``` + +#### KAFKA_SINK +```json +{ + "node_id": "sink-kafka-001", + "node_name": "写入Kafka", + "node_type": "SINK", + "operator_type": "KAFKA_SINK", + "config": { + "datasource_id": "kafka-prod-cluster", + "topic": "processed-events", + "key_field": "user_id", + "partition_strategy": "HASH", + "serialization": "JSON", + "compression": "gzip", + "acks": "all", + "batch_size": 100, + "linger_ms": 10 + } +} +``` + +#### ELASTICSEARCH_SINK +```json +{ + "node_id": "sink-es-001", + "node_name": "写入ES", + "node_type": "SINK", + "operator_type": "ELASTICSEARCH_SINK", + "config": { + "datasource_id": "elasticsearch-cluster", + "index": "user_events_{date}", + "id_field": "event_id", + "batch_size": 500, + "flush_interval_ms": 3000, + "max_retries": 3 + } +} +``` + +#### FILE_SINK +```json +{ + "node_id": "sink-file-001", + "node_name": "写入文件", + "node_type": "SINK", + "operator_type": "FILE_SINK", + "config": { + "path": "/data/output/result_{date}.json", + "format": "JSON", + "charset": "UTF-8", + "rolling_policy": { + "type": "TIME", + "interval": "1h" + }, + "compression": "gzip" + } +} +``` + +## 3. 边(Edge)定义 + +边描述节点之间的数据流向关系。 + +```json +{ + "edge_id": "edge-001", + "source_node_id": "source-kafka-001", + "target_node_id": "operator-map-001", + "edge_type": "FORWARD" +} +``` + +**边类型**: +- `FORWARD`: 一对一转发(默认) +- `BROADCAST`: 广播到所有下游 +- `SHUFFLE`: 按key重新分区(暂时不用,单机执行) + +## 4. 完整示例 + +### 4.1 简单ETL任务 + +**场景**: 从Kafka读取数据 → 解析JSON → 过滤 → 写入MySQL + +```json +{ + "version": "1.0", + "nodes": [ + { + "node_id": "source-001", + "node_name": "Kafka数据源", + "node_type": "SOURCE", + "operator_type": "KAFKA_SOURCE", + "config": { + "datasource_id": "kafka-prod", + "topics": ["user-events"], + "group_id": "etl-simple", + "auto_offset_reset": "latest" + } + }, + { + "node_id": "op-parse-001", + "node_name": "解析JSON", + "node_type": "OPERATOR", + "operator_type": "MAP", + "config": { + "function_class": "com.example.ParseJsonFunction" + } + }, + { + "node_id": "op-filter-001", + "node_name": "过滤有效数据", + "node_type": "OPERATOR", + "operator_type": "FILTER", + "config": { + "predicate_expression": "data.user_id != null AND data.event_type != null" + } + }, + { + "node_id": "sink-001", + "node_name": "写入MySQL", + "node_type": "SINK", + "operator_type": "JDBC_SINK", + "config": { + "datasource_id": "mysql-warehouse", + "table": "user_events", + "batch_size": 100 + } + } + ], + "edges": [ + { + "edge_id": "edge-001", + "source_node_id": "source-001", + "target_node_id": "op-parse-001" + }, + { + "edge_id": "edge-002", + "source_node_id": "op-parse-001", + "target_node_id": "op-filter-001" + }, + { + "edge_id": "edge-003", + "source_node_id": "op-filter-001", + "target_node_id": "sink-001" + } + ], + "global_config": { + "buffer_size": 1000, + "backpressure_strategy": "BUFFER" + } +} +``` + +### 4.2 带聚合的实时统计任务 + +**场景**: Kafka → 解析 → 窗口聚合 → 写入MySQL和Redis + +```json +{ + "version": "1.0", + "nodes": [ + { + "node_id": "source-001", + "node_name": "订单事件源", + "node_type": "SOURCE", + "operator_type": "KAFKA_SOURCE", + "config": { + "datasource_id": "kafka-prod", + "topics": ["order-events"], + "group_id": "order-stats-etl" + } + }, + { + "node_id": "op-parse-001", + "node_name": "解析订单JSON", + "node_type": "OPERATOR", + "operator_type": "MAP", + "config": { + "function_class": "com.example.ParseOrderFunction" + } + }, + { + "node_id": "op-window-001", + "node_name": "5分钟窗口", + "node_type": "OPERATOR", + "operator_type": "WINDOW", + "config": { + "window_type": "TUMBLING", + "window_size": "5m" + } + }, + { + "node_id": "op-agg-001", + "node_name": "按城市聚合", + "node_type": "OPERATOR", + "operator_type": "AGGREGATE", + "config": { + "group_by_fields": ["city"], + "aggregations": [ + { + "field": "order_id", + "function": "COUNT", + "alias": "order_count" + }, + { + "field": "amount", + "function": "SUM", + "alias": "total_amount" + } + ] + } + }, + { + "node_id": "sink-mysql-001", + "node_name": "写入MySQL", + "node_type": "SINK", + "operator_type": "JDBC_SINK", + "config": { + "datasource_id": "mysql-warehouse", + "table": "order_stats_5m", + "write_mode": "INSERT", + "batch_size": 50 + } + }, + { + "node_id": "sink-redis-001", + "node_name": "写入Redis", + "node_type": "SINK", + "operator_type": "REDIS_SINK", + "config": { + "datasource_id": "redis-cache", + "key_pattern": "order:stats:5m:{city}", + "expire_seconds": 3600 + } + } + ], + "edges": [ + { + "edge_id": "edge-001", + "source_node_id": "source-001", + "target_node_id": "op-parse-001" + }, + { + "edge_id": "edge-002", + "source_node_id": "op-parse-001", + "target_node_id": "op-window-001" + }, + { + "edge_id": "edge-003", + "source_node_id": "op-window-001", + "target_node_id": "op-agg-001" + }, + { + "edge_id": "edge-004", + "source_node_id": "op-agg-001", + "target_node_id": "sink-mysql-001" + }, + { + "edge_id": "edge-005", + "source_node_id": "op-agg-001", + "target_node_id": "sink-redis-001" + } + ], + "global_config": { + "checkpoint_enabled": true, + "checkpoint_interval_seconds": 60 + } +} +``` + +### 4.3 复杂的多分支处理任务 + +**场景**: 一个Source → 多个处理分支 → 多个Sink + +```json +{ + "version": "1.0", + "nodes": [ + { + "node_id": "source-001", + "node_name": "用户行为日志", + "node_type": "SOURCE", + "operator_type": "KAFKA_SOURCE", + "config": { + "datasource_id": "kafka-prod", + "topics": ["user-behavior"], + "group_id": "behavior-etl" + } + }, + { + "node_id": "op-parse-001", + "node_name": "解析日志", + "node_type": "OPERATOR", + "operator_type": "MAP", + "config": { + "function_class": "com.example.ParseBehaviorFunction" + } + }, + { + "node_id": "op-filter-login-001", + "node_name": "过滤登录事件", + "node_type": "OPERATOR", + "operator_type": "FILTER", + "config": { + "predicate_expression": "event_type == 'LOGIN'" + } + }, + { + "node_id": "op-filter-purchase-001", + "node_name": "过滤购买事件", + "node_type": "OPERATOR", + "operator_type": "FILTER", + "config": { + "predicate_expression": "event_type == 'PURCHASE'" + } + }, + { + "node_id": "op-filter-view-001", + "node_name": "过滤浏览事件", + "node_type": "OPERATOR", + "operator_type": "FILTER", + "config": { + "predicate_expression": "event_type == 'VIEW'" + } + }, + { + "node_id": "op-enrich-001", + "node_name": "关联用户信息", + "node_type": "OPERATOR", + "operator_type": "JOIN", + "config": { + "join_type": "LEFT", + "left_key": "user_id", + "right_key": "id", + "right_source": { + "type": "JDBC", + "datasource_id": "mysql-user", + "query": "SELECT id, name, city, vip_level FROM users WHERE id IN (?)" + } + } + }, + { + "node_id": "sink-login-001", + "node_name": "登录日志入库", + "node_type": "SINK", + "operator_type": "JDBC_SINK", + "config": { + "datasource_id": "mysql-log", + "table": "login_logs", + "batch_size": 100 + } + }, + { + "node_id": "sink-purchase-001", + "node_name": "购买记录入库", + "node_type": "SINK", + "operator_type": "JDBC_SINK", + "config": { + "datasource_id": "mysql-order", + "table": "purchase_records", + "batch_size": 50 + } + }, + { + "node_id": "sink-view-001", + "node_name": "浏览行为入ES", + "node_type": "SINK", + "operator_type": "ELASTICSEARCH_SINK", + "config": { + "datasource_id": "es-behavior", + "index": "view_logs_{date}", + "batch_size": 500 + } + }, + { + "node_id": "sink-all-001", + "node_name": "全量数据归档", + "node_type": "SINK", + "operator_type": "FILE_SINK", + "config": { + "path": "/data/archive/behavior_{date}.json", + "format": "JSON", + "rolling_policy": { + "type": "SIZE", + "max_size_mb": 100 + } + } + } + ], + "edges": [ + { + "edge_id": "edge-001", + "source_node_id": "source-001", + "target_node_id": "op-parse-001" + }, + { + "edge_id": "edge-002", + "source_node_id": "op-parse-001", + "target_node_id": "op-filter-login-001" + }, + { + "edge_id": "edge-003", + "source_node_id": "op-parse-001", + "target_node_id": "op-filter-purchase-001" + }, + { + "edge_id": "edge-004", + "source_node_id": "op-parse-001", + "target_node_id": "op-filter-view-001" + }, + { + "edge_id": "edge-005", + "source_node_id": "op-filter-login-001", + "target_node_id": "sink-login-001" + }, + { + "edge_id": "edge-006", + "source_node_id": "op-filter-purchase-001", + "target_node_id": "op-enrich-001" + }, + { + "edge_id": "edge-007", + "source_node_id": "op-enrich-001", + "target_node_id": "sink-purchase-001" + }, + { + "edge_id": "edge-008", + "source_node_id": "op-filter-view-001", + "target_node_id": "sink-view-001" + }, + { + "edge_id": "edge-009", + "source_node_id": "op-parse-001", + "target_node_id": "sink-all-001" + } + ], + "global_config": { + "buffer_size": 2000, + "backpressure_strategy": "DROP_OLDEST", + "checkpoint_enabled": true, + "checkpoint_interval_seconds": 300 + } +} +``` + +### 4.4 批处理任务示例 + +**场景**: 从MySQL增量读取 → 转换 → 写入数据仓库 + +```json +{ + "version": "1.0", + "nodes": [ + { + "node_id": "source-001", + "node_name": "MySQL增量源", + "node_type": "SOURCE", + "operator_type": "JDBC_SOURCE", + "config": { + "datasource_id": "mysql-app", + "query": "SELECT * FROM orders WHERE updated_at > ? AND updated_at <= ? ORDER BY updated_at", + "fetch_size": 5000, + "timestamp_column": "updated_at", + "increment_type": "TIME_BASED" + } + }, + { + "node_id": "op-transform-001", + "node_name": "数据转换", + "node_type": "OPERATOR", + "operator_type": "MAP", + "config": { + "function_class": "com.example.OrderTransformFunction", + "function_config": { + "date_format": "yyyy-MM-dd HH:mm:ss", + "timezone": "Asia/Shanghai" + } + } + }, + { + "node_id": "op-dedup-001", + "node_name": "去重", + "node_type": "OPERATOR", + "operator_type": "DEDUPLICATE", + "config": { + "key_fields": ["order_id"], + "keep_first": false + } + }, + { + "node_id": "sink-001", + "node_name": "写入数仓", + "node_type": "SINK", + "operator_type": "JDBC_SINK", + "config": { + "datasource_id": "mysql-dw", + "table": "dw_orders", + "write_mode": "UPSERT", + "unique_key": ["order_id"], + "batch_size": 1000, + "use_transaction": true + } + } + ], + "edges": [ + { + "edge_id": "edge-001", + "source_node_id": "source-001", + "target_node_id": "op-transform-001" + }, + { + "edge_id": "edge-002", + "source_node_id": "op-transform-001", + "target_node_id": "op-dedup-001" + }, + { + "edge_id": "edge-003", + "source_node_id": "op-dedup-001", + "target_node_id": "sink-001" + } + ], + "global_config": { + "job_type": "BATCH", + "checkpoint_enabled": false + } +} +``` + +## 5. 全局配置说明 + +```json +{ + "global_config": { + "buffer_size": 1000, + "backpressure_strategy": "BUFFER", + "checkpoint_enabled": true, + "checkpoint_interval_seconds": 60, + "restart_on_failure": true, + "max_restart_attempts": 3, + "error_handling": { + "on_source_error": "RETRY", + "on_operator_error": "SKIP", + "on_sink_error": "FAIL" + } + } +} +``` + +**配置项说明**: +- `buffer_size`: 数据缓冲区大小 +- `backpressure_strategy`: 背压策略(BUFFER/DROP/ERROR) +- `checkpoint_enabled`: 是否启用检查点 +- `checkpoint_interval_seconds`: 检查点间隔 +- `error_handling`: 错误处理策略 + +## 6. 图定义的可视化表示 + +### 简单线性流程 +``` +┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ +│ Source │───▶│ Map │───▶│ Filter │───▶│ Sink │ +└──────────┘ └──────────┘ └──────────┘ └──────────┘ +``` + +### 多分支流程 +``` + ┌──────────┐ ┌──────────┐ + ┌──▶│ Filter 1 │───▶│ Sink 1 │ + │ └──────────┘ └──────────┘ +┌──────────┐│ ┌──────────┐ ┌──────────┐ +│ Source ├┼──▶│ Filter 2 │───▶│ Sink 2 │ +└──────────┘│ └──────────┘ └──────────┘ + │ ┌──────────┐ ┌──────────┐ + └──▶│ Filter 3 │───▶│ Sink 3 │ + └──────────┘ └──────────┘ +``` + +### 聚合流程 +``` +┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ +│ Source │───▶│ Window │───▶│ Aggregate│───▶│ Sink │ +└──────────┘ └──────────┘ └──────────┘ └──────────┘ + │ + └──────────────┐ + ▼ + [State Store] +``` + +## 7. 建议和最佳实践 + +### 7.1 节点命名规范 +- 使用有意义的名称 +- 按类型添加前缀:source-、op-、sink- +- 使用连字符分隔单词 + +### 7.2 配置管理 +- 敏感信息使用占位符:`${variable_name}` +- 在运行时从配置中心或环境变量读取 +- 避免硬编码 + +### 7.3 错误处理 +- Source错误:重试 +- Operator错误:跳过或记录到死信队列 +- Sink错误:重试或失败 + +### 7.4 性能优化 +- 合理设置batch_size +- 调整buffer_size避免内存溢出 +- 使用合适的window大小 + +--- + +**文档版本**: v1.0 +**最后更新**: 2025-11-09 diff --git a/docs/graph-definition-improved.md b/docs/graph-definition-improved.md new file mode 100644 index 000000000..5ca70590a --- /dev/null +++ b/docs/graph-definition-improved.md @@ -0,0 +1,543 @@ +# StreamGraph定义改进方案 + +## 问题:上下游关系不清晰 + +当前的graph_definition设计中,节点和边是分离的,不够直观: + +```json +{ + "nodes": [ + {"node_id": "node-1", ...}, + {"node_id": "node-2", ...}, + {"node_id": "node-3", ...} + ], + "edges": [ + {"source_node_id": "node-1", "target_node_id": "node-2"}, + {"source_node_id": "node-2", "target_node_id": "node-3"} + ] +} +``` + +**问题**: +- ❌ 需要在edges中查找才能知道上下游关系 +- ❌ 节点多了之后很难追踪数据流向 +- ❌ 修改连接关系容易出错 + +## 解决方案1:在节点中添加上下游信息(推荐) + +### 方案A:添加辅助字段 + +在每个节点中添加`upstream_nodes`和`downstream_nodes`字段: + +```json +{ + "version": "1.0", + "nodes": [ + { + "node_id": "source-001", + "node_name": "Kafka数据源", + "node_type": "SOURCE", + "operator_type": "KAFKA_SOURCE", + "upstream_nodes": [], + "downstream_nodes": ["op-parse-001"], + "config": {...} + }, + { + "node_id": "op-parse-001", + "node_name": "解析JSON", + "node_type": "OPERATOR", + "operator_type": "MAP", + "upstream_nodes": ["source-001"], + "downstream_nodes": ["op-filter-001"], + "config": {...} + }, + { + "node_id": "op-filter-001", + "node_name": "过滤", + "node_type": "OPERATOR", + "operator_type": "FILTER", + "upstream_nodes": ["op-parse-001"], + "downstream_nodes": ["sink-001"], + "config": {...} + }, + { + "node_id": "sink-001", + "node_name": "写入MySQL", + "node_type": "SINK", + "operator_type": "JDBC_SINK", + "upstream_nodes": ["op-filter-001"], + "downstream_nodes": [], + "config": {...} + } + ], + "edges": [ + {"edge_id": "edge-001", "source_node_id": "source-001", "target_node_id": "op-parse-001"}, + {"edge_id": "edge-002", "source_node_id": "op-parse-001", "target_node_id": "op-filter-001"}, + {"edge_id": "edge-003", "source_node_id": "op-filter-001", "target_node_id": "sink-001"} + ] +} +``` + +**优点**: +- ✅ 一眼就能看出节点的上下游 +- ✅ 保留edges定义,用于详细配置 +- ✅ upstream_nodes和downstream_nodes可以从edges自动生成 + +**缺点**: +- ⚠️ 信息有冗余(需要保持一致性) + +### 方案B:嵌套结构(链式定义) + +直接在节点中定义下游节点: + +```json +{ + "version": "1.0", + "pipeline": { + "source": { + "node_id": "source-001", + "node_name": "Kafka数据源", + "operator_type": "KAFKA_SOURCE", + "config": {...}, + "next": { + "node_id": "op-parse-001", + "node_name": "解析JSON", + "operator_type": "MAP", + "config": {...}, + "next": { + "node_id": "op-filter-001", + "node_name": "过滤", + "operator_type": "FILTER", + "config": {...}, + "next": { + "node_id": "sink-001", + "node_name": "写入MySQL", + "operator_type": "JDBC_SINK", + "config": {...} + } + } + } + } + } +} +``` + +**优点**: +- ✅ 数据流向非常清晰 +- ✅ 适合简单的线性流程 + +**缺点**: +- ❌ 不支持多分支 +- ❌ 不支持复杂的DAG结构 + +## 解决方案2:使用可视化标注 + +在JSON中添加注释和序号: + +```json +{ + "version": "1.0", + "flow_description": "Kafka → Parse → Filter → MySQL", + "nodes": [ + { + "node_id": "source-001", + "node_name": "Kafka数据源", + "node_type": "SOURCE", + "operator_type": "KAFKA_SOURCE", + "sequence": 1, + "description": "第一步:从Kafka读取数据", + "config": {...} + }, + { + "node_id": "op-parse-001", + "node_name": "解析JSON", + "node_type": "OPERATOR", + "operator_type": "MAP", + "sequence": 2, + "description": "第二步:解析JSON数据,输入来自source-001", + "config": {...} + }, + { + "node_id": "op-filter-001", + "node_name": "过滤", + "node_type": "OPERATOR", + "operator_type": "FILTER", + "sequence": 3, + "description": "第三步:过滤有效数据,输入来自op-parse-001", + "config": {...} + }, + { + "node_id": "sink-001", + "node_name": "写入MySQL", + "node_type": "SINK", + "operator_type": "JDBC_SINK", + "sequence": 4, + "description": "第四步:写入MySQL,输入来自op-filter-001", + "config": {...} + } + ], + "edges": [ + { + "edge_id": "edge-001", + "source_node_id": "source-001", + "target_node_id": "op-parse-001", + "description": "Kafka数据源 → 解析JSON" + }, + { + "edge_id": "edge-002", + "source_node_id": "op-parse-001", + "target_node_id": "op-filter-001", + "description": "解析JSON → 过滤" + }, + { + "edge_id": "edge-003", + "source_node_id": "op-filter-001", + "target_node_id": "sink-001", + "description": "过滤 → 写入MySQL" + } + ] +} +``` + +## 解决方案3:辅助工具类 + +提供工具方法快速查询节点关系: + +```java +public class GraphHelper { + + /** + * 获取节点的上游节点列表 + */ + public static List getUpstreamNodes(String nodeId, StreamGraph graph) { + return graph.getEdges().stream() + .filter(edge -> edge.getTargetNodeId().equals(nodeId)) + .map(Edge::getSourceNodeId) + .collect(Collectors.toList()); + } + + /** + * 获取节点的下游节点列表 + */ + public static List getDownstreamNodes(String nodeId, StreamGraph graph) { + return graph.getEdges().stream() + .filter(edge -> edge.getSourceNodeId().equals(nodeId)) + .map(Edge::getTargetNodeId) + .collect(Collectors.toList()); + } + + /** + * 打印节点的上下游关系 + */ + public static void printNodeRelations(StreamGraph graph) { + graph.getNodes().forEach(node -> { + List upstream = getUpstreamNodes(node.getNodeId(), graph); + List downstream = getDownstreamNodes(node.getNodeId(), graph); + + System.out.printf("节点: %s (%s)\n", node.getNodeName(), node.getNodeId()); + System.out.printf(" ← 上游: %s\n", upstream.isEmpty() ? "无" : String.join(", ", upstream)); + System.out.printf(" → 下游: %s\n", downstream.isEmpty() ? "无" : String.join(", ", downstream)); + System.out.println(); + }); + } + + /** + * 生成Mermaid流程图 + */ + public static String generateMermaidDiagram(StreamGraph graph) { + StringBuilder sb = new StringBuilder(); + sb.append("graph LR\n"); + + // 节点定义 + graph.getNodes().forEach(node -> { + sb.append(String.format(" %s[%s]\n", + node.getNodeId(), + node.getNodeName() + )); + }); + + // 边定义 + graph.getEdges().forEach(edge -> { + sb.append(String.format(" %s --> %s\n", + edge.getSourceNodeId(), + edge.getTargetNodeId() + )); + }); + + return sb.toString(); + } +} +``` + +使用示例: + +```java +// 加载StreamGraph +StreamGraph graph = loadFromDatabase(graphId); + +// 打印节点关系 +GraphHelper.printNodeRelations(graph); + +// 输出: +// 节点: Kafka数据源 (source-001) +// ← 上游: 无 +// → 下游: op-parse-001 +// +// 节点: 解析JSON (op-parse-001) +// ← 上游: source-001 +// → 下游: op-filter-001 +// +// 节点: 过滤 (op-filter-001) +// ← 上游: op-parse-001 +// → 下游: sink-001 +// +// 节点: 写入MySQL (sink-001) +// ← 上游: op-filter-001 +// → 下游: 无 + +// 生成可视化图表 +String mermaid = GraphHelper.generateMermaidDiagram(graph); +System.out.println(mermaid); +``` + +## 推荐的最佳实践 + +### 方案:混合使用(推荐) + +**1. JSON中添加辅助信息** + +```json +{ + "version": "1.0", + "metadata": { + "name": "用户事件ETL", + "description": "从Kafka读取用户事件,解析后写入MySQL", + "flow_diagram": "Kafka → Parse → Filter → MySQL" + }, + "nodes": [ + { + "node_id": "source-001", + "node_name": "Kafka数据源", + "node_type": "SOURCE", + "operator_type": "KAFKA_SOURCE", + "position": {"x": 100, "y": 100}, + "config": {...} + }, + { + "node_id": "op-parse-001", + "node_name": "解析JSON", + "node_type": "OPERATOR", + "operator_type": "MAP", + "position": {"x": 300, "y": 100}, + "config": {...} + } + ], + "edges": [ + { + "edge_id": "edge-001", + "source_node_id": "source-001", + "target_node_id": "op-parse-001", + "label": "原始数据" + } + ] +} +``` + +**2. 数据库表添加辅助字段** + +修改`etl_stream_graph`表: + +```sql +ALTER TABLE etl_stream_graph +ADD COLUMN flow_diagram TEXT COMMENT '流程图描述', +ADD COLUMN node_relations JSON COMMENT '节点关系映射'; +``` + +存储时自动生成node_relations: + +```json +{ + "source-001": { + "upstream": [], + "downstream": ["op-parse-001"] + }, + "op-parse-001": { + "upstream": ["source-001"], + "downstream": ["op-filter-001"] + }, + "op-filter-001": { + "upstream": ["op-parse-001"], + "downstream": ["sink-001"] + }, + "sink-001": { + "upstream": ["op-filter-001"], + "downstream": [] + } +} +``` + +**3. 提供可视化界面** + +在Web管理界面提供图形化编辑器: + +``` +┌─────────────────────────────────────────────┐ +│ ETL任务可视化编辑器 │ +├─────────────────────────────────────────────┤ +│ │ +│ ┌─────────┐ ┌─────────┐ ┌────────┐│ +│ │ Kafka │───▶│ Parse │───▶│ Filter ││ +│ │ Source │ │ JSON │ │ ││ +│ └─────────┘ └─────────┘ └────────┘│ +│ │ │ +│ ▼ │ +│ ┌────────┐│ +│ │ MySQL ││ +│ │ Sink ││ +│ └────────┘│ +│ │ +└─────────────────────────────────────────────┘ +``` + +## 完整示例:带关系信息的JSON + +```json +{ + "version": "1.0", + "metadata": { + "name": "用户事件实时处理", + "description": "从Kafka读取用户事件,解析、过滤后写入MySQL", + "flow_diagram": "Kafka Source → Parse JSON → Filter Valid → MySQL Sink", + "created_by": "admin", + "created_at": "2025-11-09T10:00:00Z" + }, + "nodes": [ + { + "node_id": "source-001", + "node_name": "Kafka用户事件源", + "node_type": "SOURCE", + "operator_type": "KAFKA_SOURCE", + "position": {"x": 100, "y": 100}, + "upstream": [], + "downstream": ["op-parse-001"], + "config": { + "datasource_id": "kafka-prod", + "topics": ["user-events"], + "group_id": "user-etl" + } + }, + { + "node_id": "op-parse-001", + "node_name": "解析JSON", + "node_type": "OPERATOR", + "operator_type": "MAP", + "position": {"x": 300, "y": 100}, + "upstream": ["source-001"], + "downstream": ["op-filter-001"], + "config": { + "function_class": "com.example.ParseJsonFunction" + } + }, + { + "node_id": "op-filter-001", + "node_name": "过滤有效数据", + "node_type": "OPERATOR", + "operator_type": "FILTER", + "position": {"x": 500, "y": 100}, + "upstream": ["op-parse-001"], + "downstream": ["sink-001"], + "config": { + "predicate_expression": "user_id != null && event_type != null" + } + }, + { + "node_id": "sink-001", + "node_name": "MySQL用户事件表", + "node_type": "SINK", + "operator_type": "JDBC_SINK", + "position": {"x": 700, "y": 100}, + "upstream": ["op-filter-001"], + "downstream": [], + "config": { + "datasource_id": "mysql-warehouse", + "table": "user_events", + "batch_size": 100 + } + } + ], + "edges": [ + { + "edge_id": "edge-001", + "source_node_id": "source-001", + "target_node_id": "op-parse-001", + "label": "原始消息", + "description": "从Kafka读取的原始JSON消息" + }, + { + "edge_id": "edge-002", + "source_node_id": "op-parse-001", + "target_node_id": "op-filter-001", + "label": "解析后数据", + "description": "解析后的结构化数据" + }, + { + "edge_id": "edge-003", + "source_node_id": "op-filter-001", + "target_node_id": "sink-001", + "label": "有效数据", + "description": "过滤后的有效用户事件" + } + ], + "global_config": { + "buffer_size": 1000, + "checkpoint_enabled": true + } +} +``` + +## 查询节点关系的SQL + +```sql +-- 查询节点及其上下游关系 +SELECT + node_id, + node_name, + upstream, + downstream +FROM ( + SELECT + node_id, + node_name, + JSON_EXTRACT(graph_definition, CONCAT('$.nodes[', idx, '].upstream')) as upstream, + JSON_EXTRACT(graph_definition, CONCAT('$.nodes[', idx, '].downstream')) as downstream + FROM etl_stream_graph, + JSON_TABLE( + graph_definition, + '$.nodes[*]' COLUMNS ( + idx FOR ORDINALITY, + node_id VARCHAR(64) PATH '$.node_id', + node_name VARCHAR(128) PATH '$.node_name' + ) + ) AS nodes_table + WHERE graph_id = 'your-graph-id' +) AS node_relations; +``` + +## 总结 + +**最佳方案组合**: + +1. ✅ 在nodes中添加`upstream`和`downstream`字段(冗余但直观) +2. ✅ 保留edges定义(用于详细配置) +3. ✅ 添加`metadata`和`flow_diagram`(总览描述) +4. ✅ 添加`position`坐标(用于可视化) +5. ✅ 在edge中添加`label`和`description`(说明数据流) +6. ✅ 提供工具类快速查询关系 +7. ✅ 提供Web可视化编辑器 + +这样既保持了灵活性,又提高了可读性! + +--- + +**文档版本**: v1.0 +**最后更新**: 2025-11-09 diff --git a/docs/graph-definition-json-examples.json b/docs/graph-definition-json-examples.json new file mode 100644 index 000000000..9c7d5563c --- /dev/null +++ b/docs/graph-definition-json-examples.json @@ -0,0 +1,757 @@ +{ + "examples": [ + { + "name": "简单ETL - Kafka到MySQL", + "description": "从Kafka读取用户事件,解析JSON后写入MySQL", + "graph_definition": { + "version": "1.0", + "nodes": [ + { + "node_id": "source-kafka-001", + "node_name": "用户事件源", + "node_type": "SOURCE", + "operator_type": "KAFKA_SOURCE", + "config": { + "datasource_id": "kafka-prod", + "topics": ["user-events"], + "group_id": "user-etl-group", + "auto_offset_reset": "latest", + "poll_timeout_ms": 1000, + "max_poll_records": 500 + } + }, + { + "node_id": "op-map-001", + "node_name": "解析JSON", + "node_type": "OPERATOR", + "operator_type": "MAP", + "config": { + "function_class": "com.example.etl.function.ParseJsonFunction", + "function_config": { + "output_fields": ["user_id", "event_type", "event_time", "properties"] + } + } + }, + { + "node_id": "op-filter-001", + "node_name": "过滤有效数据", + "node_type": "OPERATOR", + "operator_type": "FILTER", + "config": { + "predicate_expression": "user_id != null && event_type != null" + } + }, + { + "node_id": "sink-mysql-001", + "node_name": "写入MySQL", + "node_type": "SINK", + "operator_type": "JDBC_SINK", + "config": { + "datasource_id": "mysql-warehouse", + "table": "user_events", + "write_mode": "INSERT", + "batch_size": 100, + "flush_interval_ms": 5000 + } + } + ], + "edges": [ + { + "edge_id": "edge-001", + "source_node_id": "source-kafka-001", + "target_node_id": "op-map-001" + }, + { + "edge_id": "edge-002", + "source_node_id": "op-map-001", + "target_node_id": "op-filter-001" + }, + { + "edge_id": "edge-003", + "source_node_id": "op-filter-001", + "target_node_id": "sink-mysql-001" + } + ], + "global_config": { + "buffer_size": 1000, + "backpressure_strategy": "BUFFER", + "checkpoint_enabled": true, + "checkpoint_interval_seconds": 60 + } + } + }, + { + "name": "实时统计 - 窗口聚合", + "description": "实时统计每5分钟各城市的订单数和金额", + "graph_definition": { + "version": "1.0", + "nodes": [ + { + "node_id": "source-001", + "node_name": "订单事件流", + "node_type": "SOURCE", + "operator_type": "KAFKA_SOURCE", + "config": { + "datasource_id": "kafka-prod", + "topics": ["order-events"], + "group_id": "order-stats-group", + "auto_offset_reset": "latest" + } + }, + { + "node_id": "op-parse-001", + "node_name": "解析订单", + "node_type": "OPERATOR", + "operator_type": "MAP", + "config": { + "function_class": "com.example.etl.function.ParseOrderFunction" + } + }, + { + "node_id": "op-window-001", + "node_name": "5分钟滚动窗口", + "node_type": "OPERATOR", + "operator_type": "WINDOW", + "config": { + "window_type": "TUMBLING", + "window_size": "5m", + "allowed_lateness": "1m" + } + }, + { + "node_id": "op-agg-001", + "node_name": "按城市聚合", + "node_type": "OPERATOR", + "operator_type": "AGGREGATE", + "config": { + "group_by_fields": ["city"], + "aggregations": [ + { + "field": "order_id", + "function": "COUNT", + "alias": "order_count" + }, + { + "field": "amount", + "function": "SUM", + "alias": "total_amount" + }, + { + "field": "amount", + "function": "AVG", + "alias": "avg_amount" + }, + { + "field": "amount", + "function": "MAX", + "alias": "max_amount" + } + ] + } + }, + { + "node_id": "sink-mysql-001", + "node_name": "统计结果入库", + "node_type": "SINK", + "operator_type": "JDBC_SINK", + "config": { + "datasource_id": "mysql-warehouse", + "table": "order_stats_5m", + "write_mode": "INSERT", + "batch_size": 50, + "field_mapping": { + "stat_time": "window_end", + "city_name": "city", + "order_cnt": "order_count", + "total_amt": "total_amount", + "avg_amt": "avg_amount", + "max_amt": "max_amount" + } + } + }, + { + "node_id": "sink-redis-001", + "node_name": "缓存最新统计", + "node_type": "SINK", + "operator_type": "REDIS_SINK", + "config": { + "datasource_id": "redis-cache", + "key_pattern": "order:stats:5m:{city}", + "value_type": "JSON", + "expire_seconds": 3600 + } + } + ], + "edges": [ + { + "edge_id": "edge-001", + "source_node_id": "source-001", + "target_node_id": "op-parse-001" + }, + { + "edge_id": "edge-002", + "source_node_id": "op-parse-001", + "target_node_id": "op-window-001" + }, + { + "edge_id": "edge-003", + "source_node_id": "op-window-001", + "target_node_id": "op-agg-001" + }, + { + "edge_id": "edge-004", + "source_node_id": "op-agg-001", + "target_node_id": "sink-mysql-001" + }, + { + "edge_id": "edge-005", + "source_node_id": "op-agg-001", + "target_node_id": "sink-redis-001" + } + ], + "global_config": { + "buffer_size": 2000, + "checkpoint_enabled": true, + "checkpoint_interval_seconds": 120 + } + } + }, + { + "name": "数据清洗 - 去重和转换", + "description": "从数据库读取数据,去重、转换后写入数据仓库", + "graph_definition": { + "version": "1.0", + "nodes": [ + { + "node_id": "source-001", + "node_name": "MySQL增量读取", + "node_type": "SOURCE", + "operator_type": "JDBC_SOURCE", + "config": { + "datasource_id": "mysql-app", + "query": "SELECT * FROM user_actions WHERE updated_at > ? AND updated_at <= ? ORDER BY updated_at", + "fetch_size": 5000, + "timestamp_column": "updated_at", + "increment_type": "TIME_BASED", + "poll_interval_seconds": 60 + } + }, + { + "node_id": "op-dedup-001", + "node_name": "去重", + "node_type": "OPERATOR", + "operator_type": "DEDUPLICATE", + "config": { + "key_fields": ["user_id", "action_id"], + "time_window": "1h", + "keep_first": true + } + }, + { + "node_id": "op-map-001", + "node_name": "数据转换", + "node_type": "OPERATOR", + "operator_type": "MAP", + "config": { + "function_class": "com.example.etl.function.TransformUserActionFunction", + "function_config": { + "add_fields": { + "etl_time": "current_timestamp", + "source": "mysql-app" + } + } + } + }, + { + "node_id": "op-filter-001", + "node_name": "过滤测试数据", + "node_type": "OPERATOR", + "operator_type": "FILTER", + "config": { + "predicate_expression": "user_id > 100000 && status == 'valid'" + } + }, + { + "node_id": "sink-001", + "node_name": "写入数仓", + "node_type": "SINK", + "operator_type": "JDBC_SINK", + "config": { + "datasource_id": "mysql-dw", + "table": "dw_user_actions", + "write_mode": "UPSERT", + "unique_key": ["user_id", "action_id"], + "batch_size": 1000, + "use_transaction": true + } + } + ], + "edges": [ + { + "edge_id": "edge-001", + "source_node_id": "source-001", + "target_node_id": "op-dedup-001" + }, + { + "edge_id": "edge-002", + "source_node_id": "op-dedup-001", + "target_node_id": "op-map-001" + }, + { + "edge_id": "edge-003", + "source_node_id": "op-map-001", + "target_node_id": "op-filter-001" + }, + { + "edge_id": "edge-004", + "source_node_id": "op-filter-001", + "target_node_id": "sink-001" + } + ], + "global_config": { + "buffer_size": 5000, + "checkpoint_enabled": true, + "checkpoint_interval_seconds": 300 + } + } + }, + { + "name": "多分支处理 - 日志分流", + "description": "读取日志流,按类型分流到不同的存储", + "graph_definition": { + "version": "1.0", + "nodes": [ + { + "node_id": "source-001", + "node_name": "应用日志流", + "node_type": "SOURCE", + "operator_type": "KAFKA_SOURCE", + "config": { + "datasource_id": "kafka-prod", + "topics": ["app-logs"], + "group_id": "log-processor-group" + } + }, + { + "node_id": "op-parse-001", + "node_name": "解析日志", + "node_type": "OPERATOR", + "operator_type": "MAP", + "config": { + "function_class": "com.example.etl.function.ParseLogFunction" + } + }, + { + "node_id": "op-filter-error-001", + "node_name": "过滤ERROR日志", + "node_type": "OPERATOR", + "operator_type": "FILTER", + "config": { + "predicate_expression": "level == 'ERROR'" + } + }, + { + "node_id": "op-filter-warn-001", + "node_name": "过滤WARN日志", + "node_type": "OPERATOR", + "operator_type": "FILTER", + "config": { + "predicate_expression": "level == 'WARN'" + } + }, + { + "node_id": "op-filter-info-001", + "node_name": "过滤INFO日志", + "node_type": "OPERATOR", + "operator_type": "FILTER", + "config": { + "predicate_expression": "level == 'INFO'" + } + }, + { + "node_id": "sink-error-001", + "node_name": "ERROR日志告警", + "node_type": "SINK", + "operator_type": "HTTP_SINK", + "config": { + "url": "https://alert.example.com/api/send", + "method": "POST", + "headers": { + "Content-Type": "application/json", + "Authorization": "Bearer ${alert_token}" + }, + "batch_size": 10, + "timeout_seconds": 5 + } + }, + { + "node_id": "sink-warn-001", + "node_name": "WARN日志入库", + "node_type": "SINK", + "operator_type": "JDBC_SINK", + "config": { + "datasource_id": "mysql-log", + "table": "warn_logs", + "batch_size": 100 + } + }, + { + "node_id": "sink-all-001", + "node_name": "全量日志存储", + "node_type": "SINK", + "operator_type": "ELASTICSEARCH_SINK", + "config": { + "datasource_id": "es-log-cluster", + "index": "app_logs_{date}", + "id_field": "log_id", + "batch_size": 500, + "flush_interval_ms": 3000 + } + } + ], + "edges": [ + { + "edge_id": "edge-001", + "source_node_id": "source-001", + "target_node_id": "op-parse-001" + }, + { + "edge_id": "edge-002", + "source_node_id": "op-parse-001", + "target_node_id": "op-filter-error-001" + }, + { + "edge_id": "edge-003", + "source_node_id": "op-parse-001", + "target_node_id": "op-filter-warn-001" + }, + { + "edge_id": "edge-004", + "source_node_id": "op-parse-001", + "target_node_id": "op-filter-info-001" + }, + { + "edge_id": "edge-005", + "source_node_id": "op-filter-error-001", + "target_node_id": "sink-error-001" + }, + { + "edge_id": "edge-006", + "source_node_id": "op-filter-warn-001", + "target_node_id": "sink-warn-001" + }, + { + "edge_id": "edge-007", + "source_node_id": "op-parse-001", + "target_node_id": "sink-all-001" + } + ], + "global_config": { + "buffer_size": 3000, + "backpressure_strategy": "DROP_OLDEST", + "checkpoint_enabled": true, + "checkpoint_interval_seconds": 180 + } + } + }, + { + "name": "API数据采集", + "description": "定期从HTTP API拉取数据并存储", + "graph_definition": { + "version": "1.0", + "nodes": [ + { + "node_id": "source-001", + "node_name": "API数据源", + "node_type": "SOURCE", + "operator_type": "HTTP_SOURCE", + "config": { + "url": "https://api.example.com/v1/users", + "method": "GET", + "headers": { + "Authorization": "Bearer ${api_token}", + "Accept": "application/json" + }, + "query_params": { + "page_size": "1000", + "updated_after": "${last_updated_time}" + }, + "poll_interval_seconds": 300, + "timeout_seconds": 30, + "retry_times": 3 + } + }, + { + "node_id": "op-flatmap-001", + "node_name": "展开数组", + "node_type": "OPERATOR", + "operator_type": "FLATMAP", + "config": { + "function_class": "com.example.etl.function.FlattenArrayFunction", + "source_field": "data.users" + } + }, + { + "node_id": "op-map-001", + "node_name": "字段映射", + "node_type": "OPERATOR", + "operator_type": "MAP", + "config": { + "function_class": "com.example.etl.function.MapFieldsFunction", + "field_mapping": { + "id": "user_id", + "name": "user_name", + "email": "user_email", + "created_at": "create_time", + "updated_at": "update_time" + } + } + }, + { + "node_id": "sink-001", + "node_name": "写入MySQL", + "node_type": "SINK", + "operator_type": "JDBC_SINK", + "config": { + "datasource_id": "mysql-user", + "table": "users", + "write_mode": "UPSERT", + "unique_key": ["user_id"], + "batch_size": 200 + } + } + ], + "edges": [ + { + "edge_id": "edge-001", + "source_node_id": "source-001", + "target_node_id": "op-flatmap-001" + }, + { + "edge_id": "edge-002", + "source_node_id": "op-flatmap-001", + "target_node_id": "op-map-001" + }, + { + "edge_id": "edge-003", + "source_node_id": "op-map-001", + "target_node_id": "sink-001" + } + ], + "global_config": { + "buffer_size": 1000, + "checkpoint_enabled": false + } + } + }, + { + "name": "文件处理 - CSV到JSON", + "description": "读取CSV文件,转换为JSON后写入Kafka", + "graph_definition": { + "version": "1.0", + "nodes": [ + { + "node_id": "source-001", + "node_name": "CSV文件源", + "node_type": "SOURCE", + "operator_type": "FILE_SOURCE", + "config": { + "path": "/data/input/*.csv", + "format": "CSV", + "charset": "UTF-8", + "delimiter": ",", + "has_header": true, + "watch_mode": "CONTINUOUS", + "scan_interval_seconds": 30, + "file_filter": "user_export_*.csv" + } + }, + { + "node_id": "op-map-001", + "node_name": "转换为JSON", + "node_type": "OPERATOR", + "operator_type": "MAP", + "config": { + "function_class": "com.example.etl.function.CsvToJsonFunction", + "function_config": { + "include_metadata": true, + "timestamp_format": "yyyy-MM-dd HH:mm:ss" + } + } + }, + { + "node_id": "op-filter-001", + "node_name": "过滤空行", + "node_type": "OPERATOR", + "operator_type": "FILTER", + "config": { + "predicate_expression": "row_data != null && row_data.trim() != ''" + } + }, + { + "node_id": "sink-kafka-001", + "node_name": "写入Kafka", + "node_type": "SINK", + "operator_type": "KAFKA_SINK", + "config": { + "datasource_id": "kafka-prod", + "topic": "user-import", + "key_field": "user_id", + "serialization": "JSON", + "compression": "gzip", + "batch_size": 100 + } + }, + { + "node_id": "sink-file-001", + "node_name": "归档JSON文件", + "node_type": "SINK", + "operator_type": "FILE_SINK", + "config": { + "path": "/data/archive/users_{date}.json", + "format": "JSON", + "charset": "UTF-8", + "rolling_policy": { + "type": "SIZE", + "max_size_mb": 100 + }, + "compression": "gzip" + } + } + ], + "edges": [ + { + "edge_id": "edge-001", + "source_node_id": "source-001", + "target_node_id": "op-map-001" + }, + { + "edge_id": "edge-002", + "source_node_id": "op-map-001", + "target_node_id": "op-filter-001" + }, + { + "edge_id": "edge-003", + "source_node_id": "op-filter-001", + "target_node_id": "sink-kafka-001" + }, + { + "edge_id": "edge-004", + "source_node_id": "op-filter-001", + "target_node_id": "sink-file-001" + } + ], + "global_config": { + "buffer_size": 2000, + "checkpoint_enabled": true, + "checkpoint_interval_seconds": 300 + } + } + }, + { + "name": "数据关联 - JOIN操作", + "description": "订单流关联用户信息和商品信息", + "graph_definition": { + "version": "1.0", + "nodes": [ + { + "node_id": "source-001", + "node_name": "订单流", + "node_type": "SOURCE", + "operator_type": "KAFKA_SOURCE", + "config": { + "datasource_id": "kafka-prod", + "topics": ["orders"], + "group_id": "order-enrich-group" + } + }, + { + "node_id": "op-parse-001", + "node_name": "解析订单", + "node_type": "OPERATOR", + "operator_type": "MAP", + "config": { + "function_class": "com.example.etl.function.ParseOrderFunction" + } + }, + { + "node_id": "op-join-user-001", + "node_name": "关联用户信息", + "node_type": "OPERATOR", + "operator_type": "JOIN", + "config": { + "join_type": "LEFT", + "left_key": "user_id", + "right_key": "id", + "right_source": { + "type": "JDBC", + "datasource_id": "mysql-user", + "query": "SELECT id, name, city, vip_level FROM users WHERE id IN (?)", + "cache_enabled": true, + "cache_ttl_seconds": 300, + "cache_max_size": 10000 + }, + "output_fields": ["*", "user.name as user_name", "user.city as user_city", "user.vip_level"] + } + }, + { + "node_id": "op-join-product-001", + "node_name": "关联商品信息", + "node_type": "OPERATOR", + "operator_type": "JOIN", + "config": { + "join_type": "LEFT", + "left_key": "product_id", + "right_key": "id", + "right_source": { + "type": "REDIS", + "datasource_id": "redis-cache", + "key_pattern": "product:info:{product_id}" + }, + "output_fields": ["*", "product.name as product_name", "product.category", "product.price"] + } + }, + { + "node_id": "sink-001", + "node_name": "写入ES", + "node_type": "SINK", + "operator_type": "ELASTICSEARCH_SINK", + "config": { + "datasource_id": "es-order-cluster", + "index": "order_detail_{date}", + "id_field": "order_id", + "batch_size": 200 + } + } + ], + "edges": [ + { + "edge_id": "edge-001", + "source_node_id": "source-001", + "target_node_id": "op-parse-001" + }, + { + "edge_id": "edge-002", + "source_node_id": "op-parse-001", + "target_node_id": "op-join-user-001" + }, + { + "edge_id": "edge-003", + "source_node_id": "op-join-user-001", + "target_node_id": "op-join-product-001" + }, + { + "edge_id": "edge-004", + "source_node_id": "op-join-product-001", + "target_node_id": "sink-001" + } + ], + "global_config": { + "buffer_size": 1500, + "checkpoint_enabled": true, + "checkpoint_interval_seconds": 120 + } + } + } + ] +} diff --git a/docs/graph-example-with-relations.json b/docs/graph-example-with-relations.json new file mode 100644 index 000000000..3be6f7fca --- /dev/null +++ b/docs/graph-example-with-relations.json @@ -0,0 +1,233 @@ +{ + "简单示例_带关系信息": { + "version": "1.0", + "metadata": { + "name": "用户事件ETL", + "flow_diagram": "Kafka → Parse → Filter → MySQL", + "description": "从Kafka读取用户事件,解析JSON后过滤,写入MySQL" + }, + "nodes": [ + { + "node_id": "source-001", + "node_name": "Kafka数据源", + "node_type": "SOURCE", + "operator_type": "KAFKA_SOURCE", + "upstream": [], + "downstream": ["op-parse-001"], + "description": "【第1步】从Kafka读取原始数据 → 发送给 op-parse-001", + "config": { + "datasource_id": "kafka-prod", + "topics": ["user-events"], + "group_id": "user-etl" + } + }, + { + "node_id": "op-parse-001", + "node_name": "解析JSON", + "node_type": "OPERATOR", + "operator_type": "MAP", + "upstream": ["source-001"], + "downstream": ["op-filter-001"], + "description": "【第2步】接收 source-001 的数据,解析JSON → 发送给 op-filter-001", + "config": { + "function_class": "com.example.ParseJsonFunction" + } + }, + { + "node_id": "op-filter-001", + "node_name": "过滤有效数据", + "node_type": "OPERATOR", + "operator_type": "FILTER", + "upstream": ["op-parse-001"], + "downstream": ["sink-001"], + "description": "【第3步】接收 op-parse-001 的数据,过滤 → 发送给 sink-001", + "config": { + "predicate_expression": "user_id != null && event_type != null" + } + }, + { + "node_id": "sink-001", + "node_name": "写入MySQL", + "node_type": "SINK", + "operator_type": "JDBC_SINK", + "upstream": ["op-filter-001"], + "downstream": [], + "description": "【第4步】接收 op-filter-001 的数据,写入MySQL", + "config": { + "datasource_id": "mysql-warehouse", + "table": "user_events", + "batch_size": 100 + } + } + ], + "edges": [ + { + "edge_id": "edge-001", + "source_node_id": "source-001", + "target_node_id": "op-parse-001", + "label": "原始JSON消息" + }, + { + "edge_id": "edge-002", + "source_node_id": "op-parse-001", + "target_node_id": "op-filter-001", + "label": "解析后的对象" + }, + { + "edge_id": "edge-003", + "source_node_id": "op-filter-001", + "target_node_id": "sink-001", + "label": "有效数据" + } + ] + }, + "多分支示例_带关系信息": { + "version": "1.0", + "metadata": { + "name": "日志分流处理", + "flow_diagram": "Kafka → Parse → [ERROR→HTTP, WARN→MySQL, ALL→ES]", + "description": "读取日志流,按级别分流到不同存储" + }, + "nodes": [ + { + "node_id": "source-001", + "node_name": "日志流", + "node_type": "SOURCE", + "operator_type": "KAFKA_SOURCE", + "upstream": [], + "downstream": ["op-parse-001"], + "description": "【第1步】从Kafka读取日志 → 发送给 op-parse-001", + "config": { + "datasource_id": "kafka-prod", + "topics": ["app-logs"] + } + }, + { + "node_id": "op-parse-001", + "node_name": "解析日志", + "node_type": "OPERATOR", + "operator_type": "MAP", + "upstream": ["source-001"], + "downstream": ["op-filter-error-001", "op-filter-warn-001", "sink-es-001"], + "description": "【第2步】接收 source-001 的数据,解析 → 分发给3个下游节点", + "config": { + "function_class": "com.example.ParseLogFunction" + } + }, + { + "node_id": "op-filter-error-001", + "node_name": "过滤ERROR", + "node_type": "OPERATOR", + "operator_type": "FILTER", + "upstream": ["op-parse-001"], + "downstream": ["sink-http-001"], + "description": "【第3步-分支1】接收 op-parse-001 的数据,过滤ERROR → 发送给 sink-http-001", + "config": { + "predicate_expression": "level == 'ERROR'" + } + }, + { + "node_id": "op-filter-warn-001", + "node_name": "过滤WARN", + "node_type": "OPERATOR", + "operator_type": "FILTER", + "upstream": ["op-parse-001"], + "downstream": ["sink-mysql-001"], + "description": "【第3步-分支2】接收 op-parse-001 的数据,过滤WARN → 发送给 sink-mysql-001", + "config": { + "predicate_expression": "level == 'WARN'" + } + }, + { + "node_id": "sink-http-001", + "node_name": "告警API", + "node_type": "SINK", + "operator_type": "HTTP_SINK", + "upstream": ["op-filter-error-001"], + "downstream": [], + "description": "【第4步-分支1】接收 op-filter-error-001 的数据,发送告警", + "config": { + "url": "https://alert.example.com/api/send" + } + }, + { + "node_id": "sink-mysql-001", + "node_name": "WARN日志表", + "node_type": "SINK", + "operator_type": "JDBC_SINK", + "upstream": ["op-filter-warn-001"], + "downstream": [], + "description": "【第4步-分支2】接收 op-filter-warn-001 的数据,写入MySQL", + "config": { + "datasource_id": "mysql-log", + "table": "warn_logs" + } + }, + { + "node_id": "sink-es-001", + "node_name": "全量日志ES", + "node_type": "SINK", + "operator_type": "ELASTICSEARCH_SINK", + "upstream": ["op-parse-001"], + "downstream": [], + "description": "【第3步-分支3】接收 op-parse-001 的全量数据,写入ES", + "config": { + "datasource_id": "es-log", + "index": "app_logs_{date}" + } + } + ], + "edges": [ + { + "edge_id": "edge-001", + "source_node_id": "source-001", + "target_node_id": "op-parse-001", + "label": "原始日志" + }, + { + "edge_id": "edge-002", + "source_node_id": "op-parse-001", + "target_node_id": "op-filter-error-001", + "label": "所有日志(分支1)" + }, + { + "edge_id": "edge-003", + "source_node_id": "op-parse-001", + "target_node_id": "op-filter-warn-001", + "label": "所有日志(分支2)" + }, + { + "edge_id": "edge-004", + "source_node_id": "op-parse-001", + "target_node_id": "sink-es-001", + "label": "所有日志(分支3)" + }, + { + "edge_id": "edge-005", + "source_node_id": "op-filter-error-001", + "target_node_id": "sink-http-001", + "label": "ERROR日志" + }, + { + "edge_id": "edge-006", + "source_node_id": "op-filter-warn-001", + "target_node_id": "sink-mysql-001", + "label": "WARN日志" + } + ] + }, + "如何阅读": { + "说明": "每个节点都包含upstream和downstream字段,可以直接看出上下游关系", + "upstream": "当前节点的数据来自哪些节点(数组)", + "downstream": "当前节点的数据发送给哪些节点(数组)", + "description": "用中文描述了数据流向,更容易理解", + "示例": { + "节点定义": { + "node_id": "op-parse-001", + "upstream": ["source-001"], + "downstream": ["op-filter-001"], + "含义": "这个节点从source-001接收数据,处理后发送给op-filter-001" + } + } + } +} diff --git a/docs/json-examples-guide.md b/docs/json-examples-guide.md new file mode 100644 index 000000000..412cc4f4a --- /dev/null +++ b/docs/json-examples-guide.md @@ -0,0 +1,386 @@ +# StreamGraph JSON配置示例指南 + +## 概述 + +本文档提供了7个完整的、可直接使用的StreamGraph JSON配置示例,涵盖常见的ETL场景。 + +完整的JSON文件位于:`graph-definition-json-examples.json` + +## 示例列表 + +### 1. 简单ETL - Kafka到MySQL + +**场景**: 从Kafka读取用户事件,解析JSON后写入MySQL + +**数据流程**: +``` +Kafka Source → Parse JSON → Filter → MySQL Sink +``` + +**适用场景**: +- 基础数据采集 +- 消息队列到数据库同步 +- 实时数据入库 + +**关键配置**: +```json +{ + "source": "KAFKA_SOURCE", + "operators": ["MAP", "FILTER"], + "sink": "JDBC_SINK" +} +``` + +--- + +### 2. 实时统计 - 窗口聚合 + +**场景**: 实时统计每5分钟各城市的订单数和金额 + +**数据流程**: +``` +Kafka Source → Parse → Window(5m) → Aggregate → MySQL + Redis +``` + +**适用场景**: +- 实时监控大屏 +- 业务指标统计 +- 实时报表 + +**特点**: +- ✅ 有状态计算(Window + Aggregate) +- ✅ 多Sink输出(数据库 + 缓存) +- ✅ 支持检查点容错 + +**聚合函数**: +- COUNT: 订单数量 +- SUM: 总金额 +- AVG: 平均金额 +- MAX: 最大金额 + +--- + +### 3. 数据清洗 - 去重和转换 + +**场景**: 从数据库读取数据,去重、转换后写入数据仓库 + +**数据流程**: +``` +JDBC Source → Deduplicate → Transform → Filter → JDBC Sink +``` + +**适用场景**: +- 数据同步 +- 离线数据处理 +- 数据仓库ETL + +**特点**: +- ✅ 增量读取(基于时间戳) +- ✅ 去重操作(DEDUPLICATE) +- ✅ UPSERT写入模式 +- ✅ 事务支持 + +--- + +### 4. 多分支处理 - 日志分流 + +**场景**: 读取日志流,按日志级别分流到不同的存储 + +**数据流程**: +``` + ┌→ Filter(ERROR) → HTTP Alert +Kafka Source ────┼→ Filter(WARN) → MySQL + └→ All Logs → Elasticsearch +``` + +**适用场景**: +- 日志分析 +- 告警系统 +- 日志归档 + +**特点**: +- ✅ 一个Source多个Sink +- ✅ 条件分支处理 +- ✅ 不同级别不同处理策略 + +--- + +### 5. API数据采集 + +**场景**: 定期从HTTP API拉取数据并存储 + +**数据流程**: +``` +HTTP Source → FlatMap → Map → JDBC Sink +``` + +**适用场景**: +- 第三方API数据同步 +- 定时数据拉取 +- 外部数据集成 + +**特点**: +- ✅ 周期性拉取(poll_interval) +- ✅ 数组展开(FlatMap) +- ✅ 字段映射 +- ✅ 重试机制 + +--- + +### 6. 文件处理 - CSV到JSON + +**场景**: 读取CSV文件,转换为JSON后写入Kafka和归档 + +**数据流程**: +``` + ┌→ Kafka Sink +File Source → Map ─┤ + └→ File Sink (JSON) +``` + +**适用场景**: +- 文件导入 +- 数据格式转换 +- 批量数据处理 + +**特点**: +- ✅ 文件监控(watch_mode) +- ✅ CSV解析 +- ✅ 多目标输出 +- ✅ 文件归档 + +--- + +### 7. 数据关联 - JOIN操作 + +**场景**: 订单流关联用户信息和商品信息 + +**数据流程**: +``` +Kafka Source → Parse → Join(User) → Join(Product) → ES Sink +``` + +**适用场景**: +- 数据补全 +- 维度关联 +- 实时宽表 + +**特点**: +- ✅ 多次JOIN操作 +- ✅ 支持缓存(提高性能) +- ✅ 从MySQL/Redis读取维度数据 +- ✅ 字段别名 + +--- + +## 如何使用这些示例 + +### 方法1: 直接插入数据库 + +```sql +-- 插入StreamGraph +INSERT INTO etl_stream_graph (graph_id, graph_name, job_id, graph_definition) +VALUES ( + 'graph-001', + '简单ETL任务', + 'job-001', + '这里粘贴完整的graph_definition JSON' +); +``` + +### 方法2: 通过API创建 + +```bash +curl -X POST http://localhost:8080/api/stream-graphs \ + -H "Content-Type: application/json" \ + -d @graph-definition-json-examples.json +``` + +### 方法3: 使用可视化界面 + +1. 登录Web管理界面 +2. 点击"创建任务" +3. 选择"导入JSON" +4. 粘贴对应的graph_definition +5. 保存并提交 + +## 配置说明 + +### 常用配置项 + +#### Source配置 +```json +{ + "datasource_id": "数据源ID(在etl_datasource表中)", + "topics": ["Kafka主题列表"], + "group_id": "消费者组ID", + "poll_interval_seconds": "轮询间隔(秒)" +} +``` + +#### Operator配置 +```json +{ + "function_class": "自定义函数类全限定名", + "predicate_expression": "过滤条件表达式", + "group_by_fields": ["分组字段"], + "window_size": "窗口大小(如5m、1h)" +} +``` + +#### Sink配置 +```json +{ + "datasource_id": "目标数据源ID", + "table": "目标表名", + "batch_size": 100, + "write_mode": "INSERT/UPSERT/UPDATE" +} +``` + +### 全局配置 + +```json +{ + "buffer_size": 1000, + "backpressure_strategy": "BUFFER/DROP/ERROR", + "checkpoint_enabled": true, + "checkpoint_interval_seconds": 60 +} +``` + +## 节点类型速查 + +| 节点类型 | operator_type | 说明 | +| --- | --- | --- | +| Source | KAFKA_SOURCE | Kafka数据源 | +| Source | JDBC_SOURCE | 数据库数据源 | +| Source | HTTP_SOURCE | HTTP API数据源 | +| Source | FILE_SOURCE | 文件数据源 | +| Operator | MAP | 一对一转换 | +| Operator | FILTER | 数据过滤 | +| Operator | FLATMAP | 一对多转换 | +| Operator | AGGREGATE | 聚合计算 | +| Operator | WINDOW | 窗口计算 | +| Operator | JOIN | 数据关联 | +| Operator | DEDUPLICATE | 数据去重 | +| Sink | JDBC_SINK | 数据库写入 | +| Sink | KAFKA_SINK | Kafka写入 | +| Sink | ELASTICSEARCH_SINK | ES写入 | +| Sink | FILE_SINK | 文件写入 | +| Sink | REDIS_SINK | Redis写入 | +| Sink | HTTP_SINK | HTTP API写入 | + +## 配置模板 + +### 最小配置(必填字段) + +```json +{ + "version": "1.0", + "nodes": [ + { + "node_id": "必填-唯一标识", + "node_name": "必填-显示名称", + "node_type": "必填-SOURCE/OPERATOR/SINK", + "operator_type": "必填-具体算子类型", + "config": {} + } + ], + "edges": [ + { + "edge_id": "必填-唯一标识", + "source_node_id": "必填-源节点ID", + "target_node_id": "必填-目标节点ID" + } + ] +} +``` + +### 完整配置(包含可选字段) + +```json +{ + "version": "1.0", + "nodes": [...], + "edges": [...], + "global_config": { + "buffer_size": 1000, + "backpressure_strategy": "BUFFER", + "checkpoint_enabled": true, + "checkpoint_interval_seconds": 60, + "restart_on_failure": true, + "max_restart_attempts": 3, + "error_handling": { + "on_source_error": "RETRY", + "on_operator_error": "SKIP", + "on_sink_error": "FAIL" + } + } +} +``` + +## 常见问题 + +### Q1: 如何添加自定义算子? + +修改nodes中的operator配置: +```json +{ + "operator_type": "MAP", + "config": { + "function_class": "com.yourcompany.YourCustomFunction", + "function_config": { + "param1": "value1" + } + } +} +``` + +### Q2: 如何实现一个Source多个Sink? + +添加多个edge指向不同的Sink: +```json +{ + "edges": [ + {"source_node_id": "op-001", "target_node_id": "sink-001"}, + {"source_node_id": "op-001", "target_node_id": "sink-002"}, + {"source_node_id": "op-001", "target_node_id": "sink-003"} + ] +} +``` + +### Q3: 如何配置检查点? + +在global_config中设置: +```json +{ + "global_config": { + "checkpoint_enabled": true, + "checkpoint_interval_seconds": 60 + } +} +``` + +### Q4: 数据源ID在哪里配置? + +数据源需要先在`etl_datasource`表中创建,然后在配置中引用其datasource_id。 + +### Q5: 如何调试配置? + +1. 使用JSON验证工具检查语法 +2. 先创建简单的任务测试 +3. 查看任务执行日志 +4. 使用监控指标分析性能 + +## 下一步 + +- 查看完整的JSON文件:`graph-definition-json-examples.json` +- 阅读详细的配置说明:`graph-definition-examples.md` +- 参考数据库设计文档:`database-design.md` +- 查看系统设计文档:`reactive-etl-framework-design.md` + +--- + +**文档版本**: v1.0 +**最后更新**: 2025-11-09 diff --git a/docs/reactive-etl-framework-design.md b/docs/reactive-etl-framework-design.md new file mode 100644 index 000000000..d71660710 --- /dev/null +++ b/docs/reactive-etl-framework-design.md @@ -0,0 +1,1501 @@ +# 响应式ETL框架设计文档 + +## 1. 概述 + +### 1.1 项目背景 + +本项目旨在设计并实现一个基于响应式编程模型的轻量级ETL(Extract-Transform-Load)数据采集框架。该框架借鉴Apache Flink的设计理念,采用Source、Operator、Sink的经典数据处理模型,并基于Project Reactor实现完全响应式的数据流处理。 + +### 1.2 设计目标 + +- **响应式流处理**:基于Reactor实现非阻塞、背压支持的数据流处理 +- **模块化设计**:清晰的任务调度、图转换、执行引擎分层架构 +- **高性能**:充分利用响应式编程的优势,支持高吞吐量数据处理 +- **易用性**:提供简洁的API,支持声明式任务定义 +- **可观测性**:内置监控指标和日志,方便运维调试 +- **可扩展性**:基于Connectors的插件化扩展机制 + +### 1.3 核心特性 + +- 声明式任务定义(StreamGraph → JobGraph转换) +- 灵活的任务调度机制(Job Scheduler) +- 高效的执行引擎(Job Executor) +- 丰富的连接器生态(Connectors) +- 内置背压机制,防止内存溢出 +- 支持有状态计算和检查点容错 + +## 2. 系统架构 + +### 2.1 整体架构图 + +```mermaid +graph TB + subgraph "User API Layer" + API[Stream API] + DSL[Job DSL] + end + + subgraph "Job Definition Layer" + SG[StreamGraph] + JG[JobGraph] + end + + subgraph "Scheduling Layer" + JS[Job Scheduler] + JM[Job Manager] + end + + subgraph "Execution Layer" + JE[Job Executor] + RT[Reactor Runtime] + end + + subgraph "Operator Layer" + SRC[Source] + OPS[Operators] + SNK[Sink] + end + + subgraph "Connector Layer" + JDBC[JDBC Connector] + KAFKA[Kafka Connector] + HTTP[HTTP Connector] + FILE[File Connector] + CUSTOM[Custom Connectors] + end + + subgraph "Infrastructure Layer" + SM[State Manager] + CP[Checkpoint Manager] + MT[Metrics Collector] + end + + API --> SG + DSL --> SG + SG --> JG + JG --> JS + JS --> JM + JM --> JE + JE --> RT + RT --> SRC + RT --> OPS + RT --> SNK + + SRC -.-> JDBC + SRC -.-> KAFKA + SRC -.-> HTTP + SRC -.-> FILE + SNK -.-> JDBC + SNK -.-> KAFKA + SNK -.-> HTTP + SNK -.-> FILE + + JDBC -.-> CUSTOM + KAFKA -.-> CUSTOM + + OPS -.-> SM + SM -.-> CP + JE -.-> MT +``` + +### 2.2 架构分层说明 + +#### 2.2.1 用户API层(User API Layer) +提供友好的编程接口,允许用户通过流式API或DSL定义ETL任务。 + +#### 2.2.2 任务定义层(Job Definition Layer) +- **StreamGraph**:用户定义的逻辑执行图,描述数据流转换关系 +- **JobGraph**:优化后的物理执行图,可实际调度执行 + +#### 2.2.3 调度层(Scheduling Layer) +- **Job Scheduler**:负责任务的调度策略(立即执行、定时执行、依赖触发等) +- **Job Manager**:管理任务的生命周期(创建、启动、停止、重启) + +#### 2.2.4 执行层(Execution Layer) +- **Job Executor**:任务的实际执行引擎 +- **Reactor Runtime**:响应式运行时环境 + +#### 2.2.5 算子层(Operator Layer) +核心的数据处理组件,包括Source、Operator、Sink。 + +#### 2.2.6 连接器层(Connector Layer) +提供与各种外部系统交互的能力,采用插件化设计。 + +#### 2.2.7 基础设施层(Infrastructure Layer) +提供状态管理、检查点、监控等基础能力。 + +### 2.3 模块依赖关系图 + +```mermaid +graph LR + Job --> StreamGraph + StreamGraph --> JobGraph + JobGraph --> JobScheduler + JobScheduler --> JobExecutor + JobExecutor --> Source + JobExecutor --> Operator + JobExecutor --> Sink + Source --> Connectors + Sink --> Connectors + Operator --> StateManager + StateManager --> CheckpointManager +``` + +## 3. 核心模块设计 + +### 3.1 Job模块 + +#### 3.1.1 设计理念 + +Job是ETL任务的最小执行单元,封装了完整的数据处理逻辑。每个Job包含唯一标识、配置信息、执行状态等元数据。 + +#### 3.1.2 Job生命周期 + +```mermaid +stateDiagram-v2 + [*] --> CREATED: create() + CREATED --> SCHEDULED: schedule() + SCHEDULED --> RUNNING: start() + RUNNING --> PAUSED: pause() + PAUSED --> RUNNING: resume() + RUNNING --> COMPLETED: success + RUNNING --> FAILED: error + FAILED --> RUNNING: retry() + RUNNING --> CANCELLED: cancel() + COMPLETED --> [*] + FAILED --> [*] + CANCELLED --> [*] +``` + +#### 3.1.3 Job元数据结构 + +```java +public class Job { + private String jobId; // 任务唯一标识 + private String jobName; // 任务名称 + private JobType jobType; // 任务类型:STREAMING/BATCH + private JobStatus status; // 任务状态 + private JobConfig config; // 任务配置 + private JobGraph jobGraph; // 执行图 + private Instant createTime; // 创建时间 + private Instant startTime; // 启动时间 + private Instant endTime; // 结束时间 + private Map metadata; // 扩展元数据 +} +``` + +### 3.2 StreamGraph模块 + +#### 3.2.1 设计理念 + +StreamGraph是用户定义的逻辑执行图,直接映射用户的API调用。它是一个有向无环图(DAG),节点代表算子,边代表数据流向。 + +#### 3.2.2 StreamGraph结构 + +```mermaid +graph LR + SN1[Source Node] --> TN1[Transform Node 1] + TN1 --> TN2[Transform Node 2] + TN1 --> TN3[Transform Node 3] + TN2 --> TN4[Transform Node 4] + TN3 --> TN4 + TN4 --> SK1[Sink Node] +``` + +#### 3.2.3 StreamNode定义 + +```java +public class StreamNode { + private int nodeId; // 节点ID + private String operatorName; // 算子名称 + private OperatorType operatorType; // 算子类型 + private List inEdges; // 输入边 + private List outEdges; // 输出边 + private int parallelism; // 并行度 + private Map config; // 节点配置 +} +``` + +#### 3.2.4 StreamGraph构建 + +用户通过流式API构建StreamGraph: + +```java +StreamGraph graph = StreamGraph.builder() + .addSource("source-1", new KafkaSource(config)) + .addOperator("map-1", new MapOperator(mapper)) + .addOperator("filter-1", new FilterOperator(predicate)) + .addSink("sink-1", new JdbcSink(config)) + .connect("source-1", "map-1") + .connect("map-1", "filter-1") + .connect("filter-1", "sink-1") + .build(); +``` + +### 3.3 JobGraph模块 + +#### 3.3.1 设计理念 + +JobGraph是StreamGraph经过优化后的物理执行图。它将可以链接的算子进行合并(Operator Chain),减少序列化开销,并确定资源分配策略。 + +#### 3.3.2 StreamGraph到JobGraph的转换 + +```mermaid +graph TB + subgraph "StreamGraph" + SN1[Source] --> SN2[Map] + SN2 --> SN3[Filter] + SN3 --> SN4[Sink] + end + + subgraph "JobGraph Optimization" + OPT1[Chain Detection] + OPT2[Resource Allocation] + OPT3[Parallelism Config] + end + + subgraph "JobGraph" + JV1[Job Vertex 1
Source→Map→Filter] + JV2[Job Vertex 2
Sink] + JV1 --> JV2 + end + + SN1 --> OPT1 + OPT1 --> OPT2 + OPT2 --> OPT3 + OPT3 --> JV1 +``` + +#### 3.3.3 Operator Chain优化 + +将满足以下条件的算子链接成一个执行单元: +- 上下游算子的并行度相同 +- 下游算子只有一个输入 +- 上游算子只有一个输出 +- 两个算子的数据传输策略为FORWARD + +#### 3.3.4 JobVertex定义 + +```java +public class JobVertex { + private int vertexId; // 顶点ID + private String vertexName; // 顶点名称 + private List chainedNodes; // 链接的节点列表 + private List inputs; // 输入边 + private List outputs; // 输出边 + private int parallelism; // 并行度 + private ResourceProfile resourceProfile; // 资源配置 +} +``` + +### 3.4 Job Scheduler模块 + +#### 3.4.1 设计理念 + +Job Scheduler负责任务的调度策略,支持多种触发方式: +- **立即执行**:任务创建后立即执行 +- **定时执行**:按照Cron表达式定时触发 +- **依赖触发**:上游任务完成后触发 +- **事件触发**:外部事件触发 + +#### 3.4.2 调度策略 + +```mermaid +graph TB + JS[Job Scheduler] + + JS --> IMM[Immediate Scheduler
立即执行] + JS --> CRON[Cron Scheduler
定时调度] + JS --> DEP[Dependency Scheduler
依赖调度] + JS --> EVT[Event Scheduler
事件调度] + + IMM --> JQ[Job Queue] + CRON --> JQ + DEP --> JQ + EVT --> JQ + + JQ --> JE[Job Executor] +``` + +#### 3.4.3 调度器接口 + +```java +public interface JobScheduler { + // 提交任务 + ScheduleResult schedule(Job job, SchedulePolicy policy); + + // 取消调度 + void cancel(String jobId); + + // 暂停调度 + void pause(String jobId); + + // 恢复调度 + void resume(String jobId); + + // 获取调度状态 + ScheduleStatus getStatus(String jobId); +} +``` + +#### 3.4.4 调度策略配置 + +```java +// 立即执行 +SchedulePolicy.immediate() + +// 每小时执行 +SchedulePolicy.cron("0 0 * * * ?") + +// 依赖上游任务 +SchedulePolicy.dependsOn("upstream-job-id") + +// 事件触发 +SchedulePolicy.onEvent("data-arrived") +``` + +### 3.5 Job Executor模块 + +#### 3.5.1 设计理念 + +Job Executor是任务的实际执行引擎,负责将JobGraph转换为可执行的Reactor流,并管理执行过程。 + +#### 3.5.2 执行流程 + +```mermaid +sequenceDiagram + participant Scheduler as Job Scheduler + participant Executor as Job Executor + participant Graph as JobGraph + participant Runtime as Reactor Runtime + participant Operator as Operators + + Scheduler->>Executor: submit(job) + Executor->>Executor: validate(job) + Executor->>Graph: getJobGraph() + Graph-->>Executor: JobGraph + + Executor->>Executor: buildExecutionPlan() + + loop For Each JobVertex + Executor->>Runtime: createFlux(vertex) + Runtime->>Operator: instantiate() + Operator-->>Runtime: Flux + end + + Executor->>Runtime: execute() + + loop Data Processing + Runtime->>Operator: process(data) + Operator-->>Runtime: result + end + + Runtime-->>Executor: completion signal + Executor-->>Scheduler: report(status) +``` + +#### 3.5.3 执行器接口 + +```java +public interface JobExecutor { + // 执行任务 + Mono execute(Job job); + + // 停止任务 + Mono stop(String jobId); + + // 获取执行状态 + ExecutionStatus getStatus(String jobId); + + // 获取执行指标 + ExecutionMetrics getMetrics(String jobId); +} +``` + +#### 3.5.4 执行模式 + +**单机执行模式** +```mermaid +graph LR + JE[Job Executor] --> T1[Task 1] + JE --> T2[Task 2] + JE --> T3[Task 3] + T1 --> TP[Thread Pool] + T2 --> TP + T3 --> TP +``` + +**分布式执行模式(未来扩展)** +```mermaid +graph TB + JM[Job Master] --> W1[Worker 1] + JM --> W2[Worker 2] + JM --> W3[Worker 3] + W1 --> T1[Tasks] + W2 --> T2[Tasks] + W3 --> T3[Tasks] +``` + +### 3.6 Source模块 + +#### 3.6.1 设计理念 + +Source是数据的入口,负责从外部系统读取数据并转换为响应式流。所有Source实现都必须支持背压机制。 + +#### 3.6.2 Source类型 + +```mermaid +graph TB + Source[Source Interface] + + Source --> BS[Bounded Source
有界数据源] + Source --> US[Unbounded Source
无界数据源] + + BS --> FS[File Source] + BS --> JS[JDBC Source] + BS --> AS[API Source] + + US --> KS[Kafka Source] + US --> WS[WebSocket Source] + US --> SS[Stream Source] +``` + +#### 3.6.3 Source接口定义 + +```java +public interface DataSource { + // 获取数据流 + Flux getDataStream(); + + // Source类型(有界/无界) + SourceType getSourceType(); + + // 是否支持并行读取 + boolean isParallel(); + + // 生命周期管理 + void start(); + void stop(); +} +``` + +### 3.7 Operator模块 + +#### 3.7.1 设计理念 + +Operator负责数据转换,分为无状态算子和有状态算子。算子可以链接成算子链,提高执行效率。 + +#### 3.7.2 Operator分类 + +```mermaid +graph TB + OP[Operator] + + OP --> SL[Stateless Operators
无状态算子] + OP --> SF[Stateful Operators
有状态算子] + + SL --> MAP[Map] + SL --> FILTER[Filter] + SL --> FLATMAP[FlatMap] + + SF --> AGG[Aggregate] + SF --> WIN[Window] + SF --> JOIN[Join] + SF --> DEDUP[Deduplicate] +``` + +#### 3.7.3 Operator接口 + +```java +public interface Operator { + // 应用转换 + Flux apply(Flux input); + + // 是否有状态 + boolean isStateful(); + + // 获取算子类型 + OperatorType getType(); +} +``` + +#### 3.7.4 Operator Chain + +```mermaid +graph LR + Input[Input Stream] --> OP1[Map Operator] + OP1 --> OP2[Filter Operator] + OP2 --> OP3[FlatMap Operator] + OP3 --> Output[Output Stream] + + subgraph "Operator Chain" + OP1 + OP2 + OP3 + end +``` + +### 3.8 Sink模块 + +#### 3.8.1 设计理念 + +Sink是数据的出口,负责将处理后的数据写入外部系统。支持批量写入以提高效率。 + +#### 3.8.2 Sink类型 + +```mermaid +graph TB + Sink[Sink Interface] + + Sink --> DB[Database Sink] + Sink --> MQ[Message Queue Sink] + Sink --> FILE[File Sink] + Sink --> API[API Sink] + + DB --> MYSQL[MySQL Sink] + DB --> PG[PostgreSQL Sink] + DB --> REDIS[Redis Sink] + + MQ --> KAFKA[Kafka Sink] + MQ --> RABBIT[RabbitMQ Sink] + + FILE --> LOCAL[Local File Sink] + FILE --> S3[S3 Sink] +``` + +#### 3.8.3 Sink接口 + +```java +public interface DataSink { + // 写入数据 + Mono write(Flux dataStream); + + // 是否支持批量写入 + boolean supportsBatch(); + + // 是否支持事务 + boolean supportsTransaction(); + + // 生命周期管理 + void start(); + void stop(); +} +``` + +### 3.9 Connectors模块 + +#### 3.9.1 设计理念 + +Connectors提供统一的外部系统连接抽象,采用SPI机制实现插件化扩展。每个Connector可以提供Source和Sink实现。 + +#### 3.9.2 Connector架构 + +```mermaid +graph TB + subgraph "Connector Framework" + CM[Connector Manager] + CR[Connector Registry] + CF[Connector Factory] + end + + subgraph "Built-in Connectors" + JDBC[JDBC Connector] + KAFKA[Kafka Connector] + HTTP[HTTP Connector] + FILE[File Connector] + end + + subgraph "Custom Connectors" + C1[Custom Connector 1] + C2[Custom Connector 2] + end + + CM --> CR + CM --> CF + + CR --> JDBC + CR --> KAFKA + CR --> HTTP + CR --> FILE + CR --> C1 + CR --> C2 + + JDBC --> SRC1[Source] + JDBC --> SNK1[Sink] + KAFKA --> SRC2[Source] + KAFKA --> SNK2[Sink] +``` + +#### 3.9.3 Connector接口 + +```java +public interface Connector { + // Connector标识 + String getType(); + + // 创建Source + DataSource createSource(SourceConfig config); + + // 创建Sink + DataSink createSink(SinkConfig config); + + // 验证配置 + void validateConfig(ConnectorConfig config); + + // 获取配置描述 + ConfigDescriptor getConfigDescriptor(); +} +``` + +#### 3.9.4 Connector配置示例 + +```yaml +# JDBC Connector配置 +connectors: + jdbc: + type: jdbc + driver: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3306/db + username: user + password: password + pool: + maxSize: 20 + maxIdleTime: 30m + +# Kafka Connector配置 + kafka: + type: kafka + bootstrapServers: localhost:9092 + consumerGroup: etl-consumer + topics: + - user-events + - order-events + properties: + enable.auto.commit: false + max.poll.records: 500 +``` + +## 4. 模块交互流程 + +### 4.1 任务提交与执行流程 + +```mermaid +sequenceDiagram + participant User + participant API as Stream API + participant SG as StreamGraph + participant JG as JobGraph + participant Scheduler as Job Scheduler + participant Executor as Job Executor + participant Runtime as Reactor Runtime + + User->>API: define job + API->>SG: build StreamGraph + SG->>SG: validate + SG->>JG: optimize & transform + JG->>JG: operator chain + JG->>JG: resource allocation + + User->>Scheduler: submit(job) + Scheduler->>Scheduler: schedule policy + Scheduler->>Executor: dispatch(job) + + Executor->>JG: getJobGraph() + Executor->>Runtime: deploy operators + Runtime->>Runtime: connect operators + Runtime->>Runtime: start execution + + Runtime-->>Executor: progress updates + Executor-->>Scheduler: status updates + Scheduler-->>User: job status +``` + +### 4.2 StreamGraph到JobGraph转换流程 + +```mermaid +flowchart TD + Start[User defines ETL job] --> SG[Build StreamGraph] + SG --> Validate{Validate DAG} + Validate -->|Invalid| Error[Throw Exception] + Validate -->|Valid| Optimize[Optimization Phase] + + Optimize --> Chain[Operator Chain Detection] + Chain --> Parallel[Parallelism Configuration] + Parallel --> Resource[Resource Allocation] + Resource --> JG[Generate JobGraph] + + JG --> Schedule[Submit to Scheduler] +``` + +### 4.3 任务调度流程 + +```mermaid +sequenceDiagram + participant User + participant Scheduler as Job Scheduler + participant Queue as Job Queue + participant Executor as Job Executor + participant Monitor as Job Monitor + + User->>Scheduler: submit(job, policy) + + alt Immediate + Scheduler->>Queue: enqueue(job) + else Cron + Scheduler->>Scheduler: register cron trigger + Note over Scheduler: Wait for trigger time + Scheduler->>Queue: enqueue(job) + else Dependency + Scheduler->>Monitor: watch(upstream job) + Monitor-->>Scheduler: upstream completed + Scheduler->>Queue: enqueue(job) + end + + Queue->>Executor: dispatch(job) + Executor->>Executor: execute + Executor-->>Monitor: report status + Monitor-->>User: notify completion +``` + +### 4.4 数据流执行流程 + +```mermaid +sequenceDiagram + participant Source + participant Op1 as Operator 1 + participant Op2 as Operator 2 + participant Sink + participant State as State Manager + + Source->>Source: read data + Source->>Op1: emit(data) + + Op1->>Op1: transform + alt Stateful + Op1->>State: get state + State-->>Op1: state value + Op1->>State: update state + end + Op1->>Op2: emit(result) + + Op2->>Op2: transform + Op2->>Sink: emit(result) + + Sink->>Sink: buffer + alt Buffer Full + Sink->>Sink: flush batch + end +``` + +### 4.5 检查点协调流程 + +```mermaid +sequenceDiagram + participant Coordinator as Checkpoint Coordinator + participant Source + participant Operator + participant Sink + participant Storage + + Coordinator->>Source: trigger checkpoint(id) + Source->>Source: snapshot state + Source->>Operator: barrier(id) + Source-->>Coordinator: ack(id) + + Operator->>Operator: snapshot state + Operator->>Sink: barrier(id) + Operator-->>Coordinator: ack(id) + + Sink->>Sink: snapshot state + Sink-->>Coordinator: ack(id) + + Coordinator->>Storage: persist checkpoint(id) + Storage-->>Coordinator: success + Coordinator->>Coordinator: checkpoint completed +``` + +## 5. 关键设计决策 + +### 5.1 为什么需要StreamGraph和JobGraph两层抽象? + +**StreamGraph(逻辑图)** +- 直接映射用户API,保持代码的清晰性 +- 方便调试和问题定位 +- 支持多种优化策略 + +**JobGraph(物理图)** +- 优化后的执行计划,提高运行效率 +- 算子链合并,减少序列化开销 +- 资源分配和并行度配置 + +### 5.2 Job Scheduler的设计考虑 + +**多种调度策略支持** +- 满足不同场景需求(实时、定时、依赖) +- 支持复杂的工作流编排 + +**任务优先级** +- 支持任务优先级设置 +- 避免低优先级任务饥饿 + +**资源感知调度** +- 根据资源使用情况调度任务 +- 避免资源竞争 + +### 5.3 响应式设计的优势 + +**背压机制** +- 自动调节数据流速 +- 防止内存溢出 + +**非阻塞IO** +- 高效利用系统资源 +- 支持高并发 + +**组合性** +- 算子可自由组合 +- 代码简洁清晰 + +### 5.4 Connector插件化设计 + +**SPI机制** +- 支持第三方扩展 +- 无需修改核心代码 + +**统一抽象** +- 降低学习成本 +- 代码可复用 + +**配置驱动** +- 无需编译 +- 灵活配置 + +## 6. 配置管理 + +### 6.1 系统配置 + +```yaml +# 系统全局配置 +system: + name: reactive-etl-framework + version: 1.0.0 + + # 执行器配置 + executor: + type: single-node # single-node / distributed + parallelism: 4 # 默认并行度 + threadPool: + coreSize: 10 + maxSize: 50 + queueCapacity: 1000 + + # 调度器配置 + scheduler: + type: quartz + threadPoolSize: 20 + jobQueueSize: 1000 + + # 检查点配置 + checkpoint: + enabled: true + interval: 60s + timeout: 10s + storage: + type: filesystem + path: /data/checkpoints + + # 状态后端配置 + state: + backend: memory # memory / rocksdb + rocksdb: + path: /data/state + blockCacheSize: 256m + + # 监控配置 + metrics: + enabled: true + reporters: + - type: prometheus + port: 9090 + - type: slf4j + interval: 60s +``` + +### 6.2 任务配置 + +```yaml +# ETL任务配置示例 +job: + id: user-etl-job + name: User Data ETL + type: streaming + + # 调度配置 + schedule: + policy: cron + expression: "0 0 * * * ?" + timezone: Asia/Shanghai + + # 资源配置 + resources: + parallelism: 8 + memory: 4g + + # Source配置 + source: + connector: kafka + type: kafka + config: + bootstrapServers: localhost:9092 + topics: [user-events] + groupId: etl-consumer + + # Operator配置 + operators: + - name: parse + type: map + parallelism: 8 + + - name: filter + type: filter + parallelism: 8 + + - name: aggregate + type: window-aggregate + parallelism: 4 + window: + type: tumbling + size: 5m + + # Sink配置 + sink: + connector: jdbc + type: jdbc + config: + url: jdbc:mysql://localhost:3306/warehouse + table: user_stats + batchSize: 100 + flushInterval: 5s +``` + +## 7. 监控与运维 + +### 7.1 监控指标体系 + +```mermaid +graph TB + Metrics[Metrics System] + + Metrics --> Job[Job Metrics] + Metrics --> Operator[Operator Metrics] + Metrics --> Resource[Resource Metrics] + + Job --> JM1[Jobs Running] + Job --> JM2[Jobs Success] + Job --> JM3[Jobs Failed] + Job --> JM4[Job Duration] + + Operator --> OM1[Records In] + Operator --> OM2[Records Out] + Operator --> OM3[Processing Time] + Operator --> OM4[Backpressure] + + Resource --> RM1[CPU Usage] + Resource --> RM2[Memory Usage] + Resource --> RM3[Thread Pool] + Resource --> RM4[Network IO] +``` + +### 7.2 关键监控指标 + +| 指标类别 | 指标名称 | 说明 | +| --- | --- | --- | +| 任务指标 | job.running | 运行中的任务数 | +| 任务指标 | job.completed | 已完成的任务数 | +| 任务指标 | job.failed | 失败的任务数 | +| 任务指标 | job.duration | 任务执行时长 | +| 算子指标 | operator.records.in | 算子输入记录数 | +| 算子指标 | operator.records.out | 算子输出记录数 | +| 算子指标 | operator.processing.time | 处理时间 | +| 算子指标 | operator.backpressure | 背压事件 | +| 资源指标 | system.cpu.usage | CPU使用率 | +| 资源指标 | system.memory.usage | 内存使用率 | +| 资源指标 | threadpool.active | 活跃线程数 | +| 资源指标 | threadpool.queue.size | 队列大小 | + +### 7.3 健康检查机制 + +```mermaid +flowchart TD + HC[Health Check] --> JS[Job Scheduler Status] + HC --> JE[Job Executor Status] + HC --> CN[Connectors Status] + + JS --> JS1{Scheduler Running?} + JS1 -->|Yes| JS2[Check Job Queue] + JS1 -->|No| FAIL1[Health: DOWN] + JS2 --> JS3{Queue Size Normal?} + JS3 -->|Yes| OK1[Health: UP] + JS3 -->|No| WARN1[Health: DEGRADED] + + JE --> JE1{Jobs Running?} + JE1 -->|Yes| JE2[Check Backpressure] + JE1 -->|No| OK2[Health: UP] + JE2 --> JE3{Backpressure High?} + JE3 -->|No| OK3[Health: UP] + JE3 -->|Yes| WARN2[Health: DEGRADED] + + CN --> CN1{All Connectors Connected?} + CN1 -->|Yes| OK4[Health: UP] + CN1 -->|No| FAIL2[Health: DOWN] +``` + +### 7.4 日志规范 + +**日志级别使用规范** +- **TRACE**: 详细的执行追踪信息(生产环境关闭) +- **DEBUG**: 调试信息,帮助定位问题 +- **INFO**: 关键业务事件(任务启动、完成、检查点等) +- **WARN**: 警告信息(重试、降级等) +- **ERROR**: 错误信息(任务失败、异常等) + +**结构化日志示例** +```json +{ + "timestamp": "2025-11-09T10:30:00.000Z", + "level": "INFO", + "logger": "JobExecutor", + "jobId": "job-123", + "jobName": "user-etl", + "event": "JOB_STARTED", + "message": "Job started successfully", + "metadata": { + "parallelism": 8, + "operators": 5 + } +} +``` + +## 8. 扩展性设计 + +### 8.1 自定义Connector开发 + +**步骤1:实现Connector接口** +```java +public class CustomConnector implements Connector { + @Override + public String getType() { + return "custom"; + } + + @Override + public DataSource createSource(SourceConfig config) { + return new CustomSource<>(config); + } + + @Override + public DataSink createSink(SinkConfig config) { + return new CustomSink<>(config); + } +} +``` + +**步骤2:实现Source和Sink** +```java +public class CustomSource implements DataSource { + @Override + public Flux getDataStream() { + // 实现数据读取逻辑 + } +} + +public class CustomSink implements DataSink { + @Override + public Mono write(Flux dataStream) { + // 实现数据写入逻辑 + } +} +``` + +**步骤3:注册Connector** +在`META-INF/services/com.framework.etl.Connector`文件中添加: +``` +com.example.CustomConnector +``` + +### 8.2 自定义Operator开发 + +```java +public class CustomOperator implements Operator { + + @Override + public Flux apply(Flux input) { + return input + .map(this::transform) + .filter(this::shouldKeep); + } + + @Override + public boolean isStateful() { + return false; + } + + private OUT transform(IN input) { + // 转换逻辑 + } + + private boolean shouldKeep(OUT output) { + // 过滤逻辑 + } +} +``` + +### 8.3 自定义调度策略 + +```java +public class CustomSchedulePolicy implements SchedulePolicy { + + @Override + public Flux getTriggers() { + // 返回触发信号流 + return Flux.interval(Duration.ofMinutes(30)) + .map(tick -> new Trigger(triggerTime)); + } + + @Override + public boolean shouldExecute(Job job) { + // 判断是否应该执行 + return checkConditions(job); + } +} +``` + +## 9. 使用示例 + +### 9.1 快速开始:简单ETL任务 + +```java +// 创建Job +Job job = Job.builder() + .name("simple-etl") + .source(Connectors.kafka() + .topic("user-events") + .groupId("etl-consumer") + .build()) + .transform(Operators.map(event -> parseUser(event))) + .transform(Operators.filter(user -> user.isActive())) + .sink(Connectors.jdbc() + .table("users") + .batchSize(100) + .build()) + .build(); + +// 提交任务 +jobScheduler.schedule(job, SchedulePolicy.immediate()); +``` + +### 9.2 定时调度任务 + +```java +Job job = Job.builder() + .name("daily-report") + .source(Connectors.jdbc() + .query("SELECT * FROM orders WHERE date = ?") + .build()) + .transform(Operators.aggregate( + Orders::getRegion, + Orders::getAmount, + Double::sum + )) + .sink(Connectors.file() + .path("/reports/daily-{date}.csv") + .build()) + .build(); + +// 每天凌晨1点执行 +jobScheduler.schedule(job, SchedulePolicy.cron("0 0 1 * * ?")); +``` + +### 9.3 复杂的流处理任务 + +```java +StreamGraph graph = StreamGraph.builder() + // Source + .addSource("kafka-source", Connectors.kafka() + .topics("events") + .build()) + + // Parse + .addOperator("parse", Operators.map(msg -> parseEvent(msg))) + + // Branch 1: User events + .addOperator("filter-user", Operators.filter(e -> e.isUserEvent())) + .addOperator("user-aggregate", Operators.windowAggregate( + Duration.ofMinutes(5), + Events::getUserId, + Collectors.counting() + )) + .addSink("user-sink", Connectors.jdbc().table("user_stats").build()) + + // Branch 2: Order events + .addOperator("filter-order", Operators.filter(e -> e.isOrderEvent())) + .addOperator("order-aggregate", Operators.windowAggregate( + Duration.ofMinutes(5), + Events::getOrderId, + Collectors.summingDouble(Events::getAmount) + )) + .addSink("order-sink", Connectors.jdbc().table("order_stats").build()) + + // Connect edges + .connect("kafka-source", "parse") + .connect("parse", "filter-user") + .connect("parse", "filter-order") + .connect("filter-user", "user-aggregate") + .connect("user-aggregate", "user-sink") + .connect("filter-order", "order-aggregate") + .connect("order-aggregate", "order-sink") + + .build(); + +// 转换为JobGraph并提交 +JobGraph jobGraph = graph.toJobGraph(); +Job job = new Job(jobGraph); +jobScheduler.schedule(job, SchedulePolicy.immediate()); +``` + +## 10. 性能优化指南 + +### 10.1 并行度配置 + +```mermaid +graph LR + subgraph "Low Parallelism" + T1[Task 1] --> R1[Result] + end + + subgraph "High Parallelism" + T2[Task 1] --> R2[Result] + T3[Task 2] --> R2 + T4[Task 3] --> R2 + T5[Task 4] --> R2 + end +``` + +**配置建议** +- CPU密集型:并行度 = CPU核心数 +- IO密集型:并行度 = 2 * CPU核心数 +- 根据数据量动态调整 + +### 10.2 批处理优化 + +```yaml +sink: + batchSize: 100 # 批次大小 + flushInterval: 5s # 刷新间隔 +``` + +**权衡考虑** +- 批次越大,吞吐量越高,但延迟增加 +- 批次越小,延迟越低,但吞吐量降低 + +### 10.3 背压控制策略 + +| 策略 | 说明 | 适用场景 | +| --- | --- | --- | +| BUFFER | 缓冲数据 | 临时性的速度不匹配 | +| DROP | 丢弃新数据 | 允许丢失部分数据 | +| LATEST | 保留最新数据 | 只关心最新状态 | +| ERROR | 抛出异常 | 不允许数据丢失 | + +### 10.4 资源配置建议 + +```yaml +resources: + # JVM配置 + jvm: + heap: 4g + metaspace: 512m + gc: G1GC + + # 线程池配置 + threadPool: + io: + coreSize: 20 + maxSize: 100 + compute: + coreSize: 8 + maxSize: 16 + + # 缓冲区配置 + buffer: + sourceBuffer: 1000 + sinkBuffer: 500 +``` + +## 11. 容错与恢复 + +### 11.1 故障类型 + +```mermaid +graph TB + Failures[Failure Types] + + Failures --> TF[Task Failures
任务失败] + Failures --> NF[Node Failures
节点故障] + Failures --> EF[External Failures
外部系统故障] + + TF --> TF1[Data Error
数据错误] + TF --> TF2[Logic Error
逻辑错误] + + NF --> NF1[Process Crash
进程崩溃] + NF --> NF2[Network Partition
网络分区] + + EF --> EF1[Source Unavailable
数据源不可用] + EF --> EF2[Sink Unavailable
目标系统不可用] +``` + +### 11.2 重启策略 + +```yaml +restart: + # 固定延迟重启 + strategy: fixed-delay + attempts: 3 + delay: 10s + + # 指数退避重启 + # strategy: exponential-backoff + # initialDelay: 1s + # maxDelay: 5m + # multiplier: 2 + + # 失败率重启 + # strategy: failure-rate + # maxFailuresPerInterval: 3 + # failureRateInterval: 5m + # delay: 10s +``` + +### 11.3 检查点恢复流程 + +```mermaid +sequenceDiagram + participant Job + participant Scheduler + participant Executor + participant Checkpoint + participant State + + Note over Job: Job Failed + + Job->>Scheduler: report failure + Scheduler->>Scheduler: apply restart strategy + + alt Should Restart + Scheduler->>Checkpoint: get latest checkpoint + Checkpoint-->>Scheduler: checkpoint-id + + Scheduler->>Executor: restart(job, checkpoint-id) + Executor->>Checkpoint: load(checkpoint-id) + Checkpoint->>State: restore state + State-->>Executor: state restored + + Executor->>Executor: resume from checkpoint + Executor-->>Scheduler: job restarted + else Max Retries Exceeded + Scheduler->>Scheduler: mark job as failed + Scheduler-->>Job: job terminated + end +``` + +## 12. 最佳实践 + +### 12.1 任务设计原则 + +1. **单一职责**:每个Job只负责一个业务逻辑 +2. **幂等性**:确保任务可以安全重试 +3. **可观测性**:添加足够的监控指标和日志 +4. **容错性**:合理配置重试和检查点策略 + +### 12.2 性能优化建议 + +1. **合理设置并行度**:根据资源和数据量调整 +2. **启用算子链**:减少序列化开销 +3. **批量处理**:使用批量写入提高吞吐量 +4. **状态管理**:大状态使用RocksDB后端 + +### 12.3 运维建议 + +1. **监控告警**:设置关键指标告警阈值 +2. **定期备份**:定期备份检查点数据 +3. **资源隔离**:不同优先级任务使用不同资源池 +4. **灰度发布**:新版本先小流量验证 + +## 13. 未来规划 + +### 13.1 短期规划(3-6个月) + +- 完善Connector生态(MongoDB、ClickHouse、HBase) +- 实现分布式执行模式 +- 提供Web管理界面 +- 支持SQL API + +### 13.2 中期规划(6-12个月) + +- 实现Exactly-Once语义 +- 支持动态扩缩容 +- 机器学习特征工程集成 +- 流批一体架构 + +### 13.3 长期规划(1-2年) + +- 云原生支持(Kubernetes Operator) +- 多租户隔离 +- 实时数据质量监控 +- 智能资源调度 + +## 14. 参考资料 + +### 14.1 技术栈 + +- **响应式编程**: Project Reactor 3.5+ +- **任务调度**: Quartz Scheduler +- **状态存储**: RocksDB +- **监控**: Micrometer + Prometheus +- **序列化**: Protobuf / Avro + +### 14.2 设计参考 + +- Apache Flink架构设计 +- Apache Kafka Streams +- Spring Cloud Data Flow +- Reactive Streams规范 + +### 14.3 相关文档 + +- [Project Reactor官方文档](https://projectreactor.io/docs) +- [Reactive Streams规范](https://www.reactive-streams.org/) +- [Apache Flink文档](https://flink.apache.org/) + +## 15. 术语表 + +| 术语 | 英文 | 说明 | +| --- | --- | --- | +| 任务 | Job | 完整的ETL处理流程 | +| 流图 | StreamGraph | 用户定义的逻辑执行图 | +| 任务图 | JobGraph | 优化后的物理执行图 | +| 调度器 | Scheduler | 任务调度组件 | +| 执行器 | Executor | 任务执行引擎 | +| 数据源 | Source | 数据输入 | +| 算子 | Operator | 数据转换 | +| 输出 | Sink | 数据输出 | +| 连接器 | Connector | 外部系统连接 | +| 背压 | Backpressure | 流量控制机制 | +| 检查点 | Checkpoint | 状态快照 | +| 算子链 | Operator Chain | 算子优化合并 | + +--- + +**文档版本**: v2.0 +**最后更新**: 2025-11-09 +**维护者**: ETL Framework Team diff --git a/images/account.png b/images/account.png new file mode 100644 index 000000000..be0e67a05 Binary files /dev/null and b/images/account.png differ