一体化数据归集接口详细设计说明

一体化数据归集接口详细设计说明

核心背景是一体化数据归集到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 的数据干净、规范、可信。

核心设计亮点

  1. 异步解耦:上传与处理通过 Kafka 分离,响应快、削峰填谷、故障隔离
  2. Append-Only:结果表只 INSERT 不 UPDATE,充分利用顺序写入优势
  3. 全链路留痕:从校验失败到处理成功/失败,每个环节都有记录可查
  4. 重发不污染:派生新记录而非回写原记录,历史结果互不干扰
  5. 大消息切片:按业务行拆分保持子树完整性,消费端零改动
  6. DDL 缓存:正负缓存 + 随机 TTL,不静默降级
  7. 校验失败留痕:最常见的问题恰恰最需要记录
  8. 游标分批清理:主键游标定位避免大表 O(n²) 扫描
  9. 动态批次大小:根据实际数据大小采样估算而非固定值
  10. 树形删除换算链:报文主键 ∪ 库中现存主键,按深度降序执行

在实际落地中,需要根据业务规模、数据量、峰值流量进行压测调优,持续监控各项指标并动态调整参数。


实践整理,代码作者 Sisyphus,整理日期 2026-09-02。*

相关推荐
明月_清风28 分钟前
十大经典排序算法 Go 实现全解:从入门到面试通关
后端·算法·排序算法
美股研究社34 分钟前
出海合规战,TEMU先胜“一城”
大数据·人工智能·物联网
LlmCraft|大模型工程实践37 分钟前
08. Docker Compose 一键编排:多容器应用的指挥家
java·spring cloud·eureka
qq4078556043 分钟前
2026 最新鼎捷 vs 轻流:生产管理系统功能对比与选型指南
大数据·人工智能·低代码·制造
珍珠先生44 分钟前
第14章 设计模式入门:单例、工厂、建造者与代理
java
leeyi1 小时前
把规则搬回家:三个 BC 的贫血→充血重构实录(第103篇)
后端
坐吃山猪1 小时前
JDK 8 到 JDK 21 新特性知识要点
java·开发语言·windows
坐吃山猪1 小时前
【多线程】FutureTask多线程底层实现
java·开发语言·数据库
weixin199701080161 小时前
[特殊字符]《从0到1:闲鱼开放平台授权登录 + AccessToken 刷新 + 聚石塔部署完整链路》(附Python源码)
java·数据库·python