快手

AI 应用开发二面面经

完整信息

28 条问法 · 26 道题

本篇目录

投的是 AI 应用开发,进去先被问了 20 分钟线程池。

面试官很直接,说我们这边的 AI 应用是要接线上流量的,你连线程池都配不明白,Agent 做得再花也没用。

体感,越叫 AI 的岗位,越考基本功。壳子做厚了,模型才能随便换。

一、项目

  1. 自我介绍,三分钟以内
  2. 挑一个你最熟的项目,讲清楚为什么要做
  3. 这个功能的难点在哪,别说"对接了大模型"
  4. 你个人写的是哪部分,多少行
  5. 上线之后 QPS 多少,P99 多少
  6. 出过什么故障,怎么恢复的

第 3 题是个筛子。答"要对接大模型接口"说明你没做过难的,答"要保证多轮上下文里的工具调用不重复执行"才像人话。

二、并发这一块

  1. 线程池的核心参数怎么配,别从业务讲,从技术讲
  2. 核心线程数和最大线程数你按什么定
  3. 队列用哪种,为什么不用无界队列
  4. 什么情况下会 OOM,怎么提前发现
  5. 拒绝策略你选哪个,为什么
  6. 为什么不直接用 Executors 那几个工厂方法
  7. 线程池里的异常去哪了,submit 和 execute 有什么区别
  8. 讲一下 Java 的内存模型
  9. 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。

三、中间件

  1. Redis 的过期删除策略有哪些,为什么不全用惰性删除
  2. 缓存和数据库一致性,你先更新谁
  3. 缓存击穿、穿透、雪崩分别怎么防
  4. 分布式锁用 Redis 怎么实现,会有什么问题
  5. 消息队列重复消费怎么处理
  6. 顺序消息怎么保证

16 题要提一句:惰性删除省 CPU 但会漏内存,定期删除是折中,采样随机 key,过期比例超过 25% 就继续扫。只答一个的都是背的。

19 题要提锁续期和误删。SETNX 加过期时间,值放唯一 id,删的时候用 Lua 比对,不然可能删掉别人的锁。

四、回到 AI

  1. Agent 的长任务跑了 15 分钟,平时 5 分钟,你怎么查
  2. 工具调用超时,重试几次合适,重试要不要换工具
  3. 多轮对话上下文怎么存,存 Redis 还是存库
  4. 上下文压缩你是在压缩什么,丢了什么
  5. 如果模型输出格式坏了,你怎么兜
  6. 反问

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小白入门记

本篇目录
  1. 01
  2. 02
  3. 03
  4. 04
  5. 05
  6. 06
  7. 07
  8. 08
  9. 09
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
  21. 21
  22. 22
  23. 23
  24. 24
  25. 25
  26. 26
  27. 27
    实现一个 `retry`,最多 4 次,指数退避加随机抖动,只对可重试异常生效,同一个业务 key 不能重复执行。
  28. 28

同作者面经

展开其余 13 篇收起其余 13 篇

相关公司