1. 统一SQL概述:为什么需要统一SQL?
做数据开发的朋友,十有八九都遇到过这个场景:
业务方早上跑来问:「昨天的DAU是多少?」
你查离线数仓,回答「1250万」。
旁边实时团队说「不对,我们实时看的是1280万」。
两边数字对不上。业务方懵了,你更懵。
这种「口径差异」我见过太多次了。刚入行那几年,我甚至觉得这是常态——离线归离线,实时归实时,各算各的呗。直到有一次大促复盘,离线口径和实时口径差了将近5%,老板在会上直接问「到底哪个是对的?」
嗯,从那以后我就开始认真琢磨:能不能用一套SQL,把实时和离线统一起来?
1.1 实时与离线口径差异的根源
要解决差异,先得搞清楚差异从哪来。我把它归结为三个层面:
1.1.1 数据源差异
离线任务读的是Hive表、HDFS文件,数据是T+1的,已经落盘了。
实时任务读的是Kafka、Pulsar这类消息队列,数据是流式的,还没定型。
你想想看,一个是从仓库里拿冻货,一个是从流水线上抓活鱼。源头就不一样,算出来的结果能完全一致吗?
| 维度 | 离线数据源 | 实时数据源 |
|---|---|---|
| 存储介质 | Hive / HDFS / HBase | Kafka / Pulsar / Redis |
| 数据状态 | 已落盘、不可变 | 未落盘、持续流入 |
| 回溯能力 | 强(全量重跑) | 弱(依赖offset重置) |
| 数据质量 | 可清洗、可回溯 | 可能有乱序、延迟 |
1.1.2 计算引擎差异
离线计算,大家用的多是Hive、Spark SQL、Presto。这些引擎擅长批处理,吞吐量大,延迟高一点没关系。
实时计算,主流是Flink SQL、Spark Streaming。它们处理的是无界流,要求低延迟、高吞吐,但状态管理复杂。
我遇到过最头疼的事:同一个SUM聚合,在Hive里跑出来是100,在Flink里跑出来是99.8。查了半天,发现是Flink的浮点累加顺序不同导致的精度损失。
说白了,引擎底层的计算模型、状态管理、容错机制都不一样。你指望它们天然对齐?不现实。
| 对比项 | 离线引擎(Hive/Spark) | 实时引擎(Flink) |
|---|---|---|
| 计算模型 | 批处理(有界数据) | 流处理(无界数据) |
| 容错机制 | 任务重跑 | Checkpoint + 状态恢复 |
| 精度控制 | 精确一次(易实现) | 精确一次(需配置) |
| 典型延迟 | 分钟~小时级 | 秒~分钟级 |
1.1.3 时间语义差异
这是最隐蔽、也最容易踩坑的地方。
离线任务通常用处理时间——也就是任务跑起来的那一刻。比如每天凌晨2点跑昨天的数据,那「昨天」就是固定的分区。
实时任务则更关注事件时间——也就是用户行为实际发生的时间。但问题来了:用户的行为日志可能延迟到达,比如网络不好、手机离线,事件时间戳可能是10分钟前的。
我记得有一次做实时大屏,PV数据总是比离线少3%左右。排查后发现,实时任务用了事件时间,但watermark设置得太激进,把一些迟到数据丢掉了。离线任务用的是处理时间,所有数据都算进去了。
你看,同一个指标,同一个SQL逻辑,就因为时间语义不同,结果就不一样。
1.2 统一SQL的核心思路
既然差异的根源清楚了,那怎么解决?
我的答案是:构建一套SQL抽象层,让同一段SQL在实时和离线引擎上跑出相同的结果。
这不是让Flink和Hive变得一样,而是让它们对外暴露的SQL语义一致。具体来说:
- 语法统一:同一段SELECT、JOIN、AGGREGATE,在离线和实时引擎上都能解析执行。
- 语义统一:时间窗口、去重逻辑、状态管理,在两种引擎下行为一致。
- 结果统一:相同输入,相同逻辑,输出相同结果。
你可能会问:这能做到吗?
能。但需要一些「技巧」。比如:
- 用统一的元数据层屏蔽数据源差异
- 用标准化的时间函数处理时间语义
- 用Lambda架构或Kappa架构做结果校验
这些内容,后面的章节会一个一个展开讲。
1.3 统一SQL的价值
说点实际的。统一SQL能带来什么好处?
- 减少沟通成本:实时团队和离线团队不用再为「谁的数字对」吵架。
- 提升开发效率:写一套SQL,到处运行。不用维护两套逻辑。
- 保证数据一致性:业务方看到的是一个数字,而不是两个。
- 降低运维复杂度:口径变更只需改一处,实时和离线同步生效。
一句话总结: 统一SQL不是技术炫技,而是解决实际业务痛点的工程手段。它让数据团队从「救火」变成「预防」,从「解释差异」变成「消除差异」。
1.4 本章知识体系
下面这张图,帮你快速理清本章的核心脉络:
这张图把本章的核心逻辑串起来了。从左到右看:先搞清楚差异从哪来,再针对性地给出解决方案,最终实现口径统一。
后面的课程,我会带着你一步步把这些方案落地。包括怎么写统一SQL、怎么配置元数据、怎么处理迟到数据、怎么做结果校验……都是我在实际项目中踩过坑、填过坑之后总结出来的经验。
准备好了吗?我们开始。