面灵AI→

启云方 AI 应用开发 一面:56 题连问,从 HashMap 到 Agent 中控

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

《面试题目》

自我介绍与项目

  1. 请做一下自我介绍。
  2. 介绍两段实习的主要工作内容和成果。
  3. 介绍一下内容咨询平台项目。
  4. 这个项目为什么用 Redis ZSet 做延迟队列?为什么不用 Kafka?
  5. 延迟队列的补偿机制怎么做的?如何保证消息消费与业务状态一致?
  6. 介绍一下你的全栈 AI 项目或 Agent 项目。

算法

  1. 复盘一下笔试这道题,当时思路是什么?
  2. 为什么没有 AC?可能漏了哪些边界情况?
  3. 现在再想,你会怎么改进?是否考虑背包 DP?

Java 基础与集合

  1. Java 基础数据结构与集合框架有哪些?
  2. List、Set、Map 的区别和常见实现类?
  3. ArrayList 和 LinkedList 的区别?
  4. HashMap、TreeMap、ConcurrentHashMap 的区别和使用场景?
  5. HashSet 和 HashMap 的关系?HashSet 如何去重?
  6. 什么是哈希冲突?
  7. HashMap 的 put 流程是什么?
  8. 为什么链表长度到 8 且数组容量到 64 要转红黑树?
  9. HashMap 的 get 流程?为什么重写 equals 要重写 hashCode?
  10. ConcurrentHashMap 如何保证线程安全?读操作为什么不加锁?
  11. CAS 是什么?有什么问题?ABA 问题怎么解决?
  12. synchronized 锁升级过程是怎样的?
  13. synchronized 和 ReentrantLock 的区别?
  14. AQS 是什么?ReentrantLock 如何基于 AQS 实现公平与非公平锁?

Redis

  1. Redis 缓存击穿是什么?怎么解决?
  2. Redis 单线程为什么性能高?
  3. Redis 过期键删除策略有哪些?
  4. Redis ZSet 底层结构了解吗?

MySQL

  1. MySQL 事务隔离级别有哪些?默认是什么?分别解决什么问题?
  2. 可重复读如何解决幻读?MVCC 和间隙锁的作用?
  3. 如何排查索引失效?执行计划重点看哪些字段?
  4. 什么是 N+1 查询?怎么优化?

Kafka

  1. Kafka 的特点和适用场景?
  2. Kafka 如何保证消息不丢?
  3. Kafka 如何避免重复消费?业务幂等怎么做?
  4. Kafka 如何保证消息顺序消费?
  5. Kafka 消息堆积怎么排查和解决?
  6. Kafka 本身如何避免重复发消息与重复消费?

线程池

  1. Java 创建线程的方式有哪些?
  2. 线程池的核心参数有哪些?执行流程是什么?
  3. 线程池 submit 和 execute 的区别?任务异常怎么处理?

LLM 与 Agent

  1. LLM 和 Agent 的区别?Agent 的核心组成是什么?
  2. function calling、skills、MCP 分别是什么?有什么区别?
  3. Agent 的记忆和上下文管理怎么设计?上下文压缩怎么做?
  4. SSE 和 WebSocket 的区别?
  5. 中控智能体怎么使用 OpenClaw 或 OpenCloud?如何控制权限风险?
  6. 智能体会话隔离怎么实现?
  7. 中控智能体和驾校数字人的角色、记忆有什么区别?
  8. 为什么要把开发规范、全局配置注入给模型?举个例子。
  9. 你平时怎么用 AI 辅助开发?完整流程是什么?
  10. AI 编码后怎么做 code review 和验收?
  11. RAG 的知识库入库和检索流程是什么?
  12. 了解混合检索、多路召回吗?
  13. 生产可用的 Agent 需要做哪些安全设计?
  14. 如果用户一句话同时涉及两个任务,怎么处理?
  15. 智能体高并发或上游 API 波动导致不可用,怎么设计降级与限流?
  16. Agent 出问题怎么通过日志排查?

《参考解析》

用 ZSet 做延迟队列的本质是「把到期时间当 score」:投递时 ZADD delay_set <executeAt> <taskId>,消费端用一个轮询线程 ZRANGEBYSCORE ... 0 now LIMIT 0 N 取出到期任务,取到后用 Lua 或 ZREM 的返回值做原子抢占,保证多实例下只有一个消费者执行。它的优势是实现简单、延迟精度由轮询间隔决定、能按时间排序;代价是轮询有开销、任务多时占内存、没有 Kafka 那样的持久化与回溯能力。选它的场景通常是「延迟精度要求不高、任务量中等、已经依赖 Redis」;如果要强可靠、要重放、要削峰,就该上 Kafka 或专门的时间轮/延迟消息中间件。

延迟队列的一致性靠「状态机 + 补偿」:任务要有明确状态(待投递、已投递、已消费、失败),消费端先落库改状态再执行业务,用业务唯一键做幂等;执行失败的进重试队列并指数退避,超过上限进死信并告警;还要有一个扫描任务,定期把「已投递但长时间未消费」的任务重新投出去。只靠 ZSet 单点做不到这点,补偿与对账才是可靠性的主力。

HashMap 的两个常考细节:put 流程是「算 hash(高 16 位异或低 16 位扰动)→ 定位桶 → 桶空直接放 → 桶是链表则遍历比较 hash 与 equals,尾插 → 链表长度到 8 且数组长度到 64 转红黑树(否则先扩容)→ 元素数超阈值扩容 rehash」。为什么是 8 和 64:8 是在泊松分布下链表长度达到 8 的概率极低(约千万分之六),到时说明哈希质量或容量有问题,转红黑树把查询从 O(n) 降到 O(log n);容量到 64 之前优先扩容,是因为扩容能真正打散冲突,比转树更划算。

ConcurrentHashMap 读不加锁靠的是 volatile + 节点不可变:JDK 8 用「CAS 插入空桶 + synchronized 锁桶首节点」写,读操作只读 volatile 的 table 与节点的 val/next,不加锁;扩容用 ForwardingNode 做多线程协同迁移。所以读是弱一致的——能看到已经完成的写,但不保证看到正在进行的写,这在绝大多数缓存/计数场景可接受。

缓存击穿的三种解法要分清场景:击穿是「热点 key 过期瞬间大量请求打到库」,解法是互斥重建(分布式锁,只让一个请求回源,其余等待)、逻辑过期(不设 TTL,异步刷新)、或热点永不过期 + 后台更新;穿透是「查不存在的数据」,用布隆过滤器或空值缓存;雪崩是「大量 key 同时过期或 Redis 挂了」,用过期的随机抖动、多级缓存、熔断降级。

Kafka 的「不丢」要三段都成立:生产端 acks=all + 重试 + 幂等 producer; broker 端 replication.factor >= 3、min.insync.replicas >= 2、关闭 unclean.leader.election;消费端关闭自动提交、处理完再手动提交 offset。重复消费无法完全避免(至少一次语义),所以业务侧必须幂等:用业务唯一键 + 唯一索引/去重表,或状态机校验(已处理过的状态直接跳过)。

线程池的执行流程与异常处理:核心线程未满则新建核心线程;满了进队列;队列满则扩到最大线程数;再满就走拒绝策略(Abort、CallerRuns、Discard、DiscardOldest,或自定义)。execute 抛出的异常会被未捕获异常处理器接住并让线程退出重建;submit 把异常包在 Future 里,不调用 get() 就永远看不到——这是线上「任务静默失败」的常见原因,所以要么用 execute,要么在任务内部 try/catch 并打日志。

Agent 与 LLM 的区别在「有没有行动闭环」:LLM 是「输入文本输出文本」,Agent 在模型外面加了目标、规划、工具调用、记忆和终止条件,能读文件、调接口、改状态并根据结果调整下一步。核心组成可以概括为:模型(决策)、工具(行动)、记忆(状态)、编排循环(何时继续、何时停)、以及护栏(权限、预算、审计)。

function calling、skills、MCP 是三层的不同东西:function calling 是模型输出结构化调用意图的协议能力;skills 是把「一类任务的步骤与知识」封装成可复用的能力包(提示词 + 脚本 + 资源);MCP 是把外部工具与数据源以统一协议暴露给任意客户端的标准接口。前者是模型能力,中者是任务封装,后者是集成协议。

生产级 Agent 的安全设计要覆盖五面:权限(最小权限、按工具而非按人授权、危险操作二次确认)、隔离(每个会话独立工作目录与上下文,禁止跨会话读数据)、输入(提示注入防护,外部内容不被当指令)、输出(敏感信息脱敏、代码执行沙箱)、可观测(每次工具调用留痕、成本与步数上限、失败可回放)。高并发与上游波动的应对是:队列 + 并发上限 + 超时 + 熔断 + 降级到更小模型或纯检索答案 + 幂等重试。

一句话两个任务的处理方式:先做意图拆分(让模型输出结构化的任务列表),再判断依赖关系——无依赖的并行执行,有依赖的串行编排;执行完做结果合并。关键是给每个子任务独立的上下文与预算,避免一个任务失败拖垮全部,并在回复里说清哪部分完成、哪部分没完成。