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(面向类);
  • 核心概念:切面、切点、通知、连接点;
  • 典型场景:声明式事务、统一日志、权限校验、接口限流。
相关推荐
lly2024062 小时前
PHP 魔术常量
开发语言
Evand J2 小时前
【MATLAB例程分享】三维非线性目标跟踪,观测为:距离+方位角+俯仰角,使用无迹卡尔曼滤波(UKF)与RTS平滑,高精度定位
开发语言·matlab·目标跟踪
编程之升级打怪2 小时前
Java NIO的简单封装
java·开发语言·nio
wuxinyan1232 小时前
Java面试题46:一文深入了解JVM 核心知识体系
java·jvm·面试题
小江的记录本2 小时前
【JEECG Boot】 《JEECG Boot 数据字典使用教程》(完整版)
java·前端·数据库·spring boot·后端·spring·mybatis
Chase_______2 小时前
【Python基础 | 第5章】面向对象与异常处理:一文搞懂类、对象、封装、继承、多态
开发语言·python
啦啦啦!2 小时前
项目环境的搭建,项目的初步使用和deepseek的初步认识
开发语言·c++·人工智能·算法
小李云雾2 小时前
Python Web 路由详解:核心知识点全覆盖
开发语言·前端·python·路由
鲸渔2 小时前
【C++ 变量与常量】变量的定义、初始化、const 与 constexpr
java·开发语言·c++