Java 后端高频 20 题超详细解析 ①

本文整理后端开发面试核心考点,涵盖 Redis、MySQL、Spring、JVM、高并发、分布式 等内容,答案详细严谨、原理清晰,适合面试复习、知识梳理与技术博客发布。


1. Redis 是多线程还是单线程的?

Redis 的线程模型需按版本与模块区分,不能简单判定为单线程或多线程:

  • Redis 6.0 之前核心命令执行全程单线程,网络 IO、命令处理、定时器、过期键清理均由唯一主线程串行执行;仅 RDB 持久化、AOF 重写等后台任务使用子线程,不影响主线程执行。
  • Redis 6.0 及以后 :引入多线程 IO 模型 ,多线程仅负责网络数据读取、协议解析、响应写回,核心命令执行依旧保持单线程,确保命令原子性与执行稳定性。

综上,Redis 采用命令执行单线程 + 网络 IO 多线程 + 后台任务多线程的混合架构,单线程执行命令是其无锁、高效、稳定的核心原因。


2. 单线程为什么快呢?

Redis 单线程仍能支撑 10W+ QPS,核心优势来源于四点:

  1. 完全基于内存操作
    所有数据读写都在内存中完成,响应延迟达到微秒级别,完全规避磁盘 I/O 带来的毫秒级性能损耗。
  2. 无锁竞争与线程切换开销
    多线程架构存在锁竞争、上下文切换、线程调度等额外开销;Redis 单线程天然避免这些损耗,CPU 利用率极高。
  3. IO 多路复用模型
    基于 epoll/kqueue/select 实现高并发网络模型,单线程可同时监听海量客户端连接,只处理活跃请求,不阻塞、不轮询空连接,并发处理能力极强。
  4. 高效数据结构与精简指令
    底层使用 SDS、跳表、压缩列表、哈希表等高度优化的数据结构,命令执行逻辑简单、计算量极小,进一步提升执行效率。

3. Redis 的过期策略指的是什么?

Redis 过期策略用于管理带过期时间的键,保证内存及时回收。Redis 不使用定时删除 (CPU 消耗过高),实际采用惰性删除 + 定期删除组合策略。

  1. 定时删除(理论方案,Redis 不采用)
    为每个过期键创建定时器,到期立即删除。优点是内存释放及时,缺点是大量定时器会严重占用 CPU,高并发下会导致 Redis 阻塞。
  2. 惰性删除(被动删除)
    仅在客户端访问某个键时,才检查该键是否过期;若过期则立即删除并返回 null。优点是不浪费 CPU 资源,缺点是冷数据会长期占用内存。
  3. 定期删除(主动删除)
    Redis 后台任务默认每秒执行 10 次,逻辑如下:
  • 随机抽取 20 个带过期时间的键;
  • 删除其中已过期的键;
  • 若过期键比例 > 25%,则继续抽取;
  • 单次执行时间不超过 25ms,避免阻塞主线程。

最终 Redis 以惰性删除为主、定期删除为辅,平衡 CPU 占用与内存利用率。


4. Redis 持久化机制有哪些?

Redis 提供三种持久化方案:RDB 快照、AOF 日志、混合持久化(4.0+)

  1. RDB(快照持久化)
  • 原理:通过 fork 子进程,将内存数据全量生成二进制 dump.rdb 文件。
  • 优点:文件体积小、数据恢复速度极快、对主线程性能影响小。
  • 缺点:两次快照之间的数据可能丢失,不适合对数据安全性要求极高的场景。
  1. AOF(追加文件持久化)
  • 原理:记录所有写命令并追加到 .aof 日志文件。
  • 三种刷盘策略:
    • always:每次写都刷盘,最安全、性能最低;
    • everysec:每秒刷盘,性能与安全性平衡,生产推荐;
    • no:由操作系统控制刷盘,性能最高、最不安全。
  • 优点:数据安全性高,丢失概率极低。
  • 缺点:文件体积大、恢复速度慢、高写入场景下影响 QPS。
  1. 混合持久化(Redis 4.0+ 推荐)
    AOF 重写时,前半段以 RDB 格式存储全量数据,后半段以 AOF 格式记录增量命令。兼顾恢复速度与数据安全性,是生产环境默认最佳方案。

5. 缓存穿透、击穿、雪崩怎么处理?

(1)缓存穿透

定义 :大量请求查询数据库中根本不存在的数据 ,缓存永远无法命中,所有流量直接打到数据库,极易导致数据库崩溃。
解决方案

  • 使用布隆过滤器过滤不存在的 key,从源头拦截非法请求;
  • 对不存在的数据缓存 null 值,并设置较短过期时间,避免重复查询数据库。

(2)缓存击穿

定义 :某个极高热点 key 过期瞬间,大量并发请求同时击穿缓存,直接访问数据库。
解决方案

  • 使用 SETNX 实现互斥锁,只允许一个线程重建缓存;
  • 采用逻辑过期方案,不设置 Redis 过期时间,将过期时间存在 value 中,后台异步重建缓存;
  • 核心热点 key 设置为永不过期,由后台定时任务刷新。

(3)缓存雪崩

定义 :大量 key 同一时间集中过期 ,或 Redis 节点宕机,导致全部流量压到数据库。
解决方案

  • 给过期时间添加随机值,避免批量 key 同时失效;
  • 搭建 Redis 哨兵/集群,保证高可用;
  • 启用多级缓存(本地缓存 + Redis);
  • 接口限流、熔断、降级,保护数据库。

6. 分布式锁实现方式?

Redis 分布式锁用于解决分布式环境下共享资源竞争问题,必须保证互斥、防死锁、防误删、可重入

  1. 基础原子实现
    使用原子命令保证加锁安全性:

    SET lock_key unique_value NX EX 30

  • NX:仅 key 不存在时设置,保证互斥;
  • EX:设置过期时间,防止死锁;
  • 唯一值:避免线程误删其他线程持有的锁。
  1. 安全解锁

    必须通过 Lua 脚本保证"判断锁归属 + 删除锁"的原子性,防止并发场景下误删锁。

  2. 生产级核心要求

    防死锁、防误删、锁自动续期(看门狗)、可重入、高可用。

  3. 成熟方案(推荐)

    直接使用 Redisson 客户端,内置可重入锁、公平锁、红锁、自动续期、联锁等完整实现,稳定可靠,无需手写。


7. Redis 常见数据结构有哪些?

Redis 支持 9 种常用数据结构,覆盖绝大多数业务场景:

  1. String:底层 SDS,用于缓存对象、计数器、分布式 ID、Session 共享;
  2. Hash:底层压缩列表/哈希表,用于存储结构化对象(用户信息、商品信息);
  3. List:底层快速列表,用于消息队列、栈、时间线、评论列表;
  4. Set:底层哈希表,用于去重、共同好友、点赞、交集/并集计算;
  5. Sorted Set:底层跳表+哈希表,用于排行榜、延时任务、范围查询;
  6. Bitmap:用于用户签到、状态标记、布隆过滤器;
  7. HyperLogLog:用于 UV、日活统计,极低内存实现高精度去重计数;
  8. GEO:用于地理位置存储、附近的人、距离计算;
  9. Stream:用于持久化消息队列、消费组、消息回溯。

8. 主从和分片集群各自的工作流程以及适用场景?

(1)主从复制

工作流程

  1. 从节点发送 PSYNC 命令向主节点请求数据同步;
  2. 主节点执行 bgsave 生成 RDB 快照文件;
  3. 主节点将 RDB 发送至从节点加载;
  4. 主节点将 RDB 生成期间的增量命令异步发送给从节点重放;
  5. 后续主节点持续将写命令异步同步至从节点。

适用场景

  • 读多写少业务;
  • 读写分离,分担主节点读压力;
  • 数据备份、故障容灾。

(2)Redis Cluster(分片集群)

工作流程

  • 所有数据映射到 16384 个哈希槽
  • 客户端通过 CRC16(key) % 16384 计算槽位,定位对应节点;
  • 每个主节点负责一部分槽位数据,从节点用于故障备份;
  • 支持主从自动故障转移,去中心化架构,可水平扩容。

适用场景

  • 海量数据存储,单机内存不足;
  • 高并发写入场景;
  • 需要水平扩展与自动故障迁移。

9. Redis 和数据库的一致性怎么保证?

Redis 作为缓存层,不追求强一致性 ,仅保证最终一致性;强一致需引入分布式事务,性能损耗极高,一般不使用。

最优方案:先更新数据库,再删除缓存

  1. 先执行数据库更新操作;
  2. 更新成功后删除缓存;
  3. 下次查询缓存不命中时,从数据库加载并回种缓存。

增强保证方案

  • 延迟双删:删除缓存后等待几百毫秒再次删除,规避并发脏数据;
  • 监听 binlog(Canal):数据库变更后异步更新缓存;
  • 给缓存设置较短 TTL,作为兜底一致策略。

不推荐"更新缓存",原因:并发更新易产生脏数据、缓存未必被访问、浪费系统资源。


10. 内存淘汰策略?

当 Redis 内存使用达到 maxmemory 上限时,触发内存淘汰机制,共 6 种策略:

  1. noeviction:默认策略,不淘汰任何数据,内存满时直接返回错误;
  2. allkeys-lru :对所有键使用 LRU(最近最少使用)算法淘汰,生产最常用
  3. volatile-lru:仅对设置了过期时间的键使用 LRU 淘汰;
  4. allkeys-random:随机淘汰所有键;
  5. volatile-random:随机淘汰设置了过期时间的键;
  6. volatile-ttl:淘汰剩余存活时间最短的键。

生产环境推荐配置:allkeys-lru


11. Nginx 集群的方式?

Nginx 集群分为负载均衡集群高可用集群两类。

  1. 负载均衡集群
    通过 upstream 配置后端服务列表,实现请求分发,支持策略:
  • 轮询(默认);
  • ip_hash:保证会话一致性;
  • least_conn:转发至连接数最少的节点;
  • weight:权重轮询;
  • url_hash:按请求路径路由。
  1. 高可用集群
  • Nginx + Keepalived:主备节点共用虚拟 VIP,主节点宕机时 VIP 自动漂移至备节点,保证入口高可用;
  • 多 Nginx 节点 + DNS 轮询;
  • 云环境直接使用厂商 SLB 云负载均衡。

12. 如何快速导入千万数据的实现思路?

千万级数据导入核心思路:减少 I/O、减少事务、关闭索引、并行导入

  1. 关闭事务自动提交,采用批量提交;
  2. 导入前临时关闭索引、外键约束,导入完成后重建;
  3. 使用批量 INSERT,每 1000~5000 条为一批,减少网络交互;
  4. 使用 MySQL 原生 LOAD DATA INFILE,速度比普通 INSERT 快 10~100 倍;
  5. 将数据文件分片,多线程/多进程并行导入;
  6. 先写入消息队列,异步消费落库,不阻塞业务;
  7. 适当调大 innodb_buffer_pool_size 提升写入效率。

13. 一条 INSERT 语句执行过程中日志的顺序?

InnoDB 执行 INSERT 严格遵循两阶段提交 ,保证 redo logbinlog 一致性,顺序如下:

  1. 记录 undo log,用于事务异常回滚;
  2. 执行插入操作,更新内存数据页;
  3. 写入 redo log,标记为 prepare 状态;
  4. 写入 binlog 日志;
  5. 写入 redo log,标记为 commit 状态;
  6. 事务完成。

该顺序可保证数据库崩溃恢复、主从复制时数据绝对一致。


14. 数据库死锁问题如何定位和避免?

定位死锁

  1. 开启死锁日志:innodb_print_all_deadlocks = 1
  2. 执行命令查看:SHOW ENGINE INNODB STATUS;
  3. 分析内容:事务持有锁、等待锁、执行 SQL、锁类型、事务隔离级别。

避免死锁

  1. 所有事务按固定顺序访问资源(如按 ID 从小到大更新);
  2. 事务尽可能小、短、快,减少锁持有时间;
  3. 避免长事务与热点行并发更新;
  4. 使用 RC(提交读)隔离级别,减少间隙锁,降低死锁概率;
  5. 优先使用乐观锁替代悲观锁。

15. 数据库表设计?

数据库表设计遵循规范、性能、扩展三大原则:

  1. 范式设计
    满足 1NF、2NF、3NF,减少数据冗余,保证数据一致性;复杂查询场景可适度反范式,提升查询效率。
  2. 主键设计
    推荐使用自增 ID 或雪花算法;禁止使用 UUID,避免索引分裂、查询性能下降。
  3. 索引设计
  • 高频查询字段建立索引;
  • 联合索引遵循最左前缀原则;
  • 避免冗余索引,不在低基数字段建索引。
  1. 字段设计
    使用最小合适类型,优先数字与定长字符串;大字段(text/blob)单独分表;冷热数据分离存储。
  2. 通用字段
    必须包含:idcreate_timeupdate_timeis_deleted

16. 如果让你设计一个高并发系统 你会先从哪些点入手?

高并发系统设计按流量分层、架构解耦、存储优化、高可用保障逐步推进:

  1. 流量分层拦截
    CDN → 网关限流 → 本地缓存 → Redis 缓存 → 数据库,层层削减流量。
  2. 应用无状态化
    应用不依赖本地存储,便于水平扩容。
  3. 异步化解耦
    使用消息队列削峰填谷,异步处理非核心流程。
  4. 缓存体系优化
    多级缓存、热点缓存、过期时间打散、主动刷新。
  5. 数据库优化
    索引优化、读写分离、分库分表。
  6. 集群与高可用
    服务集群、Redis 集群、数据库主从/集群。
  7. 限流、熔断、降级
    保护核心链路,防止级联故障。
  8. 池化技术
    线程池、数据库连接池、HTTP 连接池。
  9. 全链路压测与监控
    提前定位瓶颈,实时监控告警。

17. 自定义注解怎么实现?

Java 自定义注解通过元注解 + 反射 + AOP完整实现。

  1. 定义注解
    使用 @interface 声明注解。
  2. 配置元注解
  • @Target:指定注解作用位置(类、方法、字段等);
  • @Retention:指定生命周期,运行期使用设为 RUNTIME
  • @Documented:生成 API 文档;
  • @Inherited:允许子类继承父类注解。
  1. 解析与增强
    通过反射读取注解信息,结合 AOP 实现切面增强,如日志打印、权限校验、接口限流、事务控制等。

注解本质是接口,运行时通过反射获取信息,配合动态代理完成功能扩展。


18. 双亲委派是什么?

双亲委派是 JVM 类加载器的标准工作机制。

规则

  1. 类加载时,子类加载器先委托父类加载器尝试加载
  2. 父类加载器无法加载时,子类加载器才自行加载;
  3. 委托顺序自底向上,查找顺序自顶向下。

类加载层次

  1. 启动类加载器(Bootstrap):加载 JVM 核心类库;
  2. 扩展类加载器(Extension):加载 jre/lib/ext 包;
  3. 应用类加载器(App):加载项目 classpath 下类。

作用

保证核心类不被恶意篡改、确保类全局唯一、避免重复加载、规范类加载行为。


19. 讲讲 Spring 里面的事务传播行为?

Spring 事务传播行为控制多个事务方法相互调用时的事务策略,共 7 种:

  1. REQUIRED(默认):存在事务则加入,不存在则新建事务;
  2. REQUIRES_NEW:新建独立事务,原有事务挂起;
  3. SUPPORTS:有事务则加入,无事务则以非事务方式运行;
  4. MANDATORY:必须在已有事务中运行,否则抛出异常;
  5. NOT_SUPPORTED:始终非事务运行,已有事务挂起;
  6. NEVER:必须非事务运行,否则抛异常;
  7. NESTED:嵌套事务,支持保存点回滚,外层事务回滚则内层一并回滚。

20. 反射常见有哪几种方式?什么是反射机制?AOP 呢?

反射机制

Java 反射允许程序在运行时获取类的完整结构信息,并动态操作对象:

  • 获取类、父类、接口信息;
  • 获取字段、方法、构造器;
  • 动态调用方法、修改属性;
  • 无需在编译期确定目标对象。

获取 Class 对象的三种方式

  1. 对象.getClass()
  2. 类名.class
  3. Class.forName("全限定类名")

AOP(面向切面编程)

AOP 是一种编程范式,用于横向抽取通用逻辑(日志、事务、权限、异常、限流),不侵入业务代码。

  • 底层实现:JDK 动态代理 (面向接口)、CGLIB(面向类);
  • 核心概念:切面、切点、通知、连接点;
  • 典型场景:声明式事务、统一日志、权限校验、接口限流。
相关推荐
Scott9999HH4 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
腻害兔4 小时前
【若依项目-产品经理视角】深度拆解 RuoYi-Vue-Pro 商城模块:从商品管理到交易引擎,50 张表撑起一整套电商系统
java·大数据·vue.js·产品经理·ai编程
码智社5 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海5 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖6 小时前
JDK 26 新特性详解
java·开发语言
马优晨6 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区8 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大9 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai9 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
维天说9 小时前
CLI-Switch 2026年3月版历史设计:Hook、TTY 隔离与 JSON 状态
java·服务器·json