启云方 AI 应用开发 一面:56 题连问,从 HashMap 到 Agent 中控
- 轮次
- 一面
- 时间
- 2026-10
- 来源
- 牛客网
《面试题目》
自我介绍与项目
- 请做一下自我介绍。
- 介绍两段实习的主要工作内容和成果。
- 介绍一下内容咨询平台项目。
- 这个项目为什么用 Redis ZSet 做延迟队列?为什么不用 Kafka?
- 延迟队列的补偿机制怎么做的?如何保证消息消费与业务状态一致?
- 介绍一下你的全栈 AI 项目或 Agent 项目。
算法
- 复盘一下笔试这道题,当时思路是什么?
- 为什么没有 AC?可能漏了哪些边界情况?
- 现在再想,你会怎么改进?是否考虑背包 DP?
Java 基础与集合
- Java 基础数据结构与集合框架有哪些?
- List、Set、Map 的区别和常见实现类?
- ArrayList 和 LinkedList 的区别?
- HashMap、TreeMap、ConcurrentHashMap 的区别和使用场景?
- HashSet 和 HashMap 的关系?HashSet 如何去重?
- 什么是哈希冲突?
- HashMap 的 put 流程是什么?
- 为什么链表长度到 8 且数组容量到 64 要转红黑树?
- HashMap 的 get 流程?为什么重写 equals 要重写 hashCode?
- ConcurrentHashMap 如何保证线程安全?读操作为什么不加锁?
- CAS 是什么?有什么问题?ABA 问题怎么解决?
- synchronized 锁升级过程是怎样的?
- synchronized 和 ReentrantLock 的区别?
- AQS 是什么?ReentrantLock 如何基于 AQS 实现公平与非公平锁?
Redis
- Redis 缓存击穿是什么?怎么解决?
- Redis 单线程为什么性能高?
- Redis 过期键删除策略有哪些?
- Redis ZSet 底层结构了解吗?
MySQL
- MySQL 事务隔离级别有哪些?默认是什么?分别解决什么问题?
- 可重复读如何解决幻读?MVCC 和间隙锁的作用?
- 如何排查索引失效?执行计划重点看哪些字段?
- 什么是 N+1 查询?怎么优化?
Kafka
- Kafka 的特点和适用场景?
- Kafka 如何保证消息不丢?
- Kafka 如何避免重复消费?业务幂等怎么做?
- Kafka 如何保证消息顺序消费?
- Kafka 消息堆积怎么排查和解决?
- Kafka 本身如何避免重复发消息与重复消费?
线程池
- Java 创建线程的方式有哪些?
- 线程池的核心参数有哪些?执行流程是什么?
- 线程池 submit 和 execute 的区别?任务异常怎么处理?
LLM 与 Agent
- LLM 和 Agent 的区别?Agent 的核心组成是什么?
- function calling、skills、MCP 分别是什么?有什么区别?
- Agent 的记忆和上下文管理怎么设计?上下文压缩怎么做?
- SSE 和 WebSocket 的区别?
- 中控智能体怎么使用 OpenClaw 或 OpenCloud?如何控制权限风险?
- 智能体会话隔离怎么实现?
- 中控智能体和驾校数字人的角色、记忆有什么区别?
- 为什么要把开发规范、全局配置注入给模型?举个例子。
- 你平时怎么用 AI 辅助开发?完整流程是什么?
- AI 编码后怎么做 code review 和验收?
- RAG 的知识库入库和检索流程是什么?
- 了解混合检索、多路召回吗?
- 生产可用的 Agent 需要做哪些安全设计?
- 如果用户一句话同时涉及两个任务,怎么处理?
- 智能体高并发或上游 API 波动导致不可用,怎么设计降级与限流?
- 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 的安全设计要覆盖五面:权限(最小权限、按工具而非按人授权、危险操作二次确认)、隔离(每个会话独立工作目录与上下文,禁止跨会话读数据)、输入(提示注入防护,外部内容不被当指令)、输出(敏感信息脱敏、代码执行沙箱)、可观测(每次工具调用留痕、成本与步数上限、失败可回放)。高并发与上游波动的应对是:队列 + 并发上限 + 超时 + 熔断 + 降级到更小模型或纯检索答案 + 幂等重试。
一句话两个任务的处理方式:先做意图拆分(让模型输出结构化的任务列表),再判断依赖关系——无依赖的并行执行,有依赖的串行编排;执行完做结果合并。关键是给每个子任务独立的上下文与预算,避免一个任务失败拖垮全部,并在回复里说清哪部分完成、哪部分没完成。