经纬恒润 Java开发 二面 面经
Java后端开发二面题目(完整版)
1. 说说Spring Boot的自动装配原理是如何实现的?
答案:
核心机制:
@SpringBootApplication包含三个注解@EnableAutoConfiguration触发自动装配@Import(AutoConfigurationImportSelector.class)导入配置
装配流程:
- 读取
META-INF/spring.factories文件 - 加载所有
EnableAutoConfiguration配置类 - 根据
@Conditional条件注解过滤 - 将符合条件的Bean注册到容器
常见条件注解:
@ConditionalOnClass:类路径存在某个类@ConditionalOnMissingBean:容器中没有某个Bean@ConditionalOnProperty:配置文件有某个属性
自定义starter:
- 创建配置类加
@Configuration - 在
spring.factories中注册 - 使用条件注解控制加载
2. Redis的数据结构有哪些?分别适用什么场景?
答案:
String(字符串):
- 场景:缓存、计数器、分布式锁
- 命令:SET、GET、INCR、DECR
Hash(哈希):
- 场景:存储对象、购物车
- 命令:HSET、HGET、HMGET
- 优势:节省内存,部分更新
List(列表):
- 场景:消息队列、时间线、最新列表
- 命令:LPUSH、RPUSH、LPOP、RPOP
- 特点:有序、可重复
Set(集合):
- 场景:标签、共同好友、去重
- 命令:SADD、SMEMBERS、SINTER
- 特点:无序、不重复
ZSet(有序集合):
- 场景:排行榜、延迟队列
- 命令:ZADD、ZRANGE、ZRANK
- 特点:按score排序
其他类型:
- Bitmap:签到、布隆过滤器
- HyperLogLog:UV统计
- GEO:地理位置
- Stream:消息队列(5.0+)
3. 分布式事务有哪些解决方案?各有什么优缺点?
答案:
1. 2PC(两阶段提交)
- 准备阶段:协调者询问参与者
- 提交阶段:统一提交或回滚
- 缺点:同步阻塞、单点故障、数据不一致
2. 3PC(三阶段提交)
- 增加CanCommit阶段
- 超时机制
- 缺点:复杂度高
3. TCC(Try-Confirm-Cancel)
- Try:预留资源
- Confirm:确认提交
- Cancel:取消回滚
- 优点:性能好、无锁
- 缺点:业务侵入性强
4. 本地消息表
- 业务和消息在同一事务
- 定时扫描发送消息
- 优点:实现简单
- 缺点:依赖定时任务
5. 消息队列(最终一致性)
- 发送消息到MQ
- 消费者处理业务
- 优点:解耦、高性能
- 缺点:只能保证最终一致
6. Seata(推荐)
- AT模式:自动补偿
- TCC模式:手动补偿
- SAGA模式:长事务
- 优点:对业务侵入小
选型建议:
- 强一致性:TCC、Seata AT
- 最终一致性:消息队列
- 简单场景:本地消息表
4. JVM内存模型是怎样的?如何排查内存溢出问题?
答案:
内存区域:
线程共享:
- 堆(Heap):对象实例、数组
- 方法区(元空间):类信息、常量、静态变量
线程私有:
- 虚拟机栈:局部变量、方法调用
- 本地方法栈:Native方法
- 程序计数器:字节码行号
常见OOM:
1. 堆溢出(最常见)
- 原因:对象过多、内存泄漏
- 错误:
java.lang.OutOfMemoryError: Java heap space - 排查: jmap -heap pid查看堆使用jmap -dump:format=b,file=heap.hprof pid导出堆MAT工具分析dump文件
2. 栈溢出
- 原因:递归太深、线程过多
- 错误:
StackOverflowError - 解决:增加栈大小
-Xss
3. 元空间溢出
- 原因:类加载过多
- 错误:
OutOfMemoryError: Metaspace - 解决:增加元空间
-XX:MetaspaceSize
排查步骤:
- 查看GC日志:
-XX:+PrintGCDetails - 使用jstat监控:
jstat -gc pid 1000 - dump堆分析:找到占用内存最多的对象
- 分析代码:是否有内存泄漏
预防措施:
- 合理设置堆大小
- 及时释放资源
- 使用对象池
- 避免大对象
5. MySQL的事务隔离级别有哪些?如何解决幻读问题?
答案:
四种隔离级别:
1. 读未提交(Read Uncommitted)
- 可以读到未提交的数据
- 问题:脏读、不可重复读、幻读
2. 读已提交(Read Committed)
- 只能读到已提交的数据
- 问题:不可重复读、幻读
- Oracle默认级别
3. 可重复读(Repeatable Read)
- 同一事务内多次读取结果一致
- 问题:幻读
- MySQL默认级别
4. 串行化(Serializable)
- 完全串行执行
- 无并发问题,性能最差
并发问题:
- 脏读:读到未提交的数据
- 不可重复读:两次读取数据不一致
- 幻读:两次查询记录数不一致
MySQL如何解决幻读:
MVCC(多版本并发控制):
- 每行记录有隐藏字段:事务ID、回滚指针
- 读取时根据ReadView判断可见性
- 快照读不会产生幻读
Next-Key Lock(间隙锁):
- 锁定记录+间隙
- 防止其他事务插入
- 当前读(SELECT FOR UPDATE)使用
示例:
-- 事务A BEGIN; SELECT * FROM table WHERE id > 10 FOR UPDATE; -- 锁定id>10的记录和间隙,事务B无法插入 -- 事务B INSERT INTO table VALUES(15, ...); -- 阻塞
6. 如何设计一个秒杀系统?需要考虑哪些问题?
答案:
核心问题:
- 高并发:瞬间大量请求
- 超卖:库存扣减不一致
- 恶意请求:刷单、爬虫
架构设计:
前端优化:
- 按钮置灰,防止重复点击
- 验证码、滑块验证
- 静态资源CDN加速
接口层:
- Nginx限流:限制单IP请求频率
- 网关限流:令牌桶、漏桶算法
- 接口防刷:用户维度限流
服务层:
1. 缓存预热
- 提前将商品信息加载到Redis
- 库存放Redis,减少DB压力
2. 库存扣减
// Redis原子操作
Long stock = redisTemplate.opsForValue().decrement("stock:" + goodsId);
if (stock < 0) {
// 库存不足
return;
}
3. 异步下单
- 扣减库存成功后发送MQ消息
- 消费者异步创建订单
- 削峰填谷
4. 分布式锁
- 防止超卖
- Redisson实现
RLock lock = redisson.getLock("lock:goods:" + goodsId);
try {
lock.lock();
// 扣减库存
} finally {
lock.unlock();
}
数据库层:
- 读写分离
- 分库分表
- 乐观锁更新:
UPDATE goods SET stock = stock - 1 WHERE id = ? AND stock > 0
监控告警:
- 实时监控QPS、响应时间
- 异常自动告警
- 熔断降级
防止超卖方案:
- Redis预扣库存
- 数据库乐观锁兜底
- 定时对账,补偿机制
7. Spring AOP的实现原理是什么?动态代理有哪几种方式?
答案:
AOP核心概念:
- 切面(Aspect):横切关注点的模块化
- 连接点(JoinPoint):方法执行点
- 切点(Pointcut):匹配连接点的表达式
- 通知(Advice):在切点执行的动作
- 织入(Weaving):将切面应用到目标对象
通知类型:
@Before:前置通知@After:后置通知@AfterReturning:返回后通知@AfterThrowing:异常通知@Around:环绕通知(最强大)
动态代理方式:
1. JDK动态代理
- 基于接口
- 使用
Proxy.newProxyInstance() - 实现
InvocationHandler接口 - Spring默认方式(有接口时)
2. CGLIB代理
- 基于继承
- 生成目标类的子类
- 使用字节码技术
- 无接口时使用
区别:
- JDK代理:只能代理接口,性能略好
- CGLIB:可以代理类,final方法无法代理
应用场景:
- 日志记录
- 权限校验
- 事务管理(
@Transactional) - 性能监控
- 异常处理
8. 如何保证Redis和MySQL的数据一致性?
答案:
常见问题:
- 更新数据库后,缓存未更新
- 并发情况下数据不一致
方案对比:
1. 先更新数据库,再删除缓存(推荐)
// 更新数据库
userMapper.update(user);
// 删除缓存
redisTemplate.delete("user:" + userId);
- 优点:简单可靠
- 问题:并发时可能读到旧数据
2. 延迟双删
// 删除缓存
redisTemplate.delete("user:" + userId);
// 更新数据库
userMapper.update(user);
// 延迟删除缓存
Thread.sleep(500);
redisTemplate.delete("user:" + userId);
- 解决并发问题
- 缺点:延迟时间难确定
3. 先删除缓存,再更新数据库
- 问题:缓存失效期间,大量请求打到DB
4. 更新数据库,更新缓存
- 问题:并发时可能缓存数据错误
- 浪费资源(缓存可能不会被读取)
5. Canal监听binlog(最佳)
- Canal伪装成MySQL从库
- 监听binlog变化
- 异步更新Redis
- 优点:解耦、可靠
- 缺点:有延迟
6. 设置过期时间兜底
- 即使不一致,过期后会重新加载
- 保证最终一致性
选型建议:
- 简单场景:先更新DB,再删缓存
- 高并发:延迟双删 + 过期时间
- 复杂场景:Canal + MQ
9. 说说你对微服务的理解,服务之间如何通信?
答案:
微服务特点:
- 单一职责,服务独立
- 独立部署、独立扩展
- 去中心化治理
- 技术栈多样化
Spring Cloud组件:
注册中心:
- Eureka、Nacos、Consul
- 服务注册与发现
配置中心:
- Config、Nacos
- 统一配置管理、动态刷新
负载均衡:
- Ribbon(客户端)
- 轮询、随机、权重等策略
服务调用:
- Feign:声明式HTTP客户端
- RestTemplate + Ribbon
熔断降级:
- Hystrix、Sentinel
- 防止雪崩效应
网关:
- Gateway、Zuul
- 路由、限流、鉴权
链路追踪:
- Sleuth + Zipkin
- 分布式追踪
服务通信方式:
1. HTTP/REST(常用)
- Feign调用
- 简单直观
- 性能一般
2. RPC
- Dubbo、gRPC
- 性能高
- 二进制协议
3. 消息队列
- 异步通信
- 解耦
- 削峰填谷
选型:
- 同步调用:Feign/Dubbo
- 异步通知:MQ
- 实时性要求高:RPC
10. 如何设计一个高可用的系统?
答案:
高可用目标:
- 99.9%:年故障时间8.76小时
- 99.99%:年故障时间52.56分钟
- 99.999%:年故障时间5.26分钟
设计原则:
1. 消除单点故障
- 服务多实例部署
- 数据库主从复制
- Redis哨兵/集群
- 负载均衡
2. 故障隔离
- 服务拆分,避免级联失败
- 线程池隔离
- 熔断降级
3. 限流保护
- 网关层限流
- 服务层限流
- 数据库连接池限制
4. 降级策略
- 非核心功能降级
- 返回默认值
- 读缓存兜底
5. 超时控制
- 设置合理超时时间
- 快速失败
- 避免资源占用
6. 重试机制
- 幂等性保证
- 指数退避
- 最大重试次数
7. 监控告警
- 实时监控关键指标
- 异常自动告警
- 日志收集分析
8. 灰度发布
- 小流量验证
- 逐步放量
- 快速回滚
9. 容灾备份
- 数据定期备份
- 异地多活
- 灾难恢复预案
10. 压测验证
- 定期压测
- 找到系统瓶颈
- 容量规划
11. 说说你对设计模式的理解,项目中用过哪些?
答案:
常用设计模式:
1. 单例模式
- 场景:Spring Bean默认单例
- 实现:枚举、静态内部类
2. 工厂模式
- 场景:BeanFactory创建Bean
- 解耦对象创建
3. 代理模式
- 场景:AOP、事务管理
- JDK动态代理、CGLIB
4. 模板方法模式
- 场景:JdbcTemplate、RestTemplate
- 定义算法骨架,子类实现细节
5. 策略模式
- 场景:支付方式选择、优惠计算
- 避免大量if-else
6. 观察者模式
- 场景:Spring事件机制
- 发布订阅
7. 责任链模式
- 场景:Filter链、拦截器
- 请求依次处理
8. 装饰器模式
- 场景:IO流包装
- 动态添加功能
9. 适配器模式
- 场景:SpringMVC的HandlerAdapter
- 接口转换
10. 建造者模式
- 场景:Lombok的@Builder
- 构建复杂对象
项目实践:
- 策略模式:不同支付方式
- 工厂模式:创建不同类型的订单
- 模板方法:导出不同格式文件
- 责任链:权限校验链
设计原则:
- 单一职责
- 开闭原则
- 里氏替换
- 依赖倒置
- 接口隔离
12. 项目中遇到过哪些性能问题?如何优化的?
答案:
问题1:接口响应慢
排查:
- 查看日志,定位慢的环节
- SQL慢查询日志
- Arthas监控方法耗时
优化:
- 添加索引,SQL优化
- 增加Redis缓存
- 异步处理非核心逻辑
- 效果:500ms → 50ms
问题2:数据库连接池耗尽
原因:
- 连接未释放
- 并发量大,连接数不够
优化:
- 检查代码,确保连接关闭
- 增加连接池大小
- 优化慢SQL,减少连接占用时间
问题3:内存溢出
排查:
- jmap导出堆dump
- MAT分析,发现大对象
优化:
- 分批处理数据
- 及时释放资源
- 调整堆大小
问题4:缓存击穿
现象:
- 热点key过期,大量请求打到DB
优化:
- 热点数据不过期
- 加互斥锁,只有一个请求查DB
- 提前异步刷新缓存
问题5:GC频繁
排查:
- jstat查看GC情况
- GC日志分析
优化:
- 调整堆大小
- 优化代码,减少对象创建
- 使用对象池
总结:
- 先定位问题,再针对性优化
- 使用工具辅助分析
- 优化后验证效果
Java面试圣经,带你练透java圣经