25年11月它思科技 Java开发 二面

#JAVA##JAVA面经##JAVA内推#

1. 项目中你负责的模块如何保证接口的幂等性?

回答思路

  • 核心锚定:幂等性核心是「同一请求多次执行结果一致」,需按请求类型+业务场景选择方案,核心逻辑是「唯一标识+状态校验+防重拦截」;
  • 分层拆解方案(按场景适配)
    1. 读接口:天然幂等,无需额外处理;
    2. 写接口(如订单创建、支付回调)
      • 方案1:唯一请求ID(幂等号)+ Redis防重锁
        • 流程:前端/网关生成唯一requestId,接口接收后先通过SET requestId 1 NX EX 300加锁,成功则执行业务,失败则返回重复请求;
      • 适用场景:短链接、支付回调等一次性请求;
      • 项目落地:订单创建接口,requestId关联用户ID+订单业务号,Redis锁有效期覆盖业务最大处理时长;
      • 核心:NX保证只有一次执行,EX防止死锁;
    3. 写接口(如库存扣减)
      • 方案2:业务唯一键+数据库唯一索引
        • 流程:以「用户ID+商品ID+活动ID」为唯一键创建数据库唯一索引,重复插入时触发主键冲突,捕获异常并返回“操作已执行”;
      • 适用场景:有明确业务唯一标识的场景;
    4. 状态机控制
      • 流程:针对有状态流转的业务(如订单:待支付→已支付→已完成),执行业务前校验当前状态,仅允许合法状态流转(如已支付订单不允许重复扣减库存);
  • 核心结论:核心是「唯一标识拦截重复请求 + 业务状态校验防止重复处理」,按场景选择Redis防重锁/数据库唯一索引/状态机。

标准答案

我负责的订单模块按场景分层保证幂等:① 支付回调接口:用网关生成的唯一requestId作为幂等号,接口层先通过Redis SET requestId 1 NX EX 300加防重锁,成功则执行业务,失败则返回重复请求;② 订单创建接口:以「用户ID+商品ID+活动ID」为业务唯一键,创建数据库唯一索引,重复插入触发主键冲突时,捕获异常并返回“订单已创建”;③ 库存扣减接口:增加状态机校验,仅当订单处于“待支付”状态时允许扣减,已支付/已取消状态直接拒绝。核心逻辑是「唯一标识拦截重复请求 + 业务状态校验防止重复处理」,确保同一请求多次执行结果一致。

2. 高并发场景下,如何解决数据库读写性能瓶颈?

回答思路

  • 核心锚定:数据库瓶颈核心是「读多写少/写多读少」,解决方案围绕「读写分离+缓存+分库分表+SQL优化」,按“先缓存→再分离→最后分库分表”的优先级落地;
  • 分层拆解方案
    1. 读性能优化(核心:减轻数据库读压力)
      • 缓存层:热点数据(如商品详情、用户信息)接入Redis,设置合理过期时间+缓存预热+缓存击穿/穿透/雪崩防护;
      • 读写分离:主库写、从库读,通过中间件(MyCat/Sharding-JDBC)实现读写路由,从库可水平扩容;
      • 只读实例:针对超高频读场景,部署多个只读实例,分摊读压力;
    2. 写性能优化(核心:减少写冲突+提升写效率)
      • 分库分表:按用户ID/订单ID哈希分片,拆分大表为小表,减少单表数据量和锁竞争;
      • 批量写入:将高频小批量写入(如日志、埋点)改为批量提交,减少事务次数和IO;
      • 异步写入:非核心数据(如订单日志)通过MQ异步写入,降低接口响应时间;
    3. 基础优化(兜底)
      • SQL优化:避免大表全扫、优化JOIN、控制返回字段;
      • 数据库参数调优:增大InnoDB Buffer Pool、优化日志刷盘策略;
  • 核心结论:先通过缓存扛读压力,再读写分离扩展读能力,最后分库分表解决写瓶颈,配合基础优化兜底。

标准答案

高并发下数据库读写瓶颈按“先轻量后重度”解决:① 读瓶颈:热点数据接入Redis缓存(设置过期时间+布隆过滤器防穿透),主从分离(主库写、多从库读),通过MyCat实现读写路由;② 写瓶颈:按用户ID哈希分库分表拆分大表,高频小写入改为批量提交,非核心数据通过MQ异步写入;③ 基础优化:优化慢SQL(避免全表扫描、控制JOIN表数),调大InnoDB Buffer Pool提升缓存命中率。核心逻辑是“缓存扛读、分库分表扛写、读写分离扩容量”,优先用轻量方案(缓存),再用架构方案(分库分表)。

3. JDK 动态代理与 CGLIB 动态代理的底层实现差异是什么?

回答思路

  • 核心锚定:差异核心是「代理对象创建方式+目标类依赖」,JDK基于接口,CGLIB基于继承;
  • 分层拆解差异
    维度 JDK 动态代理 CGLIB 动态代理
    底层原理 反射机制,生成实现目标接口的代理类 ASM字节码框架,生成目标类的子类
    目标类要求 必须实现接口 无需实现接口,不能代理final类/方法
    代理对象创建 Proxy.newProxyInstance() 创建代理实例 Enhancer.create() 创建子类实例
    方法调用 通过InvocationHandler.invoke() 拦截 通过MethodInterceptor.intercept() 拦截
    性能 反射调用,创建快、执行稍慢 字节码生成,创建慢、执行稍快
  • 核心结论:JDK代理依赖接口,基于反射;CGLIB代理基于继承,基于ASM字节码,两者拦截方式和性能特性不同。

标准答案

JDK动态代理与CGLIB的核心差异:① 底层原理:JDK基于反射,生成实现目标接口的代理类;CGLIB基于ASM字节码框架,生成目标类的子类;② 目标类要求:JDK要求目标类必须实现接口,CGLIB无需接口但不能代理final类/方法;③ 调用方式:JDK通过InvocationHandler.invoke()拦截方法,CGLIB通过MethodInterceptor.intercept()拦截;④ 性能:JDK创建代理快、执行稍慢,CGLIB反之。Spring AOP默认优先用JDK代理,目标类无接口时用CGLIB。

4. Spring AOP 失效除了方法权限问题,还有哪些底层原因?

回答思路

  • 核心锚定:AOP失效核心是「代理对象未被调用」,底层原因围绕「代理创建失败+方法调用绕开代理」;
  • 分层拆解原因
    1. 代理创建失败
      • 目标类未被Spring管理:如手动new的对象(非Bean),无代理实例;
      • 目标类是final类:CGLIB无法生成子类,代理创建失败;
      • 切面切入点配置错误:如切入点表达式(@Pointcut)匹配不到目标方法;
    2. 方法调用绕开代理
      • 内部方法调用:代理对象内部调用自身方法(如A方法调用本类B方法),未经过代理拦截;
      • 静态方法/私有方法:静态方法无法被代理,私有方法JDK/CGLIB均无法拦截;
    3. 容器初始化问题
      • 切面类未被扫描:切面类未加@Aspect/@Component,Spring无法识别;
      • 代理顺序问题:多个切面冲突,导致目标切面未生效;
  • 核心结论:失效本质是代理未创建或调用未走代理,需排查Bean管理、调用方式、切入点配置。

标准答案

Spring AOP失效除方法权限外的核心原因:① 代理创建失败:目标类未被Spring管理(手动new)、目标类是final类(CGLIB无法代理)、切入点表达式匹配错误;② 方法调用绕开代理:代理对象内部调用自身方法(如A调本类B)、调用静态方法/私有方法(无法被拦截);③ 容器初始化问题:切面类未加@Aspect/@Component、多切面顺序冲突。核心是「代理未创建或调用未走代理」,可通过暴露代理(@EnableAspectJAutoProxy(exposeProxy=true))解决内部调用问题。

5. MySQL 中 InnoDB 事务原子性、持久性分别依靠什么机制保证?

回答思路

  • 核心锚定:原子性靠「undo log」,持久性靠「redo log + 双写缓冲区」;
  • 分层拆解机制
    1. 原子性(要么全执行,要么全回滚)
      • 依赖undo log(回滚日志):事务执行时,InnoDB会记录数据修改前的状态到undo log;若事务执行失败/回滚,通过undo log恢复数据到修改前的状态,保证操作全部撤销;
      • 举例:事务修改A=1、B=2,若B修改失败,通过undo log将A恢复为原值,保证原子性;
    2. 持久性(提交后数据不丢失)
      • 核心:redo log(重做日志)+ 双写缓冲区;
      • redo log:事务执行时,先将修改写入redo log(内存+磁盘),提交时通过innodb_flush_log_at_trx_commit=1强制刷盘,宕机后可通过redo log恢复已提交数据;
      • 双写缓冲区:防止数据页部分写(如IO中断),数据页刷盘前先写入双写缓冲区,保证数据页完整性;
  • 核心结论:原子性靠undo log回滚,持久性靠redo log保证提交后可恢复+双写缓冲区保证数据页完整。

标准答案

InnoDB事务原子性和持久性的保证机制:① 原子性:依赖undo log(回滚日志),事务执行时记录数据修改前的状态,失败/回滚时通过undo log恢复数据,保证操作要么全执行、要么全回滚

6. MVCC 在可重复读隔离级别下是如何实现一致性视图的?

思路

  • 抓住核心:Read View 生成时机 + 可见性规则
  • RR:事务第一次 SELECT 时生成一个全局唯一 Read View,之后全程复用
  • RC:每次 SELECT 都生成新的 Read View

答案 在可重复读(RR)级别下:

  1. 事务第一次执行查询时生成一个 Read View,之后整个事务期间不再更新
  2. 通过 Read View 中的活跃事务ID列表、最小/最大事务ID,判断数据版本是否可见;
  3. 即使其他事务提交了修改,当前事务依然使用第一次生成的视图,保证重复读取结果一致。 一句话:RR 是全程共用一个视图,RC 是每次查询新视图。

7. 分布式环境下,本地事务失效如何解决?

思路

  • 本地事务只保证单库原子性,分布式必须用分布式事务
  • 分强一致 / 最终一致

答案

  1. 强一致性:使用 Seata AT 模式(二阶段提交),TM 协调多个 RM,保证所有库要么全提交,要么全回滚。
  2. 最终一致性
    • 本地消息表 + MQ
    • 可靠消息(RocketMQ 事务消息)
  3. 业务长流程:Saga 模式,拆分子事务 + 补偿机制。

8. 如何设计一个支持高并发的分布式 ID 生成方案?

思路

  • 要求:全局唯一、趋势递增、高可用、高吞吐
  • 排除:UUID(太长无序)、自增ID(单点)

答案 成熟方案:雪花算法(Snowflake)+ 双 buffer 优化 结构:

  • 1bit 符号位
  • 41bit 时间戳
  • 10bit 机器ID
  • 12bit 序列号

优化:

  • 机房/业务标识放机器位
  • 本地缓存一段ID,减少远程调用
  • 时钟回拨处理:拒绝/等待/切换机器ID
  • 高可用:多实例部署,无中心化

9. Redis 与数据库双写一致性如何保证?

思路

  • 核心:先更库,再删缓存;不更新缓存
  • 避免并发脏数据

答案 标准方案:

  1. 查询:先查 Redis → 不存在查库 → 写回 Redis
  2. 更新/删除
    • 第一步:更新数据库
    • 第二步:删除 Redis 缓存(不做 set 更新)
  3. 兜底:
    • 缓存过期时间
    • 异步补偿重试(MQ)

10. 线上 JVM 内存泄漏如何定位与分析?

思路

  • 标准流程:dump → 分析 → 定位 → 修复
  • 工具:jmap、jstack、MAT、Arthas

答案

  1. 保留现场:jmap -dump 生成堆转储文件
  2. 分析工具:用 MAT 查看:
    • 大对象
    • GC Roots 引用链
    • 线程栈
  3. 常见泄漏点
    • ThreadLocal 没 remove
    • 静态集合缓存对象不清理
    • 流/连接未关闭
    • 内部类持有外部类引用
  4. 修复:切断引用、及时释放、加池化管理。

11. 线程池参数在高并发场景下如何合理设计?

思路

  • CPU 密集型 / IO 密集型
  • 队列、拒绝策略

答案

  1. CPU 密集型 corePoolSize = CPU核心数 + 1
  2. IO 密集型(接口、RPC、DB) corePoolSize = CPU核心数 * 2
  3. maximumPoolSize:不要过大,防止线程爆炸
  4. 队列:
    • 高并发用有界队列,防止OOM
  5. 拒绝策略:
    • 生产用 CallerRunsPolicy 或自定义抛异常

12. 线上服务频繁 FullGC 如何排查与优化?

思路

  • 先看现象:频繁 / 慢 / 卡顿
  • 定位:堆、元空间、直接内存

答案 排查步骤:

  1. jstat -gc 观察:YGC / FGC 次数、耗时
  2. 看 dump:
    • 大对象
    • 内存泄漏
  3. 常见原因:
    • 堆太小
    • 内存泄漏
    • 永久代/元空间满
    • 直接内存泄漏(Netty)
  4. 优化:
    • 调 Xmx/Xms
    • 修复泄漏
    • 优化大对象、池化

13. 如何设计一个可水平扩展的高可用后端架构?

思路

  • 无状态 → 可水平扩
  • 分层、解耦、限流、降级、熔断

答案

  1. 应用无状态化:不存本地会话,session 放 Redis
  2. 集群化:多实例部署,Nginx/LVS 负载均衡
  3. 缓存层:Redis 集群/哨兵
  4. 数据层
    • 主从 + 读写分离
    • 分库分表
  5. 高可用保障
    • 限流、降级、熔断(Sentinel/Sentinel)
    • 异步解耦 MQ
    • 监控告警 + 自动扩缩容
#JAVA##面经##面试#
全部评论

相关推荐

昨天 23:30
已编辑
小红书_后端开发
请先做一个简单的自我介绍。对于Java中的锁机制,你有什么理解?在悲观锁中,Java语言层面有哪些实现方式?它们之间的区别是什么?synchronized和reentrant lock在等待与唤醒机制上有什么区别?你对线程池的理解是怎样的?在使用线程池执行任务时,一般需要注意哪些问题?如何让主线程感知到线程内部的异常?如果线上应用频繁出现GC问题,可能是什么原因导致的?Spring AOP使用的哪种设计模式及代理方式?在哪些场景下会使用Spring AOP,以及使用时应注意哪些问题?问:InnoDB数据库中的索引使用何种数据结构,B+树和B树有何区别?在MySQL中,如何通过explain查询来分析circle执行计划并找出性能差的原因?当查询涉及到多个字段且索引设计有问题时,该如何排查和优化?对于存储数据量大的表,应如何分析其性能问题并提出解决方案?在处理频繁的修改和查询操作时,如何避免引发性能问题?问实习:在对象存储中,你们采用了哪两种经典方式?当时在测试环境中遇到了什么新问题?为了解决这个问题,你们采取了什么优化措施?....算法题:好像easy还是middle直接秒了(已经好久好久没刷题了 稍微写慢了一点)反问:部门业务大概是什么样的?答:部门属于公司个性化工程平台部,主要负责个性化让利、触达、超级VIP体系以及用户画像和标签数据四块业务。
查看17道真题和解析
点赞 评论 收藏
分享
评论
1
2
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务