面灵AI→

小红书 Product Engineer(AI 与全栈)一面:Node 后端、RAG 与秒杀场景设计

轮次
一面
时间
2026-10
来源
牛客网

《面试题目》

  1. 自我介绍
  2. 实习项目拷打:全栈项目介绍,是否独立完成,难点在哪里?
  3. 后端用 Node 写和传统后端有什么区别?
  4. Node 如何操作数据库,讲一讲?
  5. MySQL 查询慢怎么排查?
  6. AI 项目拷打:介绍项目,并具体讲解实现原理
  7. RAG 有哪些原理知识?
  8. AI workflow 平时怎么用 AI?
  9. spec 工作流怎么解决大模型幻觉?上下文压缩怎么处理?
  10. 工作中碰到过哪些挑战,如何解决?
  11. 场景题设计:双十一大促高频秒杀,如何优化提高 QPS?
  12. 数据库大量并发查询,响应缓慢甚至被打穿,如何治理?
  13. 如果一个页面请求多个服务,减少接口耗时的做法有哪些?
  14. 手撕:链表两数相加(稍复杂,需要处理进位)
  15. 反问

《参考解析》

Node 写后端和传统后端的差异,要落到运行时模型上。 Java/Go 这类多线程或 goroutine 模型靠并发度换吞吐,Node 是单线程事件循环加非阻塞 I/O:所有 I/O(网络、文件、数据库驱动)都以回调/ Promise 形式挂在 epoll 上,主线程只在事件就绪时执行回调。好处是没有锁竞争、上下文切换开销小,特别适合 I/O 密集的 BFF 和网关层;代价是任何一段同步 CPU 代码都会阻塞整个进程(大 JSON 序列化、加解密、复杂正则、同步 fs 调用),所以要靠 worker_threads 或拆服务把这些热点挪走。另外错误处理是显式传递的,未捕获的 Promise rejection 会直接打挂进程,进程级兜底(uncaughtException / 优雅退出)必须配齐。

Node 操作数据库的工程要点。 驱动层用连接池(mysql2 / pg 的 pool),池大小要跟数据库的 max_connections 和实例数一起算,不是越大越好——池过大反而会把数据库压垮。查询要么手写参数化 SQL(防注入),要么用 ORM/Query Builder。真正容易踩的是三件事:N+1 查询(循环里查关联表,要用 IN 批量或 join 收口)、事务边界(Node 的异步特性让「拿到连接—begin—多个 await—commit」必须显式固定在同一个连接上,用全局连接会串事务)、以及长事务与连接泄漏(忘记 release 会把池耗尽,看起来像数据库挂了,实际是应用侧问题)。

MySQL 慢查询的定位顺序。 先开慢查询日志确认是哪条 SQL、耗时多少、扫描了多少行;再 EXPLAIN(8.0 用 EXPLAIN ANALYZE 看真实行数)判断是没走索引、走错索引,还是走了索引但要大量回表。常见根因:条件列上做了函数/隐式类型转换导致索引失效、联合索引不满足最左前缀、选择性差的列建了索引、深分页(LIMIT 100000, 20 要扫描并丢弃前十万行,改用游标或延迟关联)、以及排序/分组无法走索引而落到临时表。优化方向永远是减少扫描行数和随机 I/O,而不是无脑加索引——索引本身会拖慢写入、占空间。

RAG 的原理和工程取舍。 链路是「文档切分 → 向量化 → 入库 → 查询改写 → 召回(向量/关键词/混合)→ 重排 → 组装上下文 → 生成」。原理上向量检索解决的是语义泛化,关键词检索(BM25)解决的是专有名词、型号、错误码这类精确匹配,所以生产上多用混合召回加 RRF 融合,再上 cross-encoder 重排。工程上真正决定效果的是三件不起眼的事:切分粒度(切太碎丢上下文、切太大稀释相似度)、元数据过滤(版本、时间、权限这类硬约束不能交给相似度)、拒答策略(检索没命中证据时要能说「不知道」,而不是靠语义相似硬编)。

幻觉和上下文压缩这两问,回答要承认取舍而不是给银弹。 幻觉治理分三层:输入端约束(检索证据必须落进上下文、要求带引用)、生成端约束(提示词里明确「只依据给定材料、无依据就说证据不足」、结构化输出便于校验)、输出端校验(引用核对、事实性检查、必要时二次检索验证)。上下文压缩本身就是幻觉的来源之一——摘要会丢细节、丢否定词、丢数字。可行做法是分层:近期对话保留原文,远期压缩成结构化状态(已确认事实、待办、约束条件)而不是自由文本摘要,关键事实外置到可检索的记忆里按需回灌;压缩后再做一次「关键信息是否还在」的检查,并把压缩策略纳入离线评测集。

秒杀提 QPS 的思路是分层削峰,不是把单机做强。 静态资源全量 CDN,活动页做成静态化;入口用验证码/答题/令牌把瞬时流量拉平;库存预扣放 Redis(Lua 脚本保证「查库存 + 扣减」原子),扣减成功才发 MQ 异步落库下单,前端轮询或推送结果;库存可按维度分桶(比如按商品分片到多个 key)降低单 key 热点,但分桶后要做对账防止超卖和少卖。剩下的都是老几样:网关限流、服务降级(非核心链路熔断)、幂等(同一用户同一活动只成一单,靠唯一键或去重表)、以及压测确认瓶颈到底在网关、Redis 还是数据库。

「并发查询打穿」和「多服务聚合耗时」是两个典型的规模化问题。 打穿的本质是热点数据在缓存失效瞬间所有请求同时回到数据库,对策是多级缓存(本地 + Redis)、逻辑过期加异步刷新、热点探测后主动预热、单飞(singleflight)合并同 key 请求、以及数据库侧限流保护。多服务聚合接口的耗时可以按三条线砍:把串行调用改成并行(Promise.all),把多次细粒度调用合并成批量接口,在 BFF 层做缓存和字段裁剪;同时必须给每个下游设独立超时 + 降级兜底,否则一个慢下游会把整页拖死(这正是「页面请求多个服务」最容易翻车的地方)。

手撕链表两数相加。 核心是虚拟头节点 + 进位:逐位取 l1.val + l2.val + carry,sum % 10 作为当前位、sum / 10 作为新进位,两个链表都走完且进位为 0 才停。原帖作者说链表构造没写出来、直接写了核心逻辑——面试时把构造和算法分开写、并主动说明边界(空链表、长度不等、最后还有进位要多挂一个节点)通常比闷头写完整更稳。