1. 介绍一下在竞赛/实习中间遇到的问题,哪些问题是你认为困难的?又是怎么解决的?
2. 在项目中为什么选择 Agent?为什么不直接调用大模型?使用 Agent 解决了哪些问题?
在项目开发中,选择 Agent 而非直接调用大模型主要基于以下几点考虑:
- 决策能力增强:Agent 能够根据环境反馈自主做出决策,形成闭环交互,而大模型通常是单次调用的开环系统。
- 复杂任务分解:Agent 可以将复杂任务拆解为多个子任务,逐步执行并整合结果,这是大模型原生能力所不具备的。
- 工具使用能力:Agent 能够调用外部工具(如数据库查询、文件操作等),弥补了大模型在实时数据获取和操作能力上的不足。
使用 Agent 主要解决了以下问题:
- 提升任务完成的自动化程度
- 增强系统对动态环境的适应性
- 实现更复杂的业务逻辑处理
3. Mybatis 的实现原理是什么?Mybatis 如何实现字段映射?
Mybatis 的核心实现原理:
- 配置加载:读取 XML 配置文件或注解信息,构建 Configuration 对象
- SQL 解析:将 SQL 语句中的参数占位符替换为实际参数值
- 执行器:通过 Executor 执行 SQL 语句
- 结果映射:将查询结果映射为 Java 对象
字段映射实现方式:
- 自动映射:基于字段名和属性名的默认映射规则
- 手动映射:通过
<resultMap>
标签或@Results
注解显式定义映射关系 - 驼峰映射:开启
mapUnderscoreToCamelCase
配置实现下划线到驼峰的自动转换
4. 什么是数据库范式?
数据库范式是为了避免数据冗余和更新异常而设计的数据库设计规范,常见的范式包括:
- 第一范式 (1NF):字段具有原子性,不可再分
- 第二范式 (2NF):满足 1NF 且非主属性完全依赖于主键
- 第三范式 (3NF):满足 2NF 且非主属性之间无传递依赖
- BCNF:每一个非平凡函数依赖的左边必须包含候选键
- 第四范式 (4NF):消除多值依赖
- 第五范式 (5NF):消除连接依赖
遵循范式设计可以减少数据冗余,但可能增加查询复杂度,实际应用中需要根据业务场景权衡。
5. 优化 sql 的方法有哪些(提到冗余字段减少多表 join,使用冗余字段会产生哪些问题?)
SQL 优化方法:
- 索引优化:合理添加索引提高查询速度
- 查询优化:避免全表扫描,减少子查询
- 架构优化:分库分表、读写分离
- 冗余字段:通过增加字段减少多表 JOIN
使用冗余字段的潜在问题:
- 数据一致性问题:更新时需要同步多个表的数据
- 空间浪费:增加了数据存储量
- 维护成本:业务逻辑变更时需要修改多个地方
- 事务复杂性:需要保证多字段更新的原子性
6. 数据库索引的底层数据结构是什么?为什么使用 B + 树作为索引的数据结构?B + 树怎么应用于最左前缀匹配?
数据库索引的底层数据结构主要是 B + 树。选择 B + 树的原因:
- 磁盘 IO 优化:B + 树的高度通常较小,减少了磁盘 IO 次数
- 范围查询高效:叶子节点形成有序链表,便于范围查询
- 查询效率稳定:所有查询都要到叶子节点,查询效率相当
最左前缀匹配原理:
B + 树索引会按照索引字段的顺序依次建立,例如复合索引 (a,b,c) 会先按 a 排序,a 相同的情况下按 b 排序,以此类推。因此查询条件中如果有 a 字段,就可以利用该索引加速查询;如果没有 a 字段,则无法使用该索引。
7. 什么是 Redis 的缓存穿透?如何解决缓存穿透?为什么会出现缓存穿透?
缓存穿透是指查询一个不存在的数据,由于缓存中没有该数据,每次请求都会穿透到数据库。
解决方法:
- 空值缓存:对不存在的数据也缓存一个空值并设置较短过期时间
- 布隆过滤器:在访问缓存前通过布隆过滤器判断数据是否存在
- 接口校验:对请求参数进行合法性校验,过滤非法请求
缓存穿透产生的原因:
- 恶意攻击,故意请求不存在的数据
- 业务数据异常,查询条件错误
- 缓存与数据库数据不同步
8. 如何解决 Redis 和数据库的一致性问题?
解决 Redis 和数据库一致性的常见方案:
- Cache-Aside Pattern:应用先操作数据库,再操作缓存(删除或更新)
- Read/Write Through:应用只操作缓存,由缓存系统同步更新到数据库
- Write Behind Caching:写操作只更新缓存,由后台线程异步更新数据库
- 重试机制:操作失败时将任务放入队列进行重试
- 最终一致性:允许短时间内的不一致,通过异步机制保证最终一致
需要根据业务场景选择合适的方案,例如对一致性要求高的场景可采用 Cache-Aside 并结合重试机制。
9. jvm 单机锁是否适用于分布式场景?为什么?那该怎么解决?
JVM 单机锁(如 synchronized、ReentrantLock)不适用于分布式场景,原因如下:
- 单机锁只在单个 JVM 进程内有效
- 分布式系统中多个服务实例无法共享同一个锁对象
- 单机锁无法跨进程实现互斥访问
解决分布式锁的方案:
- 基于数据库:利用数据库的唯一性约束或行锁
- 基于 Redis:使用 SETNX 命令或 Redisson 等框架
- 基于 ZooKeeper:利用临时有序节点实现分布式锁
- 基于 Etcd:利用其分布式一致性特性实现锁机制
10. 说说 Redisson 实现分布式锁的方法
Redisson 实现分布式锁的核心原理:
- 可重入锁:通过 Redis 的 Hash 结构记录锁的持有者和重入次数
- 看门狗机制:自动延长锁的过期时间,防止业务执行时间过长导致锁提前释放
- 公平锁:通过 Redis 的列表结构实现请求的排队机制
- 联锁:多个锁的原子操作,确保多个资源的一致性
- 红锁:多个 Redis 节点的锁,提高可用性
Redisson 的分布式锁实现了 JUC 的 Lock 接口,使用方式与单机锁类似,但底层通过 Redis 实现了跨进程的互斥访问。
11. 哈希表的底层结构是什么?put 方法的实现原理是什么?
哈希表的底层结构通常是数组 + 链表(或红黑树)。以 Java 的 HashMap 为例:
- 哈希函数:通过 key 的 hashCode 计算数组索引位置
- 冲突解决:当发生哈希冲突时,采用链表或红黑树存储多个元素
- 扩容机制:当元素数量超过阈值时,数组扩容为原来的 2 倍
put 方法的实现步骤:
- 计算 key 的哈希值
- 确定数组索引位置
- 如果该位置为空,直接插入新节点
- 如果该位置已有节点,遍历链表(或红黑树):
- 若找到相同 key 的节点,则更新其值
- 若未找到,则插入新节点(链表尾部或红黑树)
- 插入后检查是否需要扩容
12. 手撕代码:反转链表
以下是反转链表的 Java 实现:
public class ListNode {int val;ListNode next;ListNode(int x) { val = x; }
}public ListNode reverseList(ListNode head) {ListNode prev = null;ListNode curr = head;while (curr != null) {ListNode nextTemp = curr.next;curr.next = prev;prev = curr;curr = nextTemp;}return prev;
}
该算法的时间复杂度为 O (n),空间复杂度为 O (1),通过迭代方式依次反转每个节点的指针方向。