一体化数据归集接口详细设计说明
核心背景是一体化数据归集到Doris数据库,各个业务系统都需要调用该接口完成数据质量控制,数据录入Doris数据库里面。该接口设计就天然需要支持高并发,高可用,高性能,系统阐述数据归集接口的架构设计、高并发高可用高性能方案、数据质量控制流程及工程落地细节。
该接口通过分层架构、异步化、批量写入、无状态扩展、多副本部署等手段,系统性满足高并发、高可用、高性能要求。同时将数据质量控制前置到入口,确保进入 Doris 的数据干净、规范、可信。在实际落地中,需要根据业务规模、数据量、峰值流量进行压测调优,持续监控各项指标并动态调整参数。
1. 背景与目标
各业务系统需要将数据统一归集至 Doris 数据仓库,并在入库前完成数据质量控制。该接口作为统一数据入口,承担数据校验、清洗、转换、入库等职责。
核心要求:高并发(峰值 TPS 万级)、高可用(≥99.99%)、高性能(低响应延迟、高入库吞吐)、全链路可追溯。
核心挑战:
| 挑战 | 说明 |
|---|---|
| 报文体积不可控 | 主子表嵌套树形数据,序列化后可能超过消息队列单条消息上限 |
| 表结构动态变化 | Doris 表结构可能随时变更,校验规则需动态适配 |
| 主子表级联语义 | 新增/覆盖需"先删后插、全量替换",删除需按外键逐层换算 |
| 失败数据可恢复 | 校验失败、写入失败的数据不能丢失,需支持精准重发 |
| 大批量日志治理 | 高吞吐下日志表快速膨胀,需高效清理且不影响在线业务 |
2. 总体架构
业务系统 → 统一接入层(负载均衡/网关/限流鉴权)
→ 数据接入服务层(无状态,可水平扩展)
├── 请求头/报文基础校验(同步)
├── 全量必填字段前置校验(同步,基于 DDL 缓存)
├── 上传日志落库(完整报文持久化)
└── Kafka 消息发送(按大小切片)
→ 消息队列 Kafka(削峰填谷、异步解耦)
→ 数据写入服务 / Kafka 消费者
├── 消息解析与二次校验
├── 删除计划构建(树形递归、换算链模型)
├── 先删后插(子表按 fk 全量替换)
└── Doris Stream Load 批量写入
→ Doris 集群(FE 多副本 / BE 多副本)
核心设计原则:接入层与业务层分离、服务层无状态化、异步化与削峰、批量写入优化、质量校验前置与后置结合、Append-Only 结果表、全链路留痕。
3. 接口定义设计
3.1 接口形式
提供 RESTful API 和 gRPC 双协议支持。RESTful 便于快速接入(JSON 格式,支持主子表嵌套树形报文);gRPC 适合高性能低延迟场景(Protobuf 序列化,性能比 JSON 高 3~10 倍)。
3.2 请求报文结构
采用 Head + Body 分离设计:
json
{
"head": {
"id": "请求流水号(全局唯一)",
"app_code": "调用系统标识",
"target_app_code": "目标系统标识",
"service_code": "服务代码",
"request_type": "C-新增 / U-覆盖 / D-删除",
"request_timestamp": "2026-09-02 10:00:00"
},
"body": [
{
"data_code": "目标表名",
"id": "主键字段名(如 id/role_id/user_id)",
"fk_id": "外键字段名(子表关联主表,仅子节点使用)",
"operation_type": "0-默认 / 1-级联删除 / 2-删除所有",
"data": [
{
"id": "业务主键值",
"field1": "值1",
"data_subs": [
{
"data_code": "子表名",
"id": "子表主键字段名",
"fk_id": "子表外键字段名",
"operation_type": "0",
"data": [{ "id": "子表记录主键", "field1": "子表值" }]
}
]
}
]
}
]
}
关键字段 :head.id(链路追踪)、request_type(C/U/D 枚举)、data_code(目标表名)、id(主键字段名,支持自定义)、fk_id(外键字段名)、operation_type(0/1/2 操作语义)、data_subs(任意深度嵌套子表)。
3.3 响应报文结构
json
{ "code": 200, "message": "受理成功:100组数据", "data": true }
同步返回受理结果(非最终入库结果),最终处理结果通过结果查询接口异步获取。
3.4 幂等设计
head.id实现链路追踪,同一流水号下多次请求各自独立记录- Doris
UNIQUE KEY模型保证同主键后写入覆盖先写入 - Stream Load label 机制实现 Exactly-Once
- 消费端手动 ack,确保处理完成后才提交位移
4. 高并发设计
4.1 无状态水平扩展
服务部署在容器化平台,通过 HPA 根据 CPU/内存/QPS 自动扩缩容。单实例支撑数千 TPS,集群吞吐随实例数线性扩展。@DS 注解动态切换数据源,不绑定本地会话。
4.2 异步非阻塞 I/O
Netty(gRPC)或 Reactor 模型实现非阻塞网络通信。线程池隔离:网络线程、业务线程、Doris 写入线程分离。Doris Stream Load 使用 HttpClient 连接池复用连接。
4.3 消息队列削峰与大消息切片
接入层完成基础校验后数据写入 Kafka,队列作为蓄水池吸收高峰流量。当单条消息超过 Kafka max.message.bytes(默认 1MB)时,通过 KafkaMessageChunker 自动切片:
| 拆分原则 | 说明 |
|---|---|
| 结构不变 | 每片仍是完整消息结构(head+bodyItems+recordIds),消费端零改动 |
| 行为最小单位 | 以 data 顶层行为最小单位,行内 data_subs 子树整体不可拆散 |
| 主键范围不相交 | 各片主键/fk 范围天然不相交,片间互不误删 |
| recordId 复用 | 同一 bodyItem 拆出的多片复用同一 recordId,结果表 append-only 天然兼容 |
| 行数守恒校验 | 切片后校验主表行总数,防止丢失或重复 |
为什么子树不能拆散? 消费端执行"先删后插"时,预删除计划按本片上级主键/fk 定位。子树拆散后删除范围会错乱,导致语义错误。
4.4 限流与熔断
接入层按业务系统分配独立令牌桶。Doris 压力过大时触发熔断,快速失败或降级。提供动态调整限流阈值能力。
4.5 连接池与动态批次合并
Doris HttpClient 连接池复用连接。消费端微批合并后通过 Stream Load 写入。动态批次大小计算:采样前 5 条记录序列化估算平均行大小,按 10MB 安全上限倒推 batch size,约束在 50, 500 范围内。
4.6 缓存设计
DDL 元数据缓存 (Redis):正常缓存 7 天 + 随机偏移防雪崩;表不存在的负缓存 5 分钟;DESCRIBE 失败抛异常不静默降级;hasColumn 复用缓存不新增查询;excludeFields 仅当表真实存在该列时才跳过必填校验。
数据源配置缓存(Guava Cache):最大 30 条,60 分钟过期,双重检查锁防击穿,密码 DES 加解密。
5. 高可用设计
5.1 多可用区部署与故障转移
服务集群跨机房部署,负载均衡器健康检查自动摘除故障实例。客户端 SDK 内置重试。Kafka 消费者配置自定义错误处理器(ConsumerAwareListenerErrorHandler),异常消息不阻塞后续消费。
5.2 幂等与 Exactly-Once
消息队列配合幂等控制保证不重复不丢失。Doris Stream Load label 机制保证同一导入任务只成功执行一次。Unique 模型按导入顺序生效,同主键后写入覆盖先写入。
5.3 Doris 高可用
FE 3 节点以上部署通过 BDBJE 保证元数据高可用。BE 多副本存储,单节点故障不影响数据完整性。@PreDestroy 确保 HttpClient 连接池正确释放。
5.4 校验失败留痕机制
校验失败时整批数据不进入 Kafka,若不留痕调用方将完全无法自查。具体做法:head 为 null 时用空对象兜底;bodyItems 为空时退化为占位记录;使用 @DSTransactional(propagation = REQUIRES_NEW) 挂起外层多数据源事务独立提交;同时补记失败处理结果。
为什么用 REQUIRES_NEW? 外层主流程使用多数据源事务(ThreadLocal 事务上下文),Spring 原生
@Transactional无法感知并挂起它,补记内容会随主流程回滚一起丢失。
6. 高性能设计
6.1 批量写入与 Append-Only
使用 Stream Load 将多行数据以 JSON 格式一次性推送到 Doris BE,吞吐量比单条 INSERT 提升数十倍。消费端聚合策略:按数据量(每 100 条)、按数据大小(动态计算)、按时间(最长等待)。
Append-Only 结果表 :原日志表和结果表都只做 INSERT 不做 UPDATE。大批量场景下 UPDATE 产生大量随机 I/O,INSERT-only 充分利用顺序写入优势。查询时通过 recordId 聚合多条结果(Collectors.reducing O(1) 空间聚合):全部成功才算成功,含任意失败即失败。
6.2 Doris 表设计优化
分桶(按业务主键均匀分布)、分区(按时间便于管理裁剪)、数据模型(明细用 DUPLICATE KEY、去重用 UNIQUE KEY、聚合用 AGGREGATE KEY)、压缩(LZ4/ZSTD)、索引(Bloom Filter/Bitmap)。
6.3 序列化与连接优化
gRPC Protobuf 比 JSON 性能高 3~10 倍。序列化时不忽略 null 字段(WriteMapNullValue)保证消费端字段完整性。HttpClient 连接池核心连接常驻,空闲连接复用前通过 validateAfterInactivity 校验存活性。
6.4 合并删除与插入写入
提供「逻辑删除行 + 数据插入行」合并写入开关:开启时每表 Stream Load 调用从 2 次降为 1 次(删除行置于插入行之前);关闭时保持先删后插原逻辑;安全网确保未被消费的删除行逐表单独写入不遗漏。
7. 数据质量控制流程
7.1 校验规则分类
| 规则类型 | 示例 | 执行阶段 | 实现方式 |
|---|---|---|---|
| 完整性 | 非空校验 | 同步 | DDL 缓存 isRequired() |
| 表存在性 | 目标表是否存在 | 同步 | DDL 缓存 tableExists() |
| 列存在性 | 字段是否在表中 | 同步 | DDL 缓存 hasColumn() |
| 唯一性 | 主键唯一 | 同步/异步 | Doris Unique 模型 |
| 一致性 | 外键关联存在 | 异步 | 换算链模型 |
7.2 双重校验机制
生产端前置校验:请求头/报文基础校验 → 请求类型合法性 → 全量必填字段递归校验(主表+所有嵌套子表)→ 删除场景主键校验 → Fail-Fast 返回。
消费端二次校验:消息解析校验 → 必填字段二次校验 → 表存在性校验。两者规则必须对称,防止"生产端通过但消费端拒绝"的矛盾。
7.3 错误数据处理
同步错误立即返回并补记日志与失败结果;异步错误写入结果表提供查询接口;错误信息截断至 2000 字符防止字段溢出;校验失败的行被跳过不影响其他有效行写入。
8. 树形删除计划与换算链模型
8.1 问题背景
主子表嵌套数据的"先删后插、全量替换"是最复杂场景:主表原有 5 条子表关联记录、新报文 3 条 → 先删 5 再插 3;子表已删拿不到主键 → 依赖 fk 关联上级删除;多层级嵌套 → 逐层换算。
8.2 换算链模型
将报文节点树递归解析为 NodeSpec,根据 operationType 生成 DataDeleteStatement 列表:
| operationType | 语义 | 行为 |
|---|---|---|
| 2 | 删除所有 | 按主键删除本级,级联强制删除全部子孙 |
| 1 | 级联删除 | 本级不删(deleteSelf=false),产出库中现存主键供子孙引用 |
| 0 | 默认 | 跳过本级,继续扫描子孙 |
换算链 :子孙 fk 语句的删除条件 = 上级报文主键(构建时已知)∪ 上级库中现存主键(执行时由上级语句换算产生,经 parentTableKey 引用获取)。按 depth 降序执行(先孙后子再父),保证换算查询在上级行被软删前完成。
8.3 D 场景整树删除
request_type=D 时:data 携带主键值 → 按主键删除(优先于 fk);data 为空且声明 fk_id → 按 fk IN (上级主键集合) 删除全部关联行;data 为空且无 fk_id → 条件链断点跳过并告警。
9. 失败重发机制
重发不是简单重发原消息,而是派生全新日志记录:分配新雪花 ID → 替换报文中 recordIds 为新 ID → 原样继承请求上下文 → 批量落库新日志 → 重新投递 Kafka(按大小切片)。单次重发上限 1000 条防止撑大事务。
为什么不直接重发原消息? 消费端结果挂在 recordId 下,重发仍用原 ID 会导致新结果与原失败结果混淆。派生新记录后原日志历史结果不受影响,新日志有独立结果归属。
10. 日志治理与批量清理
10.1 保留策略
| 保留类型 | 天数 | 说明 |
|---|---|---|
| WEEK | 7 | 仅保留最近一周 |
| MONTH | 30 | 仅保留最近一个月 |
| QUARTER | 90 | 仅保留最近三个月 |
| YEAR | 365 | 保留最近一年 |
10.2 游标分批清理
采用游标分批删除:WHERE 清理条件 AND id > lastId ORDER BY id LIMIT 1000,游标经主键索引直接定位避免大表 O(n²) 扫描。从表处理结果用 EXISTS 关联子查询一次性删除避免大 IN 列表。清理条件为空时拒绝执行避免误删全表。单批 1000 条控制事务规模。
11. 监控与告警
关键指标:请求 QPS/TPS、响应时间 P99、错误率、队列积压量、Doris 写入延迟/失败率、数据质量通过率、消息切片数、DDL 缓存命中率。
告警策略:QPS 突降或错误率突增、队列积压超阈值、Doris 资源超 80%、切片行数不守恒(紧急)。
链路追踪 :请求流水号 head.id 贯穿日志表、Kafka 消息、结果表,全链路可追溯。
12. 多数据源与事务管理
使用 dynamic-datasource 组件,@DS 注解动态切换数据源。数据接入服务使用 DIS 数据源。@DSTransactional 管理多数据源事务,REQUIRES_NEW 传播行为用于校验失败留痕的独立事务提交。Doris 写入通过 Stream Load HTTP 接口不走 JDBC。
13. 总结
该接口通过分层架构、异步化、批量写入、无状态扩展、多副本部署等手段,系统性满足高并发、高可用、高性能要求。同时将数据质量控制前置到入口,确保进入 Doris 的数据干净、规范、可信。
核心设计亮点:
- 异步解耦:上传与处理通过 Kafka 分离,响应快、削峰填谷、故障隔离
- Append-Only:结果表只 INSERT 不 UPDATE,充分利用顺序写入优势
- 全链路留痕:从校验失败到处理成功/失败,每个环节都有记录可查
- 重发不污染:派生新记录而非回写原记录,历史结果互不干扰
- 大消息切片:按业务行拆分保持子树完整性,消费端零改动
- DDL 缓存:正负缓存 + 随机 TTL,不静默降级
- 校验失败留痕:最常见的问题恰恰最需要记录
- 游标分批清理:主键游标定位避免大表 O(n²) 扫描
- 动态批次大小:根据实际数据大小采样估算而非固定值
- 树形删除换算链:报文主键 ∪ 库中现存主键,按深度降序执行
在实际落地中,需要根据业务规模、数据量、峰值流量进行压测调优,持续监控各项指标并动态调整参数。
实践整理,代码作者 Sisyphus,整理日期 2026-09-02。*