三七互娱后端实习一面:单例模式、CI/CD 链路与 RAG 全流程
- 轮次
- 一面
- 时间
- 2026-10
- 来源
- 牛客网
《面试题目》
- 来一段自我介绍。
- 你了解设计模式吗?
- 了解单例模式吗?手写一下如何实现单例模式吧。
- 讲解一下你的实习成果具体有什么?
- 你设计的 API 文档自动化搭建,GitLab 的 CI/CD 具体的一个触发链路是怎么样的?
- 你提到过参与了线上排查与修复,说一下你印象最深的一个 bug 吧。
- 这个 bug 的难点是怎么排查出来的?既然如此,你有没有能优化这类 bug 排查链路的思路?
- 那对于 MySQL 的 SQL 执行得很慢,这类慢查询你该如何去查?
- EXPLAIN 中的 type 有哪些种类?
- 对于这类慢查询,你的排查和解决思路具体是什么?
- 你提到了协程,那能具体讲讲你对协程的理解吧。
- 在 K8s 里,Pod 是指什么?
- 如果 Pod 里面有容器出现了问题,你的排查和解决思路是什么?
- RAG 是什么?
- RAG 的构建流程具体展开讲讲?
- 那 RAG 召回的流程也顺便描述一下吧。
- 你所使用的向量模型是什么?
- 如果模型进行检索后的结果很差,这类问题你该如何排查?
- 你过来需要多久时间?
- 如果我要上线一个 AI 应用,如何搭建一个 AI 质量检测平台,及其所实现的链路思路是什么?
- 你的实习时长是多久?
- 反问。
《参考解析》
设计模式与手写单例
单例模式要写出线程安全的版本并说清取舍。懒汉式(双重检查锁)是标准答案:实例字段用 volatile 修饰(防止其他线程读到「引用已赋值但构造未完成」的半成品对象),第一次判空后 synchronized,同步块内再判空一次。静态内部类(持有者)写法最简洁且天然线程安全(类加载机制保证),也不会有反射以外的破坏方式;枚举实现能防反射与反序列化破坏,是《Effective Java》推荐的写法。要主动讲清破坏单例的手段:反射调用私有构造、反序列化生成新实例(用 readResolve 兜)、以及多类加载器;再补一句「Spring 的单例是容器级单例(一个容器一个实例),与 JVM 级的单例是两回事」。设计模式整体回答时给分类(创建型、结构型、行为型),各举一个你在项目里真正用到的(比如策略模式处理多种支付渠道、模板方法定义流程骨架、责任链做校验),比背诵 23 种更有说服力。
CI/CD 触发链路与线上 bug 的排查链路优化
API 文档自动化 + GitLab CI/CD 的链路按「代码提交 → 触发 → 构建 → 校验/生成 → 发布」讲:开发者提交或合并 MR(only/except 或 rules 决定触发条件),Runner 领取任务执行 .gitlab-ci.yml 里的 stage(拉代码、装依赖、跑测试、扫描),文档生成通常是从代码注释或接口定义(OpenAPI/注解)产出静态文件,再发布到文档站或制品库,最后通知(钉钉/飞书/邮件)。要说清工程细节:Runner 的类型与并发、缓存与产物(cache 与 artifacts)、环境变量与密钥管理(避免把凭据写进仓库)、以及幂等与回滚(文档站用版本目录,出问题能切回上一版)。优化的落点是「让文档与代码不脱节」——把生成步骤放进流水线并设为合并门禁,接口变更不带文档就不允许合并。
「印象最深的 bug」按现象—定位—修复—预防四段讲,并要能回答「怎么优化排查链路」这个加分追问。可用的优化方向有四类:把信息前置(关键路径打结构化日志与 traceId、出入参与错误码统一、让一次请求的所有日志可串联);把工具做顺(给常见故障写排查手册与脚本,比如一键看某个实例的 GC、线程栈、慢查询);把边界涂明(用参数校验与断言把问题挡在入口,用单测覆盖易错分支);把经验沉淀(每次故障写复盘并转成监控规则,比如这次 bug 如果换成「某指标突增就告警」就能提前发现)。面试官问这题,其实是在看你会不会把一次故障变成系统的能力。
慢查询排查与 EXPLAIN 的 type
慢查询排查按「发现 → 定位 → 优化 → 验证」讲。发现:开慢查询日志(slow_query_log、long_query_time)、看监控里的耗时分布与 performance_schema 的 TOP SQL。定位:用 EXPLAIN 看执行计划,重点关注 type(访问类型)、key(实际用的索引)、rows(预估扫描行数)、filtered、以及 Extra(是否 Using filesort、Using temporary、Using index)。type 从优到劣大致是:system > const > eq_ref > ref > range > index(全索引扫描)> ALL(全表扫描),另外还有 fulltext、index_merge、ref_or_null、unique_subquery 等;出现 ALL 或 index 通常就要考虑加索引或改写 SQL。优化手段按收益排序:补或用对索引(消除函数与隐式转换、遵守最左前缀)、减少返回列(避免回表、用覆盖索引)、改写 SQL(把子查询改 join、拆大事务、避免 or)、处理深分页(游标或延迟关联)、必要时做结构优化(分区、归档、汇总表、读写分离)。验证:用 EXPLAIN ANALYZE 或对比优化前后的实际耗时与扫描行数,并在压测环境确认没有把其他查询拖慢。
协程、K8s Pod 与容器故障排查
协程是用户态的轻量级并发单元,由运行时或语言自己调度(Go 的 goroutine、Kotlin 的 coroutine、Python 的 asyncio 任务),切换不陷入内核、栈可以很小(几 KB 起步),因此能开出远超线程的数量。要讲清它的模型差异:Go 用 GMP 模型(G 是协程、M 是内核线程、P 是处理器上下文),通过工作窃取与网络轮询器把阻塞的 goroutine 挂起而不是阻塞线程;Python 的协程是单线程事件循环,遇到阻塞调用会卡住整个循环,所以必须用异步库。适用场景是高并发 IO(网络请求、文件、数据库),不适合 CPU 密集(GIL 或调度开销),CPU 密集要用多进程或并行框架。要顺带提坑:协程泄漏(没有正确等待或取消)、共享状态需要显式同步、以及超时与取消要沿调用链传递。
K8s 里 Pod 是最小的可调度单元,一个 Pod 可以包含一个或多个共享网络命名空间与存储卷的容器(典型的多容器模式是 sidecar 做日志/代理、init container 做初始化)。Pod 故障排查按顺序:先看状态(kubectl get pod -o wide 看 STATUS 与重启次数,kubectl describe pod 看 Events 与探针失败原因),再看日志(kubectl logs,多容器要 -c,崩溃前要看 --previous),然后看资源与调度(kubectl top pod、describe 里的节点资源不足与 OOMKilled、Evicted、镜像拉取失败 ImagePullBackOff、探针失败导致反复重启 CrashLoopBackOff)。常见根因分类:镜像与配置(拉取失败、配置项或密钥缺失、启动命令错误)、资源(内存超限被 OOM Kill、CPU 被限流导致探针超时、节点资源不足被驱逐)、依赖(下游不可达、DNS 解析失败、网络策略拦截)、以及存储挂载失败。排查完要给出修复与预防:探针阈值调整、资源 request/limit 合理设置、用 PodDisruptionBudget 与多副本避免单点。
RAG 全流程、向量模型与检索效果差的排查
RAG 的构建流程按「资料 → 切分 → 向量化 → 存储 → 检索 → 生成 → 评估」讲:资料收集与清洗(去重、去噪、版本管理)、切分(按结构或语义,带重叠,块大小与粒度要按问答类型调)、向量化(选 embedding 模型,注意维度、中文效果、以及是否需要归一化)、入库(向量库 + 元数据,元数据用于过滤权限与范围)、检索(向量召回 + 关键词召回混合,融合后 rerank)、生成(把片段拼进 prompt,要求标注引用、资料不足时明确说不知道)、评估(建带标准答案的测试集,看召回命中率与答案正确率)。召回流程单独描述时要讲清:查询改写(同义扩展、把口语问题转成检索式)、多路召回(向量管语义、BM25 管关键词)、去重与截断(按相似度与多样性)、以及重排(交叉编码器打分)。
向量模型答不出具体名字时,要能给出选型维度而不是硬编:中文语义效果、向量维度与存储成本、最大输入长度、是否支持多语言、以及推理成本与部署方式(API 还是本地)。
「检索结果很差怎么排查」是这道题的核心,按环节归因:一是切分问题(块太大导致一个块里多主题互相稀释、太小导致语义不完整、丢了标题层级与表格结构);二是查询问题(用户口语与文档术语不匹配、指代未消解、多意图混在一句话里,需要查询改写与多轮改写);三是召回策略问题(只用向量导致专有名词匹配不上、Top-K 太小或太大、没有 rerank);四是数据问题(文档本身过期、重复、或与问题无关,知识库里根本没有答案);五是模型与拼装问题(上下文过长被截断或稀释、prompt 没要求依据资料、模型能力不足)。每一类都要给对应的验证手段:人工看召回片段是否包含答案(区分「召回错」与「生成错」),这是最关键的分界线——召回里有正确片段但答案错了是生成问题,召回里根本没有就是检索问题。提改进时也给优先级:先修数据与切分,再做查询改写与混合召回,最后才是换模型。
搭一个 AI 质量检测平台:链路怎么设计
这题是开放设计,按四层答最稳。第一层是数据集与用例管理:维护版本化的评测集(问答对、多轮对话、工具调用任务),覆盖正常、边界与对抗样本,能按业务场景切片;数据集要有来源与标注人,避免只用模型自己生成的数据评自己。第二层是评测执行:离线批量评测(跑完整数据集,算准确率、召回率、引用正确性、拒答率、格式合规率)与在线抽样评测(对真实流量按比例采样打分);打分方式三种结合——规则与程序判定(格式、引用是否可点击、是否命中关键词)、模型评审(LLM as Judge,按维度打分并给理由,要注意位置偏差与长度偏差,成对比较要交换顺序)、人工抽检(作为基准校准前两者)。第三层是监控与告警:把质量指标、延迟、成本、工具调用失败率、无答案率接进监控面板,按版本与模型维度切分,跌出阈值就告警——原帖面试官提到的「可观测性」正是这一层,也是生产上最容易被忽略的一层。第四层是发布门禁与回流:上线前跑回归,质量不达标不允许发布(或只做小流量灰度);线上发现的 badcase 回流到评测集,形成「发现—修复—回归」的闭环;同时保留 trace 让每次回答能追溯到用了哪份资料、哪个模型版本、哪次检索,出问题能定位到环节。答题时把「离线评测 + 在线监控 + 人工基准 + 发布门禁」这四件事说出来,结构就完整了。