字节中交广 AI 全栈开发一面:AI Pipeline、Doris 优化与实时轮播场景
- 轮次
- 一面
- 时间
- 2026-09
- 来源
- 牛客网
《面试题目》
- 最近一段实习做的这套 AI Pipeline 的主要目的是什么?
- 在这套 AI Pipeline 中,你如何保证它每一个节点执行效果的准确性?
- 让 AI 自己发挥的话,它肯定会有一些幻觉或者偏移,你是怎么解决这些问题的?
- 你是怎么想到这套方案的?有参考过什么项目或者文章吗?
- 这套 Pipeline 最终的效果怎么评估?
- Pipeline 中 generate 步骤(代码生成环节)你怎么保证生成的代码质量比较高?
- AI Pipeline 对业务知识的理解怎么保证准确?
- 在这个项目当中,你觉得你遇到过的最大的问题是什么?
- 数据漏斗是怎么做的?数据存储介质用的是什么?筛选过滤方案是什么?
- 针对营销场景模块,你对其中大规模数据的 SQL 是如何处理优化的?
- 存储介质为什么用 Doris,不用 Hive / ES?Doris 的优势在哪里?如何做的技术选型?
- 从脚本出数改为自动化流程出数,带来的价值是什么?为什么要做这样一个改造?
- 这么大的数据量对你来说有什么挑战?处理过程中遇到过什么问题?如何解决的?
- 除了 SQL 手段,有没有想过通过其他技术手段提升出数任务的效率?
- 当时是怎么想到给 Doris 加倒排索引的?你是怎么分析、怎么排查瓶颈的?完整思考过程是怎样的?
- 有没有考虑过不用倒排索引、选择其他方案做优化?
- 场景题:现在有一个场景,对几十亿的数据进行筛选过滤,加了索引之后效果还是不好,这个时候你会怎么优化这条链路?(底表可能上千亿,加索引查出来还有几亿条)
- 场景题:医院排队取药屏上会轮播患者名字。现在要做一个 PC 页面实现类似的轮播通知,信息是用户维度的、不同用户之间隔离。后端通过实时数仓的 MQ 每秒推送消息,后端消费后给到前端,前端轮播展示最近 20 条。让你设计实现,你会怎么考虑?
- 在你这套方案中,你觉得哪块成本最高、最复杂?
- 最近 20 条信息你是如何过滤的?消息是一条一条推送的,你如何做截断?另外 19 条消息在哪?
- 你说把数据放到 Redis 里、再用 MySQL 做持久化,为什么要做持久化?直接用 Redis 不行吗?
- 你同时用了 MySQL 和 Redis,会有数据不一致的问题吗?保证一致性的方案有哪些?
- 那我直接用 MySQL 不就好了吗?为什么要用 Redis,有什么好处?
- 如果 MQ 发了很多条一样的消息,你怎么做幂等?
- 前端轮播为什么采用 WebSocket 的形式?还有什么其他方案?用 WebSocket 会引发什么其他问题?
- 手撕:一道两张表的 SQL,讲一下你的思路;如果要给这两张表加索引,你会怎么设计?
《参考解析》
为什么用 Doris 而不是 Hive / ES:选型的核心是「查询模式决定引擎」。Hive 是离线批处理,查询延迟分钟级起步,做不了看板上的交互式下钻;ES 擅长全文检索和日志场景,但做聚合分析的精度与成本都不划算(terms 聚合有近似问题、宽表存储膨胀、join 能力弱)。Doris 是 MPP 架构的列式分析库,支持向量化执行、物化视图和标准 SQL,能对亿级明细做秒级聚合,还能直接对接实时写入。加上倒排索引后,它又能承担一部分「点查 + 高基数列过滤」的检索需求,等于把「分析」和「过滤」放在同一个引擎里,少一次数据搬运。
排查倒排索引瓶颈的完整过程:先定位再动手。① 用 EXPLAIN 和 profile 看执行计划,确认慢在哪一段——是扫描行数太多、谓词没下推,还是聚合阶段爆了;② 看具体 SQL 的过滤条件分布,如果某个高基数列(如用户 ID、订单号)上的等值/IN 过滤把扫描量卡在几十亿行,说明缺的是「点查型」索引而不是分区裁剪;③ 验证假设——建倒排索引后对比扫描行数、耗时和 I/O;④ 还要做全局比较:如果瓶颈其实是聚合中间结果太大,那可以先用索引把候选集压小再聚合,或者用预聚合表/物化视图把重复计算消掉。
几十亿过滤后还剩几亿怎么优化:加索引只是把「读得少」做到位,剩几亿条说明结果集本身很大,瓶颈已经转移到「算得慢 / 传得慢」。这时候的方向是:① 预聚合——把高频的过滤+聚合组合固化成物化视图,让查询直接读结果;② 分区与分桶裁剪,按时间分区、按高基数列分桶,减少参与计算的数据块;③ 只取需要的列(列存下压),避免 SELECT *;④ 如果下游只要 Top-K 或看板聚合值,就在引擎侧先算完再出结果,不要拉明细;⑤ 再不行就上采样/近似算法(如 HyperLogLog 做 UV),用精度换时间。
实时轮播通知的设计:拆分「接收—存放—推送」三段。接收侧消费 MQ,按 userId 做幂等(消息 ID 或业务唯一键 + Redis SETNX),避免重复推送。存放侧为每个用户维护一个定长列表,Redis 用 LPUSH + LTRIM 0 19 保证只留最近 20 条,LTRIM 是 O(1) 的边界裁剪,天然解决「如何截断」,不需要自己去数第 21 条。持久化用 MySQL 是为了可追溯与重启恢复:Redis 是缓存,宕机或过期后列表就没了,而轮播是用户可见的功能,需要能从 DB 重建。一致性上采取「DB 为准、Redis 可重建」,写 DB 成功后再写 Redis,Redis 未命中时回源 DB 并回填,不做双写强一致。
为什么必须用 Redis:单靠 MySQL 撑不住每秒级、按用户维度的高频读写——每条消息都要写一次、每个前端都要读一次,MySQL 会被写入放大和连接数打满。Redis 的 LPUSH + LTRIM 把「维护定长列表」变成一个 O(1) 操作,读侧也能一次 LRANGE 拿到 20 条。更本质的理由是职责分离:DB 存全量历史保证不丢,Redis 存热点窗口保证读得快。
MQ 幂等:三层兜底。① 消息自带唯一 ID,消费前用 Redis SETNX 抢占,成功才处理,失败直接 ack;② 数据库唯一索引兜底,即使 Redis 失效重复写入也会被约束挡住;③ 消费逻辑本身设计成幂等,比如用 INSERT ... ON DUPLICATE KEY UPDATE 或状态机式更新(只在特定状态才推进),而不是 balance = balance + x 这种累加。
WebSocket 的必要性与代价:轮播要求服务端主动推、延迟低,HTTP 轮询要么延迟高(间隔大)要么请求量大(间隔小)。WebSocket 一条长连接双向通信,最贴合。代价是长连接的稳定性运维:需要心跳保活、断线重连、连接数上限与内存占用、网关层的连接迁移;服务端还得为每个连接维护订阅关系(谁关心哪个 userId),并在用户多设备登录时广播到该用户的所有连接。