鸣鸣很忙 后端开发管培生 一面
- 轮次
- 一面
- 时间
- 2026-10
- 来源
- 牛客网
《面试题目》
- 自我介绍
- 医疗意图识别为什么不直接用大模型,而是用三层架构判断所有意图?
- 三层架构每一层的具体作用是什么,为什么分这三级?
- 准确率和仲裁调用率是怎么测出来的?
- 评测集的容量是不是有点小,这样测出来的数据准吗?
- 如果评测集数量小,怎么保证测试结果准确?
- 评测集数量有限,能用什么手段降低样本有限导致的偏差?
- 这个东西上线之后效果不好,怎么进一步优化?
- 0.6B 和 8B 模型的「纠缠度」指标是什么意思?
- 为什么要基于纠缠度对比模型差异,有没有其他评判标准?
- 介绍下 LDC 架构
- 为什么用 CountDownLatch 做缓存并发写入,有没有其他方式?最终为什么确定用它?
- 多机房写入时如果有机器写入失败或超时怎么处理?
- 写入失败是完全依赖上游系统重新写来保证一致性吗?
- 你们线程池的核心参数是怎么定的?
- 如果扫码确认的请求量突增,线程打满怎么应对?
- 如果发现线程池参数定得不对,出了问题怎么处理?
- 简历提到的事务消息,什么是事务消息,为什么它能解决这个问题?
- 幂等框架的幂等 ID 为什么超过 64 字节才降级 MD5?
- 既然用 MD5,怎么保证不会因为碰撞导致误拦截?
- 为什么要用策略模式回查,直接写 if/else 不行吗?策略模式的优势是什么?
- 支付场景里订单状态更新如何避免脏读、幻读,用 MySQL 知识解决?
- 缓存的击穿、穿透、雪崩分别是什么?
- 扫码付款场景下什么时候会发生击穿、穿透、雪崩,怎么处理?
- 如果让你重新设计简历上的两个项目,你会怎么优化?说三个地方的优化思路
- synchronized、volatile、ThreadLocal 三者的区别是什么,分别适用哪些场景?
- 怎么解决 ThreadLocal 的内存泄漏?
- ThreadLocal 的实现原理是什么,为什么可以做到线程级别的隔离?
- CAS 是什么?
- CAS 一般用在什么场景,和加锁有什么区别?
- CAS 有什么问题,怎么解决?
- Redis 实现分布式锁的流程是什么样的?
- 用 Redis 实现分布式锁,有哪些点需要注意?
- 如果分布式锁的过期时间比较短,任务还没做完锁就过期了怎么办?
- 讲一下 TCP 的三次握手、四次挥手的流程?
- TCP 三次握手为什么不是两次?如果是两次有什么问题?
- RAG 是怎么做的,分几步?
- 如果要保证好的匹配度,怎么确定文本切分 chunk 的大小?
《参考解析》
评测集小怎么办:这是全场最考验方法论的一问。评测集小(几百条以内)时,单点准确率的置信区间很宽,所以做法不是「照着数字下结论」而是降低方差:用交叉验证或自助采样(bootstrap)给出区间;分层抽样保证长尾意图都有覆盖;对边界样本做定向构造与对抗样本增强;多人标注并计算一致性(Kappa),把标注噪声暴露出来;报指标时同时报样本量,并把「离线指标」和「线上 badcase 回流」结合看。至于「上线后效果不好怎么优化」,按链路定位:意图漏识别就补分类样本与规则、仲裁率过高就扩评测集和调阈值、召回不足就改检索与切分,最后用 A/B 实验验证。
缓存并发写与多机房一致性:用 CountDownLatch 的场景是「本机房写入 + 远程机房写入并行提交,等全部完成再返回」,目的是压 RT。替代方案有 CompletableFuture.allOf(更现代、天然支持超时与异常组合)、信号量、以及异步 MQ 广播(彻底解耦但一致性变弱)。部分失败的处理要分主次:本机房写成功即认为请求成功,远程失败进异步补偿队列(重试 + 对账),并打点告警;绝不能让远程超时阻塞主链路——await 必须带超时。多机房写入的一致性通常只能做最终一致,所以要有定时对账把差异修回来。
线程池与流量突增:核心参数(核心线程数、最大线程数、队列容量、拒绝策略)要按压测与目标 RT 反推:先测单任务耗时与依赖的 RT,估算并发度;队列必须有界(无界队列会把内存吃爆,且会掩盖容量不足);拒绝策略用 CallerRunsPolicy 做天然背压或直接快速失败并返回友好提示。突增流量的应对顺序是:接入层限流(按用户 / IP / 接口维度)→ 排队与削峰(MQ)→ 降级非核心链路 → 扩容(无状态服务水平扩展)→ 缓存前置挡读。参数定错了的处理:先止血(改配置、降级、扩容),再复盘(是估算错、依赖变慢还是流量模型变了),最后把参数与容量评估写进 SOP。
事务消息与幂等:事务消息解决的是「本地事务与消息发送的原子性」——先发半消息,本地事务执行成功后提交消息,失败则回滚;Broker 定期回查本地事务状态。这样能保证「订单创建成功则消息一定发出」,消费端再用幂等键去重,整体得到最终一致。幂等 ID 与 MD5 的追问见前文思路:阈值来自线上键长分布与容量估算,MD5 的碰撞风险要用唯一约束、命名空间前缀与双写校验来兜。策略模式的优势是替换掉会持续膨胀的分支判断,代价是引入抽象层——变化维度少时 if/else 反而更清晰。
MySQL 隔离与锁:订单状态更新要防脏读 / 幻读,方案是:读已提交(RC)能防脏读但会幻读;可重复读(RR,MySQL 默认)通过 MVCC 快照防脏读,并用间隙锁 / next-key lock 在「当前读」下防幻读。业务上更常用的手段是「用锁把状态迁移串起来」:update orders set status='PAID' where id=? and status='UNPAID' 这种带前置状态的 CAS 更新(影响行数为 0 说明已被人改过),配合唯一索引与乐观锁版本号,比依赖隔离级别更直接可控。
并发基础速答:synchronized 是关键字、JVM 实现、可重入、保证可见性与原子性,适合临界区短、无超时需求的场景;volatile 只保证可见性与有序(禁止重排),不能保证原子性,适合状态标志位;ThreadLocal 是线程私有变量,底层是 ThreadLocalMap(key 是弱引用的 ThreadLocal 本体,value 强引用),所以线程不结束且不 remove 时 value 不会被回收,造成泄漏——用 try/finally 调 remove() 解决。CAS 是「比较并交换」的原子指令,靠自旋实现无锁并发,适用计数器、状态位、栈顶更新;和锁的区别是无阻塞、无上下文切换,但存在 ABA 问题(用版本号 / AtomicStampedReference 解决)、自旋开销(用自适应自旋或 LongAdder 分摊)、以及只能保证单个变量的原子性。
Redis 分布式锁:流程是 SET key value NX PX ttl(value 用唯一标识,TTL 防死锁)→ 业务执行 → 用 Lua 脚本「比对 value 再删除」保证只删自己的锁。注意点:加锁与设置过期必须原子、释放要校验持有者、锁续期(看门狗)防任务超时、以及主从切换丢锁的问题(Redlock 或改用 etcd / ZooKeeper 的强一致实现)。锁过期任务未完成的常见处理是:业务上做幂等 + 用看门狗自动续期 + 把长任务拆小;绝不能靠延长 TTL 糊过去。
TCP 与 RAG:三次握手是 SYN → SYN+ACK → ACK,目的是双方确认彼此的收发能力并同步初始序列号;不能两次的原因有两点:一是服务端无法确认客户端收到了自己的 SYN(历史连接可能带着旧序列号造成混乱),二是两次握手无法防止已失效的连接请求突然到达服务端而建立无效连接(浪费资源、可能重复处理)。四次挥手是 FIN → ACK → FIN → ACK,因为 TCP 是全双工,一方关闭后另一方可能还有数据要发,所以 ACK 与 FIN 分开,主动关闭方最后进入 TIME_WAIT 等 2MSL 保证最后的 ACK 能到达并让旧报文消散。RAG 分离线(采集解析、清洗、切分、embedding 入库、建关键词与元数据索引)与在线(query 改写、混合检索、rerank、上下文组装、生成与引用,最后评测回流)。chunk 大小按「文档结构 + 检索粒度」定:按标题层级和段落切,落在几百到一千字符量级,加 10%~20% 重叠防切断,再用评测集(召回率、答案忠实度)调到最优——没有通用最优值,只有针对语料实测出来的值。
这一轮的特点:38 个问题覆盖项目、AI、并发、MySQL、Redis、网络,是典型的「全栈式八股 + 项目深挖」高压面试。作者总结「强度比较大」名副其实。应对这类面试的方法是建立自己的知识索引:每个主题准备「是什么 → 为什么这么设计 → 边界与代价 → 我踩过的坑」四层,并且把简历上的每个技术点都往下准备两层,这样被连问时才不会卡在第一层。