它思科技 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要求目标类必须实现接口,CGL

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

本专栏在精不在多,内容分为八股文、大厂真实面经,面试通过后将offer和面试题私发给我,可退还专栏的收益部分费用。欢迎大家共建专栏

全部评论

相关推荐

03-23 20:11
已编辑
百度_高级研发工程师
这篇继续盘点一下后端转 AI 方向面试时,最容易被面试官“扒皮”的几个工程落地场景。全是实打实的干货,希望能帮兄弟们避坑。一、 RAG(检索增强生成)的全链路拆解面试官极其看重你怎么把企业文档变成知识库的。这块如果你只是个纯调包侠(只会调 LangChain 的 API),被稍微一深挖绝对露馅。我跟他完整勾勒了整条数据流水线:从文档解析,到文本切片(Chunking)。这里有个加分项:一定要提**“按长度切分并保留 Overlap(重叠区)”**,这样能保证上下文语义不断裂。至于向量库选型,别干巴巴地只说一个。我给出的方案是:数据量极大、分布式要求高的场景直接上 Milvus;而轻量级、或者需要和传统关系型数据强绑定的场景,用 pgvector。顺带提一嘴查询时用的是“混合检索(Keyword + Vector)”,召回的精准度会靠谱很多。二、 大模型幻觉与 Prompt 约束兜底面试官必问:大模型胡说八道、乱承诺怎么办?对付这个,咱们后端有常规的三板斧:控参数: 调低模型生成时的 Temperature 参数,直接把发散性和创造性压下来。强指令: 在 Prompt 里加入极其严格的系统级指令兜底(比如:“如果你不知道,请直接回答不知道,严禁编造信息”)。引入 Few-Shot(少样本提示),给几个标准的问答 Case,把它的输出格式和边界死死限制住。三、 核心痛点:用 RocketMQ 做异步解耦与削峰AI 接口耗时极长,这是通病。面试时必须明确态度:大模型打分或推理,绝对不能同步阻塞主流程。当时的解法是:用户发消息后,聊天服务只管快速落库,然后立刻往 RocketMQ 里丢一条异步消息返回给前端。后端的打分微服务作为消费者,在后台慢慢跑,调完大模型再去更新数据库。进阶防坑: 面试官听到 MQ 肯定会追问重复消费。记得补一句:“我在消费端的 Java 代码里做了防重,基于业务主键(SessionID + MsgID)在 Redis 里做了 Key 校验,或者在 MySQL 用唯一索引兜底。坚决不能让大模型对同一条记录重复打分,浪费 Token 算力。”四、 全双工流式交互(WebSocket + SSE)解决 AI 响应慢导致用户吃灰的问题,还得靠前端流式输出。我重点聊了用 SSE(Server-Sent Events)和 WebSocket 技术,把大模型的响应“逐字”推给前端,而不是傻等到全部生成完再返回,这样能把首字延迟(TTFB)压到极致。进阶防坑: 聊流式交互必问断线处理。我们在网关层(如 Netty 构建的 WebSocket 集群)加了心跳保活机制(Ping-Pong)。一旦检测到死链接,立刻释放后端线程池资源;同时客户端配合自动重连和断点续传逻辑,保证流式数据不丢字。
查看15道真题和解析
点赞 评论 收藏
分享
评论
1
3
分享

创作者周榜

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