面灵AI→

腾讯云 AI 应用一面:性能优化、Redis Cluster 与上下文体系

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

《面试题目》

  1. 先做一下自我介绍。几段实习分别做了多久?哪段实习对你的帮助最大?
  2. 介绍一下第二个实习中让你收获最大的性能优化。你是怎么定位到这个性能瓶颈的?
  3. 把同步调用改成异步或并发执行,需要注意哪些问题?
  4. 线程池的工作原理是什么?它对这次优化有什么帮助?
  5. Future 里的任务报错了会怎样?调用线程怎么拿到异常,工作线程又会怎样?
  6. 这个项目里,哪些工作是你独立完成的?
  7. Linux 中进程和线程是什么关系?
  8. 了解 Java 虚拟线程吗?底层是怎么实现的?
  9. 反射能做什么?如果手里只有接口或抽象类类型的引用,怎么找到对象的实际类型和字段?
  10. Redis Lua 一般用在什么场景?脚本执行中途失败能回滚吗?
  11. 在 Redis Cluster 里,Lua 涉及多个 Key 时有什么限制?怎么解决?
  12. Redis Cluster 怎么分片?客户端第一次访问一个 Key 时,怎么找到它对应的 Master?
  13. Cluster 中 Master 挂掉后,新的 Master 是怎么选出来的?
  14. 如果是网络问题导致心跳异常、误判节点下线,怎么改进检测?
  15. 介绍一下第一个实习项目的背景,以及你在里面最有收获的一项工作。
  16. 你们的上下文体系具体是怎么设计的?
  17. 怎么评测这套上下文方案有没有效果?数据和标注怎么做?
  18. 改动上线后怎么判断它真的有效?做 A/B 或灰度时会看什么?
  19. 新版本发布时,正在运行的 Agent Loop 怎么办?如果新旧版本的索引或缓存不兼容,又怎么处理?
  20. Agent Loop 或沙箱的生命周期是什么样的?
  21. 平时会怎么用 AI 编程工具完成一个需求?
  22. 怎么保证 AI 生成的代码可靠?单元测试和端到端测试会怎么做?

《参考解析》

性能瓶颈怎么定位:顺序是”先量化再归因”。① 打点——方法级耗时埋点或 APM,找出 P99 高的那一段;② 区分类型——是 CPU 密集(火焰图看热点)、I/O 等待(线程栈大量处于 socket/DB 调用)还是锁竞争(jstack 看 BLOCKED);③ 确认是单次变慢还是并发上不去(前者优化算法与 I/O,后者解决串行化与资源池过小);④ 改造后用同一套压测对比。

同步改异步要注意什么:① 异常不能再靠 try/catch 就地捕获,必须通过 Future.get() 或回调拿到,否则会静默丢失;② 上下文传递——ThreadLocal、MDC、事务、安全上下文都不会自动传到工作线程;③ 顺序与依赖——原本顺序执行隐含的先后关系,异步后要显式表达;④ 超时与取消——必须给每个异步任务设超时,否则一个慢任务会拖住整个聚合;⑤ 资源隔离——每个下游独立线程池,避免一个慢下游占满公共池;⑥ 线程池打满后的拒绝策略与降级。

Future 的异常语义:任务抛出的异常会被捕获并包装进 Future,调用 get() 时以 ExecutionException 抛出(原始异常在 cause 里);如果从不调用 get(),异常就悄无声息。工作线程不会因为异常而终止(线程池会捕获并继续跑下一个任务),但 submit 提交的 Runnable 同理需要 get() 才能看到异常,而 execute 提交的任务异常会直接抛到线程的未捕获异常处理器。最佳实践是用 CompletableFuture 的 exceptionally/handle 显式处理,或 invokeAll 后逐个检查。

Linux 进程与线程:Linux 内核里线程本质上也是任务(task),与进程共用 task_struct,区别在于是否共享地址空间、文件描述符表、信号处理等资源。线程共享同一地址空间,所以通信靠共享内存(但要同步),切换开销小;进程相互隔离,通信要用 IPC。clone() 系统调用通过参数决定共享哪些资源,fork() 相当于全不共享,pthread_create 相当于共享地址空间。

Java 虚拟线程:JDK 21 正式引入,是 JVM 层面的轻量线程,由 ForkJoinPool 式的调度器把大量虚拟线程复用到少量平台线程(载体线程)上。关键机制是** continuation**:虚拟线程阻塞在可感知的阻塞点(Thread.sleep、Socket I/O、ReentrantLock)时,会把栈帧拷贝到堆上并让出载体线程,恢复时再拷回来,所以阻塞不再占用 OS 线程。限制是被 synchronized 块或 native/JNI 调用钉住时会 pin 住载体线程,另外不要池化虚拟线程(它们本来就该一个任务一个)。

Redis Lua 与 Cluster:Lua 用于需要原子性的复合操作(判断后写入、限流、分布式锁的加解锁),因为 Redis 单线程执行脚本期间不会插入其他命令。但脚本中途报错不会回滚已执行的写命令,全部命令要么都成功执行、要么在错误处停止,前面的已经生效——所以脚本要写成要么不做、要么做完,避免”写一半”。在 Cluster 下,脚本涉及的所有 Key 必须在同一个 slot(同一个节点),否则报 CROSSSLOT 错误;解决办法是用 hash tag({user}:1 和 {user}:2 强制同 slot)、或者把逻辑拆成多个单 Key 脚本再在上层协调。

Redis Cluster 分片与寻址:把键空间划成 16384 个 slot,slot = CRC16(key) mod 16384,每个 master 负责一部分。客户端启动时拿到一份 slot 到节点的映射缓存到本地,计算 slot 后直连对应 master;如果访问的节点不负责该 slot,会返回 MOVED 重定向(永久,客户端应更新映射)或 ASK(临时迁移中,只本次重定向)。所以”第一次访问”并不需要广播或代理,本地映射就能定位。

故障转移与误判:Master 挂掉时,从节点发起选举——只有持有最新复制偏移、且与主节点失联时间超过阈值(cluster-node-timeout)的从节点才可参选,获得多数 master 投票后晋升并从旧 master 的槽位接管。心跳误判的常见原因是网络抖动或主线程被慢命令(大 key、keys *、大 Lua 脚本)阻塞导致心跳超时。改进手段是调大 cluster-node-timeout、避免慢命令与阻塞型操作、把探测放到独立连接、以及用 gossip 的间接判定(多个节点一致认为下线才更可信)。

上下文体系怎么评测:先定义指标——任务成功率、关键信息召回率(回答是否用到该用的历史/证据)、无依据回答比例、平均上下文体量、每轮 Token 成本、P95 延迟。数据靠人工标注一批”必须知道 X 才能答对”的任务,构造正例与反例(有 X / 没有 X / X 被压缩掉)看模型表现差异。上线后做 A/B 或灰度:对照组用旧上下文策略、实验组用新策略,看任务成功率、成本与延迟三项的联合变化,避免只看单一指标。

版本发布时正在跑的 Agent Loop:不能粗暴重启。做法是版本化——索引与缓存带上版本号,新版本构建到新命名空间(蓝绿或影子索引),已启动的 Loop 记录自己绑定的版本继续用旧资源直到结束,新建的 Loop 才用新版本;旧版本资源在确认没有活跃 Loop 后延迟回收。不兼容时宁可让旧 Loop 用旧索引跑完,也不要中途切换导致上下文语义断裂。同时要用 feature flag 控制回滚。

沙箱与 Loop 生命周期:通常按任务创建——创建 → 初始化(工作目录、依赖、凭证)→ 运行 → 空闲检测 → 销毁。为了复用会做池化:空闲超时回收、活跃则续期,容量按并发设上限。关键设计点是销毁时的资源回收与状态持久化:工作区文件要么挂载到持久卷、要么在销毁前导出,否则任务中断后无法续跑。

AI 编程质量保障:需求给它说清上下文与边界(目录结构、数据模型、不要新增依赖、不要改公共接口),产出必须过确定性检查(编译、类型检查、lint),关键路径人工 review 看边界与异常,测试上先补单测锁住行为再做修改,端到端测试覆盖真实链路。核心原则是”用机器能验证的标准兜底,人只看机器看不出来的部分”。