AI 应用开发二面面经
完整信息
本篇目录
投的是 AI 应用开发,进去先被问了 20 分钟线程池。
面试官很直接,说我们这边的 AI 应用是要接线上流量的,你连线程池都配不明白,Agent 做得再花也没用。
体感,越叫 AI 的岗位,越考基本功。壳子做厚了,模型才能随便换。
一、项目
- 自我介绍,三分钟以内
- 挑一个你最熟的项目,讲清楚为什么要做
- 这个功能的难点在哪,别说"对接了大模型"
- 你个人写的是哪部分,多少行
- 上线之后 QPS 多少,P99 多少
- 出过什么故障,怎么恢复的
第 3 题是个筛子。答"要对接大模型接口"说明你没做过难的,答"要保证多轮上下文里的工具调用不重复执行"才像人话。
二、并发这一块
- 线程池的核心参数怎么配,别从业务讲,从技术讲
- 核心线程数和最大线程数你按什么定
- 队列用哪种,为什么不用无界队列
- 什么情况下会 OOM,怎么提前发现
- 拒绝策略你选哪个,为什么
- 为什么不直接用 Executors 那几个工厂方法
- 线程池里的异常去哪了,submit 和 execute 有什么区别
- 讲一下 Java 的内存模型
- volatile 能保证原子性吗
7 题要报数。调模型是 IO 密集,核心线程按 QPS×平均耗时估,比如 200 QPS、平均 300ms,理论并发 60,核心给 64,最大给 128,队列给 200 且有界。他追问为什么最大是核心的两倍,我说留一点突发余量,再高就该限流了,线程不是越多越好。
9 题必须答有界。无界队列的后果是任务堆积,堆内存慢慢涨,最后 Full GC 都救不回来,表现是接口越来越慢然后突然全挂。
12 题别只说"阿里不让"。newFixedThreadPool 的队列是无界的,newCachedThreadPool 的最大线程数是 Integer.MAX_VALUE,两个都可能把机器打死。
13 题是细节题。submit 会把异常包在 Future 里,不调 get 你永远看不到;execute 直接抛到 UncaughtExceptionHandler。
三、中间件
- Redis 的过期删除策略有哪些,为什么不全用惰性删除
- 缓存和数据库一致性,你先更新谁
- 缓存击穿、穿透、雪崩分别怎么防
- 分布式锁用 Redis 怎么实现,会有什么问题
- 消息队列重复消费怎么处理
- 顺序消息怎么保证
16 题要提一句:惰性删除省 CPU 但会漏内存,定期删除是折中,采样随机 key,过期比例超过 25% 就继续扫。只答一个的都是背的。
19 题要提锁续期和误删。SETNX 加过期时间,值放唯一 id,删的时候用 Lua 比对,不然可能删掉别人的锁。
四、回到 AI
- Agent 的长任务跑了 15 分钟,平时 5 分钟,你怎么查
- 工具调用超时,重试几次合适,重试要不要换工具
- 多轮对话上下文怎么存,存 Redis 还是存库
- 上下文压缩你是在压缩什么,丢了什么
- 如果模型输出格式坏了,你怎么兜
- 反问
22 题我答的是先看 trace,把一次任务拆成模型推理、工具调用、编排三段,看哪段涨了。涨在工具就查下游,涨在推理就看是不是上下文变长了导致首 token 变慢。
手撕:带退避和幂等的重试器
实现一个 retry,最多 4 次,指数退避加随机抖动,只对可重试异常生效,同一个业务 key 不能重复执行。
public class Retrier {
private final Set<String> done = ConcurrentHashMap.newKeySet();
public <T> T run(String key, Callable<T> task, T fallback) {
if (done.contains(key)) return fallback;
long base = 100L;
for (int i = 0; i < 4; i++) {
try {
T r = task.call();
done.add(key);
return r;
} catch (RetryableException e) {
long sleep = base * (1L << i) + ThreadLocalRandom.current().nextLong(base);
try { Thread.sleep(sleep); } catch (InterruptedException ie) {
Thread.currentThread().interrupt();
return fallback;
}
} catch (Exception e) {
return fallback; // 不可重试,直接兜底
}
}
return fallback;
}
}
坑:抖动不能省,不加抖动一千个请求会在同一毫秒一起重试,把下游再打挂一次。InterruptedException 必须恢复中断位再退出,不然上层收不到停止信号。幂等判断只能挡"已经成功过"的重复,挡不住并发进来两次,真并发要去重得加锁或者用状态机。还有 done 放内存,多实例部署等于没有,得挪到 Redis。
他看完问,如果 fallback 也失败呢。
我说那就抛出去,让上层决定是降级还是排队,重试器不该吞掉所有错误。
他说,行,这个回答比刚才那个线程池的强。
—— AIGC小白入门记
本篇目录
当前问题0128
- 01
自我介绍,三分钟以内
- 02
挑一个你最熟的项目,讲清楚为什么要做
- 03
这个功能的难点在哪,别说"对接了大模型"
- 04
你个人写的是哪部分,多少行
- 05
上线之后 QPS 多少,P99 多少
- 06
出过什么故障,怎么恢复的
- 07
线程池的核心参数怎么配,别从业务讲,从技术讲
- 08
核心线程数和最大线程数你按什么定
- 09
队列用哪种,为什么不用无界队列
- 10
什么情况下会 OOM,怎么提前发现
- 11
拒绝策略你选哪个,为什么
- 12
为什么不直接用 Executors 那几个工厂方法
- 13
线程池里的异常去哪了,submit 和 execute 有什么区别
- 14
讲一下 Java 的内存模型
- 15
volatile 能保证原子性吗
- 16
Redis 的过期删除策略有哪些,为什么不全用惰性删除
- 17
缓存和数据库一致性,你先更新谁
- 18
缓存击穿、穿透、雪崩分别怎么防
- 19
分布式锁用 Redis 怎么实现,会有什么问题
- 20
消息队列重复消费怎么处理
- 21
顺序消息怎么保证
- 22
Agent 的长任务跑了 15 分钟,平时 5 分钟,你怎么查
- 23
工具调用超时,重试几次合适,重试要不要换工具
- 24
多轮对话上下文怎么存,存 Redis 还是存库
- 25
上下文压缩你是在压缩什么,丢了什么
- 26
如果模型输出格式坏了,你怎么兜
- 27
实现一个 `retry`,最多 4 次,指数退避加随机抖动,只对可重试异常生效,同一个业务 key 不能重复执行。
- 28
如果 fallback 也失败呢。
