主题
按简历实习三段 bullet 和自我介绍钩子组织的专项题库,每题带完整答案与演练状态。(演练状态见文末总览表)。配套:Binlog2Hive链路设计、可观测性埋点、专项 · CodeAgent CLI 题库。
第一部分 · Bullet 1:同步主链路
1. 「Canal、Spark 是专门团队做的,你说的『编排与落地』具体做了什么,边界在哪?」 演练 9 分
完整答案:
在这条 MySQL 到 Hive 的链路中,我主要负责把 Canal 采集、MQ 中转、HDFS 存储、Spark 计算这四段既有能力编排成一条可配置的同步链路。具体三块:一是任务创建与发布流程——12 个阶段的发布流程从 RDS 读权限、Canal 到 MQ 的路由选择、HDFS 落地配置、自动建表,到全量导入和 Spark 增量任务的注册,都是我打通的;二是全量和增量的衔接机制——以 wholeLoadBatchTime 作批次锚点,配合 Snap2Binlog 三种迁移模式;三是联调与故障排查——延迟按四段链路逐段定位,数据不一致做比对重导。Canal 内部的采集实现和 Spark 的计算逻辑分别是采集团队和引擎团队的,但选型、配置、上下游契约都是我的工作范围,原理我也掌握到设计级。
要点:三档动词守边界——「都是我打通的」(自有)/「选型、配置、契约是我的工作范围」(参与对接)/「采集实现和计算逻辑是团队的」(原理我掌握)。
2. 「为什么采集和计算中间必须隔一层 Kafka/DDMQ?Canal 直接写 HDFS 不行吗?」 演练 9 分
完整答案:
五个理由。一是削峰:源库一次大事务或批量 update 会瞬间产生海量 Binlog,直接落 HDFS 既写不进去也会把 Canal 压垮,MQ 是缓冲池。二是解耦:Canal 的生命周期和数据消费完全分离,下游处理出错不影响采集。三是多订阅:同一份 Binlog 可以同时给实时数仓、索引构建、缓存失效等多个消费方。四是回放:下游处理逻辑有 bug 时,重置消费位点就能重放数据——这也是死信修复和历史批次补跑的地基。五是这条链路特有的:Dsink 是按 micro-batch 落 HDFS 的,天然需要 MQ 做聚合缓冲——直连的话小文件问题和可用性问题会一起爆炸。
3. 「全量和增量的重叠窗口怎么产生的?批次锚点怎么解决?」 两场复检通过
完整答案:
重叠是结构性的:Canal 采集链路在发布流程第 2 步就建好,从那一刻起 Binlog 持续进 MQ;而全量是第 8 步才执行的快照导入,DataX 写完 Hive 首个分区才记录批次时间。所以从采集开始到全量完成之间,同一行数据既在快照里、又在 MQ 的 Binlog 流里——这个窗口消灭不了,只能收敛。
锚点的角色是把「不丢」钉死:全量完成时记录 wholeLoadBatchTime 作锚点,增量任务从锚点对应的位点之后开始消费,位点之后的变更一条不少。位点之前、已经进 MQ 的部分和快照天然重复,靠下游 Spark 合并按主键保留位点最大的末态收敛——重复写一百遍结果一样,这就是幂等。所以整条链路的口径是 at-least-once 投递加主键幂等,业务上不丢不重。
全量跑的几个小时里,增量一直在 MQ 里按「表加主键」分区有序积压;全量完成、锚点记录后,增量调度任务从锚点位点开始消费,这些变更按调度周期逐步写成 Hive 分区——同一主键全量里已有的,位点大的覆盖位点小的。
4. 「锚点为什么用 Binlog 位点,不用业务时间戳?」 快问过线
完整答案:
核心一句:位点在单库内是全序单调的,业务时间戳不是。
位点由「Binlog 文件名 + 偏移量」组成,每一条事件唯一且严格递增——所以既能当切分锚点(wholeLoadBatchTime 精确落在某条事件上,增量从位点之后消费,一条不丢),又能当排序依据(同一主键多次变更,按位点保留最大的那条,末态一定正确)。
业务时间戳撑不起这两个职责:一是碰撞——同一秒多条变更,排序分不出先后;二是回拨——NTP 校时、主从切换都会让时间戳倒退,按时间戳切分会出现「后发生的变更被当成旧的」;三是它不是 Binlog 原生属性,应用层填的时间戳不可控。所以位点管顺序和切分,时间戳只做展示。
第二部分 · Bullet 1 延伸:数据语义
5. 「整条链路是 exactly-once 吗?重复怎么产生、在哪收敛?」 演练 9.5 分
完整答案:
诚实定性:链路整体是 at-least-once,靠下游主键幂等去重把效果做到 exactly-once。重复来源有三类:MQ 在消费者失败时重放、Spark 任务失败重跑会重读分区、全量快照与增量的重叠窗口——追求端到端 exactly-once 需要事务性 sink(比如 Flink 两阶段提交加事务表),复杂度和运维成本都高。
我们的做法是把「去重」放在最确定的位置:Spark 合并按主键保留末态,天然幂等——同一批数据重跑一百遍,结果都一样。再配合全量批次时间锚点和分区覆盖写,「可重跑、可重放、结果收敛」。对数仓场景来说,这套「at-least-once + 幂等」的性价比远高于强 exactly-once。
三类重复来源正好对应简历上的三种修复能力:MQ 重放 → 位点重置回放,任务失败 → 历史批次补跑,大面积不一致 → 快照重导。
6. 「Hive 不支持行级更新,变更怎么落地?同一主键一分钟改五次怎么保证留最新?」 演练后补强(本题曾丢末态半问)
完整答案:
落地上 Hive 是只追加的分区表,行级变更两种模式。合并模式(latest):每次调度时 Spark 读本周期分区的 Binlog 增量,和历史最新状态按主键合并,保留每条主键的最新版本写出新分区——下游看到的是「当前状态」表。保留变更模式:事件原样落表、带 INSERT/UPDATE/DELETE 标记,下游自己聚合,适合审计场景。DELETE 的处理由 saveDelete 配置控制:开是保留删除标记、可审计可回溯,关是物理剔除、不进结果集。
同一主键一分钟改五次,末态靠两层顺序保证。传输层:Canal 投 MQ 的分区键是「表 + 主键」,同一主键的所有事件永远落同一个分区、分区内严格有序。计算层:Spark 合并时按 Binlog 位点排序取末态——位点在单库内是全序单调的,比业务时间戳可靠(时间戳可能重复、可能回拨)。所以五次变更不管落在几个调度批次里,最终收敛的都是位点最大的那条。再引申一句:这个方案最隐蔽的关键设计就是分区键——如果用随机 key,同一主键的事件散到不同分区,跨分区无序,末态就可能是错的。
边界也主动交代:如果业务要求真正的行级更新和事务,业界现在的答案是 Hudi/Iceberg 这类 ACID 湖表格式;我们用「分区 + 合并」是平台既有体系的取舍——知道替代方案,才说明这个模式是选出来的,不是只会这一个。
第三部分 · Bullet 2:一致性保障与修复
7. 「单事件 / 批次 / 全量三级修复粒度,分别指什么?各靠什么机制?」 未答过(完整答案已备)
完整答案:
三级粒度对应三种「坏的范围」——单条坏、一个周期坏、整表坏,各有一条修复链路。
**单事件级:死信队列修复。**场景是某个事件解析失败——这种「毒丸」不能留在主流程重试(反复失败会把整个分区的消费堵死),也不能直接丢(丢数据)。做法是原样转存死信、记录源端偏移量,主链路跳过继续消费;事后修好解析逻辑,按偏移量从源端重放,重放走正常入湖链路。成功率 98% = 修复成功重新入湖的条数 ÷ 进入死信的总条数;修不了的 2% 是源头数据本身损坏、怎么解析都不合法的,标记跳过加告警,不阻塞主链路。
**批次级:历史批次补跑(Gap Batch)。**场景是某个调度周期(小时 / 天)的任务失败或漏跑,导致那段时间的数据没进 Hive。做法是按指定时间范围把漏掉的批次调度任务补上,而且依赖关系自动处理——补 T-2 之前先确认 T-3 已经完成。因为 Spark 合并按主键保留末态,重跑是无害的,补跑就是「重新执行一遍调度」这么简单。
全量级:全量回溯(快照重导)。场景是 Binlog 丢失损坏、或大面积数据不一致,增量修复救不回来。做法是 DataX 低峰限流重新导一次快照、覆盖历史分区,完成后记录新的批次时间锚点,增量从新锚点重新衔接。
三级能做成「无害重放」,靠的是同一个底座:下游主键幂等 + 批次锚点。这也是为什么敢把修复拆成三个粒度——每一级的重放、补跑、重导都是幂等收敛的,不会越修越乱。
8. 「死信修复成功率 98% 的口径?」 毕业题(复检)
完整答案:
口径:死信修复成功率 = 修复成功重新入湖的条数 ÷ 进入死信的总条数。
为什么要有死信:解析失败的异常事件是「毒丸」——留在主流程重试会把整个分区的消费堵死,直接丢弃就是丢数据,所以折中:原样转存死信、记录源端偏移量,主链路跳过继续消费,事后集中修复。
修复的闭环:死信里带着原始事件和偏移量,大多数修复是「修好解析逻辑后按偏移量从源端重放」,重放走正常入湖链路,可观测、可审计。修不了的 2% 是源头数据本身损坏、怎么解析都不合法的——标记跳过加告警,人工确认后归档,不阻塞主链路。
9. 「位点重置回放是什么场景用的?回放会不会写重?」 题库内自答
完整答案:
场景是下游消费逻辑有 bug:比如 Spark 的解析逻辑有问题,某个时间段的数据处理错了,这时不需要动源库,把消费位点重置到那个时间段之前,MQ 里的数据就能重放一遍——这是 MQ 解耦给数据修复留的地基。
会不会写重:会重复写入,但无害——Spark 合并按主键保留位点最大的末态,重放的数据重跑一百遍结果都一样,靠下游幂等收敛。这就是「at-least-once 投递 + 幂等」口径在修复场景的应用:回放只管把数据再给一次,正确性由幂等兜底。
第四部分 · Bullet 1 延伸:延迟与可观测
10. 「入湖延迟从 3 分钟涨到两小时,怎么排查?」 演练 9 分
完整答案:
分诊先说:告警三阈值(延迟超基线、错误记录数、数据量低于下限)先于人工发现问题,超基线先通知任务负责人自判——是不是自己源库在批量写;处理不了升级到平台侧,按链路四段从上游往下逐段看水位。
第一段看源:MySQL 写入 QPS 是否突增——大事务、定时批量 job,源头洪峰属正常延迟,等峰值过去或临时扩下游消费。第二段看 Canal:采集位点滞后多少,实例有没有重启或报错。第三段看 MQ:消费堆积是否持续增长——增长说明消费能力不足或消费者挂了;再看分区倾斜,热点表打满单个分区。处置优先加消费者实例,不随便扩分区——扩分区改变 key 到分区的映射,同一主键的新旧消息可能跨分区,分区内有序被打破,而末态正确性依赖这个有序性。第四段看 HDFS 和 Spark:Dsink 落地文件是否按时生成,Spark 调度有没有失败、资源是否排队——资源不足就提并行度。
实践中大多数就两类:源头批量写入的洪峰,等或扩消费;Spark 资源不足排队,提并行度。
11. 「可观测性那条:AOP 埋点切面怎么实现?埋了哪些字段?40+ 怎么统计?」 演练 5 分(口径已立)
完整答案:见 可观测性埋点 的「60 秒标准答案」——@Aspect + 自定义注解 @InterfaceLog 显式标注、@Around 环绕通知 proceed() 前后计时、统一执行记录(接口标识 / 入参摘要 / 出参摘要 / 耗时 / 状态 / 异常摘要 / traceId)、敏感字段脱敏、日志异步写;40+ 是显式标注注解的核心接口数(12 阶段发布流程各阶段接口 + 任务管理接口 + 数据源注册)。
要点:标准化是题眼;异常路径全覆盖;脱敏与发布流程加密步骤呼应;40+ 报构成不报约数。
演练状态总览
| 题号 | 主题 | 状态 |
|---|---|---|
| 1 | 职责边界与三档动词 | 9 分 |
| 2 | MQ 五个理由 | 9 分 |
| 3 | 重叠窗口 + 锚点 | 两场复检 |
| 4 | 位点 vs 时间戳 | 快问过线 |
| 5 | exactly-once 口径 | 9.5 分 |
| 6 | Hive 落地 + 末态 | 丢过末态半问,已补 |
| 7 | 三级修复粒度 | 未答(答案已备) |
| 8 | 死信 98% 口径 | 毕业题 |
| 9 | 位点重置回放 | 题库内自答 |
| 10 | 延迟排查 | 9 分 |
| 11 | AOP 埋点 | 5 分,口径已立 |