从原始消息到业务数据:三层数据架构的设计与实践

1. 整体描述

一条上游消息进入系统后,往往不能直接成为页面查询或报表统计的数据。

消息可能包含嵌套数组,同一业务对象可能多次更新,不同来源也可能使用不同的字段和时间格式。如果把报文解析、版本判断和业务查询全部放在一个环节中,后续定位问题、调整规则和修复历史数据都会变得困难。

本文结合一套事件驱动的数据处理链路,介绍 Original、Raw、Processed 三层结构的职责与协作方式。客户名称、系统名称、表名、字段名和业务标识均已泛化;报文、时间、编号和数量示例为虚构内容,不对应真实业务记录。

这三层分别保留不同阶段的数据形态:

text 复制代码
上游 Kafka 消息
       ↓
Original:保存来源报文及来源位置
       ↓ 解析、展平
Raw:保存结构化的来源明细
       ↓ 业务键识别、版本比较、结果加工
Processed:形成业务可消费的数据
       ↓
查询服务、业务应用、聚合视图和报表

层间使用 Kafka 通知触发后续处理,部分下游数据通过定时任务或同步链路生成。

分层的主要价值是让三个问题可以分别回答:

  • 收到了什么:通过 Original 查看来源报文。
  • 解析出了什么:通过 Raw 查看结构化明细。
  • 业务当前使用什么:通过 Processed 查看按规则加工后的结果。

本文中的"三层"指数据处理的逻辑分层,不代表三个数据库、三个服务或三台服务器。它也不直接等同于 ODS、DWD、DWS:Processed 可以包含业务明细和最新状态,查询聚合与历史报表还可能继续建立在其后。


2. 具体描述

2.1 先按职责分层,再讨论表名

判断一张表属于哪一层,应先看数据来源、粒度和处理规则。

层次 保存的内容 主要用途 不能仅凭这一层确认的事情
Original 接入时保存的报文及来源元数据 追溯、重新解析、故障排查 明细已经完整写入
Raw 从报文展开的结构化字段和明细 SQL 加工、来源核对、重新计算 数据已经符合最终业务口径
Processed 按业务键和版本等规则加工的结果 业务查询、下游共享、进一步聚合 任意历史时点的状态都可恢复

三层保留的是不同处理阶段的信息。它们不是把同一张业务表简单复制三份。

例如,一条消息包含多个商品和多个接收地点,Original 可以保存一条报文,Raw 会展开为多条明细,Processed 则根据有效版本保留业务需要的结果。三层行数并不要求一致。

2.2 Original:保留可以重新解释的数据

Original 的核心作用,是保留进入接入程序时的消息内容,让后续处理可以回到一个明确的来源。

以下为字段示意:

字段 含义
source_id 本地原始记录标识
payload 保存的消息内容
source_topic 来源主题,可由记录或接入配置关联确定
source_partition 来源分区
source_offset 分区内消息位置
received_at 本地接收或落库时间

需要区分"保存原始报文"和"保存原始字节"。如果接入程序已经将 Avro 等消息对象转换为 JSON,再存入数据库,保留的是转换后的内容,不能直接宣称它与上游传输字节完全一致。

Kafka 消息位置也需要主题、分区和 offset 一起解释。单独一个 offset 数字不能标识整个系统中的唯一消息,更不能作为跨分区的全局时间顺序。

Original 为重放提供了数据依据,但能否重放仍取决于报文是否完整、转换规则是否可用,以及关联配置是否保留。仅仅存在一列 JSON,并不意味着系统已经具备完整的重放能力。

2.3 Raw:将嵌套消息展开为可加工的明细

Raw 将消息结构转换为关系型数据,减少后续每次处理时重复解析报文的成本。

假设接收到以下虚构消息:

json 复制代码
{
  "documentNo": "DOC-DEMO",
  "revision": 4,
  "items": [
    {
      "lineNo": "L1",
      "productCode": "PRODUCT-A",
      "destinations": [
        {"locationCode": "SITE-X", "quantity": 12},
        {"locationCode": "SITE-Y", "quantity": 7}
      ]
    }
  ]
}

按"单据 + 行 + 地点"展开后,得到两条明细:

source_id document_no line_no location_code quantity revision
SRC-DEMO DOC-DEMO L1 SITE-X 12 4
SRC-DEMO DOC-DEMO L1 SITE-Y 7 4

这一步最需要明确的是明细粒度。只按单据号识别明细,会把不同商品行和地点混在一起;把地点数组错误地交叉展开,也可能制造额外记录。

Raw 通常仍然接近来源字段语义。日期可能暂时保留来源字符串,字段名也可能反映嵌套结构。业务展示格式和汇总计算不必在这一层全部完成。

同时,展开后的字段容量必须能承接来源内容。数组序列化后的文本写入过短字符串列,是一种实际可能出现的失败原因。Original 写入成功,不代表 Raw 一定能够完整落库。

2.4 Processed:将来源事件转换为业务结果

Raw 侧重表达来源数据,Processed 则开始回答业务如何使用这些数据。

本文参考的加工链路包含以下步骤:

  1. 从 Kafka 通知中取得关联标识。
  2. 查询对应 Raw 数据,记录本批次涉及的明细和业务键。
  3. 读取这些业务键已有的 Processed 数据。
  4. 合并新数据与已有结果,按约定规则选择有效版本。
  5. 将候选结果写入临时表。
  6. 替换目标范围内的 Processed 数据。

这是一种基于受影响业务键的增量加工方式,不必每收到一批消息就重新计算全部历史数据。

Processed 也不意味着所有业务规则都已统一。不同数据域可以有不同的粒度、版本含义和删除语义;实际接口还可能关联主数据、业务配置或汇总表。

2.5 将原始记录、明细和业务对象分别标识

三层链路中,至少存在三类标识:

标识 识别的对象 典型关系
原始记录 ID 一次保存的来源消息 一条消息可生成多条明细
明细 ID 一条 Raw 记录 多条明细可属于同一业务对象
业务键 跨消息持续更新的业务对象 一个对象可对应多个事件版本

三者不能互相替代。原始 ID 不同,可能只是同一业务对象发生了更新;业务键相同,也不代表两条来源消息可以直接当作重复消息删除。

在当前参考链路中,通知携带的关联值可以先定位原始记录,再找到对应 Raw 明细。不能仅凭程序中的变量名,就认定消息里一定传递 Raw 主键。

对外应将通知内容定义清楚:携带的是原始 ID、明细 ID,还是业务键,以及通知意味着"有数据待加工"还是"某阶段已经完成"。

2.6 版本选择不能简单理解为最后到达者覆盖

网络延迟、重试和历史补跑都可能让旧事件晚于新事件到达。

假设同一业务对象有两个版本:

事件 来源业务时间 本地到达顺序
较新事件 09:30 先到达
较旧事件 09:10 后到达

如果只按本地插入时间覆盖,后到的旧事件可能把业务状态回退。

参考实现会将新 Raw 数据与已有 Processed 结果合并,再按来源发布时间等字段排序;本地时间和原始记录标识也参与排序。这种处理意图是让新批次与现有结果共同参与版本选择。

但排序字段是否正确,需要结合来源契约判断:来源时间是否可靠,是否有正式版本号,相同时间如何处理,删除事件是否具有明确含义。数据库自增 ID 只能说明本地记录顺序,不能自动证明业务版本更高。

还要区分两种模型:

  • 完整快照:新版本代表整个业务对象,应按对象选择一个版本,再保留其完整明细集合。
  • 局部变更:新事件只更新部分字段或明细,需要按约定合并,不能直接替换整个对象。

参考 SQL 使用排名函数保留最高排名候选。并列排名可能保留多行,这可能正是保留同一版本多条明细所需要的行为;它不等于已经按明细业务键完成去重。

2.7 批次日志和临时表分别解决什么问题

批次日志记录本次处理涉及哪些 Raw 记录和业务键;临时表保存计算后的候选结果。

text 复制代码
通知关联标识
      ↓
定位 Raw 明细和受影响业务键
      ↓
记录 batch_id + 明细标识 + 业务键
      ↓
合并候选、选择版本
      ↓
临时表保存本批次结果
      ↓
替换 Processed 中对应业务范围的数据

这种方式把"本次影响范围"和"本次计算结果"显式保存下来,便于核对加工过程。

批次隔离需要贯穿所有 SQL。即使日志和临时表具有 batch_id,如果某条查询没有按批次过滤,仍可能混入其他任务的数据。字段存在与隔离生效是两回事。

临时表也不自动等于数据库会话级临时表。它可能是带批次字段的普通表,生命周期、索引和清理方式需要单独管理。

2.8 先删后插的结果替换需要事务和并发约束

为了更新同一业务对象的多条明细,参考链路先生成候选结果,再删除目标范围中的旧记录,最后插入新记录。

text 复制代码
计算候选结果 → 删除旧结果 → 插入新结果 → 提交

如果删除已经提交,而插入失败,业务查询就可能看到数据缺失。因此,相关步骤应处于明确的事务边界内,并确认实际使用的数据源、事务管理器和调用方式。

服务方法上的事务注解表达了设计意图,但不能单凭注解证明所有数据库操作都已由同一事务管理,更不能覆盖外部 Kafka 操作。

事务还需要与并发处理分别讨论。两个批次同时处理同一业务键,仍可能基于不同旧状态计算并交叉覆盖。需要结合消息分区、处理串行化、数据库锁或版本条件,明确同一对象的并发更新规则。

2.9 Kafka 负责触发处理,数据库承载阶段结果

在这类链路中,Kafka 通知可以携带关联标识,消费者收到通知后再读取数据库。

这样可以避免在层间重复传递完整报文,后续任务也可以基于已保存的数据重新计算。但消费者与表结构、数据可见时机及保留周期之间建立了依赖。

生产端应先确认当前阶段数据可用,再通知下一阶段。对于通知重复、投递失败和消费重试,也需要有对应处理方式。

text 复制代码
数据库写入成功
      ↓
Kafka 通知发送失败
      ↓
当前层有数据,下一层可能没有启动

这个故障窗口不会因为增加了数据分层而消失。反过来,先发通知再提交数据库,也可能让消费者读取不到数据。

案例不能因此宣称具有跨数据库和 Kafka 的原子提交。若需要加强可靠性,可以进一步设计待发送事件表、投递补偿和下游幂等,但这些属于明确的增强措施,不能当作三层结构天然提供的能力。

2.10 分层让故障定位和恢复入口更清楚

当业务查询出现缺数时,可以沿着三个阶段逐步核对。

数据状态 优先检查 可考虑的恢复入口
Original 缺失 上游发送、接入消费与原始落库 从上游恢复数据
Original 存在,Raw 缺失 解析、展平、字段容量和写入异常 从 Original 重新解析
Raw 存在,Processed 缺失 通知、消费、业务键和加工异常 从 Raw 重新加工或补通知
Processed 存在,页面缺失 查询条件、同步链路、关联和权限 修复应用侧查询或同步

这也是保留 Original 和 Raw 的直接收益:不用每次修复都回到上游重新取数。

不过,重新加工仍需明确范围、代码版本、重复处理和覆盖规则。分层提供了恢复的数据基础,任务状态、检查点、幂等和验收对账则需要另外实现。

2.11 Processed 之后仍然需要面向应用的数据组织

业务列表和报表的粒度,未必与 Processed 明细粒度一致。

例如,页面按"单据 + 运输批次"分页,而底层存在多个事件和商品明细。查询时直接关联多张明细表,可能导致一条页面记录扩成多行,影响分页和统计。

当前工程中也存在先通过视图、临时表和同步表组织结果,再由业务接口查询的链路。它表明三层主链路之后,仍可以根据应用需求建立派生数据。

结果形态 需要明确的设计
当前状态查询表 业务键、更新规则与数据延迟
应用聚合表 分组粒度、关联基数与刷新机制
历史报表快照 统计范围、主数据版本与生成规则

Processed 保存当前有效结果,不代表历史报表可以直接查询它来恢复任意过去状态。历史快照需要额外的数据和规则约束。

因此,三层结构应被视为数据接入与加工的主干,而不是对所有后续分析模型的穷举。

2.12 分层需要支付存储、延迟和维护成本

Original 保存报文,Raw 展开明细,Processed 保存业务结果,加上索引、批次日志和临时数据,会带来额外存储成本。

层间异步处理也带来可见性延迟:Original 已收到数据时,Processed 可能尚未更新;应用同步表或报表又可能晚一步。

成本 需要考虑的事项
多阶段存储 保留周期、归档、索引和临时数据清理
多阶段延迟 各阶段积压、刷新时间和业务可接受时效
多套数据结构 来源字段变化、解析兼容和历史重算影响
多个故障入口 分阶段错误记录、通知补偿和差异核对
来源信息留存 访问范围、展示脱敏和日志内容控制

这些是架构需要持续管理的事项,不代表当前系统已经全部完成治理。

对于数据量小、结构简单、没有重放和复用需求的链路,完整保留三层未必划算。分层深度应由追溯、加工复杂度和消费方式决定。

2.13 用数据链路核对分层是否有效

验证三层结构,应检查各层之间的关系和处理语义,而不只确认三张表都有数据。

场景 应核对的结果
嵌套数组展开 明细粒度、数量及来源关联符合预期
旧事件晚到 Processed 不因到达顺序错误回退
同一版本多条明细 完整保留所需明细,不误删、不扩行
重复通知 重复执行不产生不符合业务预期的结果
明细部分缺失 不以"已有一条数据"代替完整性判断
替换过程失败 核对回滚和提交后状态,不留下半次替换
同一对象并发加工 不混用批次,不发生未约束的覆盖
数据有而通知缺失 能够定位缺口并选择合适恢复入口
查询关联扩行 页面粒度、分页与汇总口径保持一致

以上是核对观察点,不代表已经完成全部验证。本文不提供未经测量的性能收益或测试通过结论。


3. 总结

Original、Raw、Processed 三层结构,把来源保存、结构转换和业务加工分成了可分别理解的阶段。

第一,Original 保留接入内容和来源位置,为追溯及重新解析提供依据。

第二,Raw 明确结构化明细的粒度,保留来源关联,让后续加工能够围绕关系型数据执行。

第三,Processed 根据业务键、版本和加工规则形成消费结果,查询聚合与历史快照仍可继续建立在其后。

第四,批次日志和临时表帮助组织增量加工,但批次隔离、事务、并发更新和通知可靠性需要分别落实。

第五,分层让故障恢复入口更清楚,同时也增加了存储、延迟和维护成本。它为可追溯、可复用和可重新计算提供基础,这些目标仍需要明确的数据契约与运行机制支撑。


附:实现检查表

检查项 需要确认的内容
层次职责 来源保存、结构转换、业务加工的边界明确
原始内容 清楚区分转换后报文与上游原始字节
来源位置 主题、分区、offset 可正确关联
明细粒度 展平规则与一对多关系明确
标识体系 原始 ID、明细 ID、业务键各司其职
消息契约 通知中的关联值及阶段含义明确
版本规则 区分业务时间、到达时间、快照与局部变更
批次隔离 日志、候选计算、临时表和更新范围一致
结果替换 事务资源及同一业务键并发规则明确
通知可靠性 数据库成功与 Kafka 投递成功分别确认
故障恢复 不同缺口有对应入口、范围和重复处理规则
应用消费 查询粒度、同步时效与历史快照分别设计
生命周期 保留、归档、清理及来源信息访问有明确约束

标签: 数据架构、数据分层、Kafka、数据加工、版本处理、数据追溯

相关推荐
lzhdim1 小时前
100条常用SQL语句
数据库·sql·mysql·mybatis
数据库小学妹1 小时前
金融数据库成本怎么算?五笔钱加一张TCO表
数据库·金融行业·数据库选型·国产化替代·tco·金融数据库
KaiwuDB2 小时前
KaiwuDB 分布式集群扩缩容,从原理到完整操作踩坑总结
运维·数据库·分布式
天远Date Lab2 小时前
零信任架构实战:基于天远企业年报信息核验构建自动化商业保理财务与社保穿透评估网关
人工智能·架构·自动化
EnCi Zheng2 小时前
后端怎么把AI接口稳定安全低成本接进业务:超时重试、密钥隔离与成本熔断清单
人工智能·后端·安全·架构·api
救救孩子把2 小时前
LangGraph 工程化架构:类型、状态、流式通信与可观测性
架构·agent·trace·opentelemetry·langgraph·graphrag
auspicious航2 小时前
PostgreSQL的备份与恢复
数据库·postgresql
这个DBA有点耶2 小时前
从 MySQL 到时序数据库:我帮朋友做迁移的完整记录
数据库·架构·dba
泡海椒2 小时前
深度解析JQuick-SQL AST到逻辑计划转换机制|分布式SQL原理
数据库·分布式·sql