本文整理后端开发面试核心考点,涵盖 Redis、MySQL、Spring、JVM、高并发、分布式 等内容,答案详细严谨、原理清晰,适合面试复习、知识梳理与技术博客发布。
1. Redis 是多线程还是单线程的?
Redis 的线程模型需按版本与模块区分,不能简单判定为单线程或多线程:
- Redis 6.0 之前 :核心命令执行全程单线程,网络 IO、命令处理、定时器、过期键清理均由唯一主线程串行执行;仅 RDB 持久化、AOF 重写等后台任务使用子线程,不影响主线程执行。
- Redis 6.0 及以后 :引入多线程 IO 模型 ,多线程仅负责网络数据读取、协议解析、响应写回,核心命令执行依旧保持单线程,确保命令原子性与执行稳定性。
综上,Redis 采用命令执行单线程 + 网络 IO 多线程 + 后台任务多线程的混合架构,单线程执行命令是其无锁、高效、稳定的核心原因。
2. 单线程为什么快呢?
Redis 单线程仍能支撑 10W+ QPS,核心优势来源于四点:
- 完全基于内存操作
所有数据读写都在内存中完成,响应延迟达到微秒级别,完全规避磁盘 I/O 带来的毫秒级性能损耗。 - 无锁竞争与线程切换开销
多线程架构存在锁竞争、上下文切换、线程调度等额外开销;Redis 单线程天然避免这些损耗,CPU 利用率极高。 - IO 多路复用模型
基于epoll/kqueue/select实现高并发网络模型,单线程可同时监听海量客户端连接,只处理活跃请求,不阻塞、不轮询空连接,并发处理能力极强。 - 高效数据结构与精简指令
底层使用 SDS、跳表、压缩列表、哈希表等高度优化的数据结构,命令执行逻辑简单、计算量极小,进一步提升执行效率。
3. Redis 的过期策略指的是什么?
Redis 过期策略用于管理带过期时间的键,保证内存及时回收。Redis 不使用定时删除 (CPU 消耗过高),实际采用惰性删除 + 定期删除组合策略。
- 定时删除(理论方案,Redis 不采用)
为每个过期键创建定时器,到期立即删除。优点是内存释放及时,缺点是大量定时器会严重占用 CPU,高并发下会导致 Redis 阻塞。 - 惰性删除(被动删除)
仅在客户端访问某个键时,才检查该键是否过期;若过期则立即删除并返回null。优点是不浪费 CPU 资源,缺点是冷数据会长期占用内存。 - 定期删除(主动删除)
Redis 后台任务默认每秒执行 10 次,逻辑如下:
- 随机抽取 20 个带过期时间的键;
- 删除其中已过期的键;
- 若过期键比例 > 25%,则继续抽取;
- 单次执行时间不超过 25ms,避免阻塞主线程。
最终 Redis 以惰性删除为主、定期删除为辅,平衡 CPU 占用与内存利用率。
4. Redis 持久化机制有哪些?
Redis 提供三种持久化方案:RDB 快照、AOF 日志、混合持久化(4.0+)。
- RDB(快照持久化)
- 原理:通过
fork子进程,将内存数据全量生成二进制dump.rdb文件。 - 优点:文件体积小、数据恢复速度极快、对主线程性能影响小。
- 缺点:两次快照之间的数据可能丢失,不适合对数据安全性要求极高的场景。
- AOF(追加文件持久化)
- 原理:记录所有写命令并追加到
.aof日志文件。 - 三种刷盘策略:
always:每次写都刷盘,最安全、性能最低;everysec:每秒刷盘,性能与安全性平衡,生产推荐;no:由操作系统控制刷盘,性能最高、最不安全。
- 优点:数据安全性高,丢失概率极低。
- 缺点:文件体积大、恢复速度慢、高写入场景下影响 QPS。
- 混合持久化(Redis 4.0+ 推荐)
AOF 重写时,前半段以 RDB 格式存储全量数据,后半段以 AOF 格式记录增量命令。兼顾恢复速度与数据安全性,是生产环境默认最佳方案。
5. 缓存穿透、击穿、雪崩怎么处理?
(1)缓存穿透
定义 :大量请求查询数据库中根本不存在的数据 ,缓存永远无法命中,所有流量直接打到数据库,极易导致数据库崩溃。
解决方案:
- 使用布隆过滤器过滤不存在的 key,从源头拦截非法请求;
- 对不存在的数据缓存
null值,并设置较短过期时间,避免重复查询数据库。
(2)缓存击穿
定义 :某个极高热点 key 过期瞬间,大量并发请求同时击穿缓存,直接访问数据库。
解决方案:
- 使用
SETNX实现互斥锁,只允许一个线程重建缓存; - 采用逻辑过期方案,不设置 Redis 过期时间,将过期时间存在 value 中,后台异步重建缓存;
- 核心热点 key 设置为永不过期,由后台定时任务刷新。
(3)缓存雪崩
定义 :大量 key 同一时间集中过期 ,或 Redis 节点宕机,导致全部流量压到数据库。
解决方案:
- 给过期时间添加随机值,避免批量 key 同时失效;
- 搭建 Redis 哨兵/集群,保证高可用;
- 启用多级缓存(本地缓存 + Redis);
- 接口限流、熔断、降级,保护数据库。
6. 分布式锁实现方式?
Redis 分布式锁用于解决分布式环境下共享资源竞争问题,必须保证互斥、防死锁、防误删、可重入。
-
基础原子实现
使用原子命令保证加锁安全性:SET lock_key unique_value NX EX 30
NX:仅 key 不存在时设置,保证互斥;EX:设置过期时间,防止死锁;- 唯一值:避免线程误删其他线程持有的锁。
-
安全解锁
必须通过 Lua 脚本保证"判断锁归属 + 删除锁"的原子性,防止并发场景下误删锁。
-
生产级核心要求
防死锁、防误删、锁自动续期(看门狗)、可重入、高可用。
-
成熟方案(推荐)
直接使用 Redisson 客户端,内置可重入锁、公平锁、红锁、自动续期、联锁等完整实现,稳定可靠,无需手写。
7. Redis 常见数据结构有哪些?
Redis 支持 9 种常用数据结构,覆盖绝大多数业务场景:
- String:底层 SDS,用于缓存对象、计数器、分布式 ID、Session 共享;
- Hash:底层压缩列表/哈希表,用于存储结构化对象(用户信息、商品信息);
- List:底层快速列表,用于消息队列、栈、时间线、评论列表;
- Set:底层哈希表,用于去重、共同好友、点赞、交集/并集计算;
- Sorted Set:底层跳表+哈希表,用于排行榜、延时任务、范围查询;
- Bitmap:用于用户签到、状态标记、布隆过滤器;
- HyperLogLog:用于 UV、日活统计,极低内存实现高精度去重计数;
- GEO:用于地理位置存储、附近的人、距离计算;
- Stream:用于持久化消息队列、消费组、消息回溯。
8. 主从和分片集群各自的工作流程以及适用场景?
(1)主从复制
工作流程:
- 从节点发送
PSYNC命令向主节点请求数据同步; - 主节点执行
bgsave生成 RDB 快照文件; - 主节点将 RDB 发送至从节点加载;
- 主节点将 RDB 生成期间的增量命令异步发送给从节点重放;
- 后续主节点持续将写命令异步同步至从节点。
适用场景:
- 读多写少业务;
- 读写分离,分担主节点读压力;
- 数据备份、故障容灾。
(2)Redis Cluster(分片集群)
工作流程:
- 所有数据映射到 16384 个哈希槽;
- 客户端通过
CRC16(key) % 16384计算槽位,定位对应节点; - 每个主节点负责一部分槽位数据,从节点用于故障备份;
- 支持主从自动故障转移,去中心化架构,可水平扩容。
适用场景:
- 海量数据存储,单机内存不足;
- 高并发写入场景;
- 需要水平扩展与自动故障迁移。
9. Redis 和数据库的一致性怎么保证?
Redis 作为缓存层,不追求强一致性 ,仅保证最终一致性;强一致需引入分布式事务,性能损耗极高,一般不使用。
最优方案:先更新数据库,再删除缓存
- 先执行数据库更新操作;
- 更新成功后删除缓存;
- 下次查询缓存不命中时,从数据库加载并回种缓存。
增强保证方案:
- 延迟双删:删除缓存后等待几百毫秒再次删除,规避并发脏数据;
- 监听 binlog(Canal):数据库变更后异步更新缓存;
- 给缓存设置较短 TTL,作为兜底一致策略。
不推荐"更新缓存",原因:并发更新易产生脏数据、缓存未必被访问、浪费系统资源。
10. 内存淘汰策略?
当 Redis 内存使用达到 maxmemory 上限时,触发内存淘汰机制,共 6 种策略:
- noeviction:默认策略,不淘汰任何数据,内存满时直接返回错误;
- allkeys-lru :对所有键使用 LRU(最近最少使用)算法淘汰,生产最常用;
- volatile-lru:仅对设置了过期时间的键使用 LRU 淘汰;
- allkeys-random:随机淘汰所有键;
- volatile-random:随机淘汰设置了过期时间的键;
- volatile-ttl:淘汰剩余存活时间最短的键。
生产环境推荐配置:allkeys-lru。
11. Nginx 集群的方式?
Nginx 集群分为负载均衡集群 与高可用集群两类。
- 负载均衡集群
通过upstream配置后端服务列表,实现请求分发,支持策略:
- 轮询(默认);
ip_hash:保证会话一致性;least_conn:转发至连接数最少的节点;weight:权重轮询;url_hash:按请求路径路由。
- 高可用集群
- Nginx + Keepalived:主备节点共用虚拟 VIP,主节点宕机时 VIP 自动漂移至备节点,保证入口高可用;
- 多 Nginx 节点 + DNS 轮询;
- 云环境直接使用厂商 SLB 云负载均衡。
12. 如何快速导入千万数据的实现思路?
千万级数据导入核心思路:减少 I/O、减少事务、关闭索引、并行导入。
- 关闭事务自动提交,采用批量提交;
- 导入前临时关闭索引、外键约束,导入完成后重建;
- 使用批量
INSERT,每 1000~5000 条为一批,减少网络交互; - 使用 MySQL 原生
LOAD DATA INFILE,速度比普通INSERT快 10~100 倍; - 将数据文件分片,多线程/多进程并行导入;
- 先写入消息队列,异步消费落库,不阻塞业务;
- 适当调大
innodb_buffer_pool_size提升写入效率。
13. 一条 INSERT 语句执行过程中日志的顺序?
InnoDB 执行 INSERT 严格遵循两阶段提交 ,保证 redo log 与 binlog 一致性,顺序如下:
- 记录 undo log,用于事务异常回滚;
- 执行插入操作,更新内存数据页;
- 写入
redo log,标记为prepare状态; - 写入
binlog日志; - 写入
redo log,标记为commit状态; - 事务完成。
该顺序可保证数据库崩溃恢复、主从复制时数据绝对一致。
14. 数据库死锁问题如何定位和避免?
定位死锁
- 开启死锁日志:
innodb_print_all_deadlocks = 1; - 执行命令查看:
SHOW ENGINE INNODB STATUS;; - 分析内容:事务持有锁、等待锁、执行 SQL、锁类型、事务隔离级别。
避免死锁
- 所有事务按固定顺序访问资源(如按 ID 从小到大更新);
- 事务尽可能小、短、快,减少锁持有时间;
- 避免长事务与热点行并发更新;
- 使用 RC(提交读)隔离级别,减少间隙锁,降低死锁概率;
- 优先使用乐观锁替代悲观锁。
15. 数据库表设计?
数据库表设计遵循规范、性能、扩展三大原则:
- 范式设计
满足 1NF、2NF、3NF,减少数据冗余,保证数据一致性;复杂查询场景可适度反范式,提升查询效率。 - 主键设计
推荐使用自增 ID 或雪花算法;禁止使用 UUID,避免索引分裂、查询性能下降。 - 索引设计
- 高频查询字段建立索引;
- 联合索引遵循最左前缀原则;
- 避免冗余索引,不在低基数字段建索引。
- 字段设计
使用最小合适类型,优先数字与定长字符串;大字段(text/blob)单独分表;冷热数据分离存储。 - 通用字段
必须包含:id、create_time、update_time、is_deleted。
16. 如果让你设计一个高并发系统 你会先从哪些点入手?
高并发系统设计按流量分层、架构解耦、存储优化、高可用保障逐步推进:
- 流量分层拦截
CDN → 网关限流 → 本地缓存 → Redis 缓存 → 数据库,层层削减流量。 - 应用无状态化
应用不依赖本地存储,便于水平扩容。 - 异步化解耦
使用消息队列削峰填谷,异步处理非核心流程。 - 缓存体系优化
多级缓存、热点缓存、过期时间打散、主动刷新。 - 数据库优化
索引优化、读写分离、分库分表。 - 集群与高可用
服务集群、Redis 集群、数据库主从/集群。 - 限流、熔断、降级
保护核心链路,防止级联故障。 - 池化技术
线程池、数据库连接池、HTTP 连接池。 - 全链路压测与监控
提前定位瓶颈,实时监控告警。
17. 自定义注解怎么实现?
Java 自定义注解通过元注解 + 反射 + AOP完整实现。
- 定义注解
使用@interface声明注解。 - 配置元注解
@Target:指定注解作用位置(类、方法、字段等);@Retention:指定生命周期,运行期使用设为RUNTIME;@Documented:生成 API 文档;@Inherited:允许子类继承父类注解。
- 解析与增强
通过反射读取注解信息,结合 AOP 实现切面增强,如日志打印、权限校验、接口限流、事务控制等。
注解本质是接口,运行时通过反射获取信息,配合动态代理完成功能扩展。
18. 双亲委派是什么?
双亲委派是 JVM 类加载器的标准工作机制。
规则:
- 类加载时,子类加载器先委托父类加载器尝试加载;
- 父类加载器无法加载时,子类加载器才自行加载;
- 委托顺序自底向上,查找顺序自顶向下。
类加载层次:
- 启动类加载器(Bootstrap):加载 JVM 核心类库;
- 扩展类加载器(Extension):加载
jre/lib/ext包; - 应用类加载器(App):加载项目
classpath下类。
作用 :
保证核心类不被恶意篡改、确保类全局唯一、避免重复加载、规范类加载行为。
19. 讲讲 Spring 里面的事务传播行为?
Spring 事务传播行为控制多个事务方法相互调用时的事务策略,共 7 种:
- REQUIRED(默认):存在事务则加入,不存在则新建事务;
- REQUIRES_NEW:新建独立事务,原有事务挂起;
- SUPPORTS:有事务则加入,无事务则以非事务方式运行;
- MANDATORY:必须在已有事务中运行,否则抛出异常;
- NOT_SUPPORTED:始终非事务运行,已有事务挂起;
- NEVER:必须非事务运行,否则抛异常;
- NESTED:嵌套事务,支持保存点回滚,外层事务回滚则内层一并回滚。
20. 反射常见有哪几种方式?什么是反射机制?AOP 呢?
反射机制
Java 反射允许程序在运行时获取类的完整结构信息,并动态操作对象:
- 获取类、父类、接口信息;
- 获取字段、方法、构造器;
- 动态调用方法、修改属性;
- 无需在编译期确定目标对象。
获取 Class 对象的三种方式
对象.getClass();类名.class;Class.forName("全限定类名")。
AOP(面向切面编程)
AOP 是一种编程范式,用于横向抽取通用逻辑(日志、事务、权限、异常、限流),不侵入业务代码。
- 底层实现:JDK 动态代理 (面向接口)、CGLIB(面向类);
- 核心概念:切面、切点、通知、连接点;
- 典型场景:声明式事务、统一日志、权限校验、接口限流。