Java面试问题记录(二)
三、系统设计与问题排查
1、假设你要设计一个 “秒杀系统”,需要考虑高并发、高可用、防超卖等问题,你的整体技术方案是什么?从前端、接口层、服务层、存储层分别说说核心设计点。
秒杀系统设计
设计核心:瞬时高并发,库存防超卖,系统稳定性
1)前端层:秒杀未开始时,按钮置灰,开始后点击一次后置灰,防止用户重复点击。限制单用户每秒请求次数,避免恶意脚本高频请求;将秒杀页面的静态资源(商品图片,HTML,CSS)放入CDN,减少源站压力。
2)接口层:可通过nginx限流,直接拦截恶意请求,或者通过网关限流,Spring Cloud Gateway + Sentinel 实现接口级限流(如秒杀接口 QPS 限制为 5000),超过阈值返回 “请求繁忙”。
3)服务层:首先是消息队列削峰,秒杀请求不直接调用业务接口,而是发送到MQ中,业务服务从MQ消费请求,将瞬时高并发转为异步处理;其次是分布式锁防超卖,用Redis分布式锁确保同一时间只有一个线程操作库存,具体应该先扣减Redis的缓存库存,若缓存库存<=0,直接返回“已抢完”,再异步同步到数据库,减少数据库压力;然后是库存预扣与兜底,在秒杀前,将商品库存预加载到Redis缓存中,避免查询数据库,并在数据库中添加唯一索引,防止因缓存失效导致的重复下单。
4)存储层:秒杀订单按用户ID分表,避免单表写入压力过大,并将热点数据,例如商品库存,秒杀状态等放入Redis中,数据库仅处理最终订单入库和库存同步,在秒杀期间,实现数据库读写分离,订单查询走从库,写入走主库,避免主库压力过载。
2、项目中如果出现接口响应慢(比如超时),你的排查流程是什么?说明可能的瓶颈点和对应的排查工具。
接口响应慢的排查需遵循从客户端到存储层的链路顺序,结合工具定位瓶颈
1)客户端层:检查网络,可以用ping测试客户端到服务端的网络延迟,正常应小于50ms;检查客户端配置,查看是否设置了合理的超时时间,是否存在重试逻辑导致请求叠加,举个例子,用户反馈app下单慢,排查发现用户所在地区网络波动,延迟达500ms,通过cdn调度优化链路。
2)网关层:查看网关日志,分析请求在网关的耗时,判断是否因路由规则复杂,过滤逻辑耗时导致延迟,再检查网关限流,是否因限流策略过严导致请求排队等待。
3)服务层:查看服务调用链路,定位耗时最长的服务;用jstat命令查看GC情况,若YGC频繁或者Full GC耗时过长,说明JVM内存配置不合理。也可以用jmap 查看存活对象,是否有大对象占用内存,导致GC压力大。在代码层面检查是否有耗时的操作,例如循环调用数据库,同步RPC调用等,是否使用异步编程优化。
4)存储层:查看数据库的慢日志,通过执行计划分析耗时的Sql,并检查数据库连接池,是否因为连接池满导致请求等待。
3、分布式系统中,如何保证 “分布式事务” 的一致性?你用过哪些方案(?请对比它们的优缺点,并说说你在项目中选择某方案的原因。
方案 | 实现原理 | 优点 | 缺点 | 实战场景 |
---|---|---|---|---|
2PC(两阶段提交) | ① 准备阶段:协调者通知所有参与者预提交事务;② 提交阶段:所有参与者确认后,协调者通知提交 | 一致性强,实现简单 | 性能差(同步阻塞),协调者单点故障风险高 | 传统金融核心系统(如银行转账),不敏感于性能 |
TCC(Try-Confirm-Cancel) | ① Try:资源检查与预留(如冻结库存);② Confirm:确认执行(如扣减库存);③ Cancel:回滚(如解冻库存) | 性能好(异步非阻塞),灵活性高 | 编码复杂(需手写 Try/Confirm/Cancel 接口),幂等性难保证 | 电商核心链路(订单创建 + 库存扣减),高并发场景 |
SAGA 模式 | 将分布式事务拆分为多个本地事务,按顺序执行,失败时按逆序回滚 | 适合长事务(如订单履约:下单→支付→发货→确认收货),无锁 | 一致性弱(最终一致),回滚逻辑复杂 | 长事务场景(如物流系统、供应链系统) |
本地消息表 | ① 业务服务写本地事务表 + 消息表;② 定时任务扫描消息表,发送消息到 MQ;③ 消费端执行事务并回调确认 | 实现简单,依赖低(仅需数据库和 MQ) | 消息表与业务表耦合,需处理消息重复和超时 | 非核心业务(如订单创建后发送通知、日志同步) |
实战案例:用 TCC 实现 “订单创建 + 库存扣减” 分布式事务
- Try 阶段:
- 订单服务:创建 “待支付” 状态订单(本地事务)。
- 库存服务:检查库存是否充足,若充足则冻结库存(如库存 100 → 冻结 10,可用 90)。
- Confirm 阶段:
- 订单服务:将订单状态改为 “已确认”。
- 库存服务:将冻结库存转为实际扣减(冻结 10 → 扣减 10,库存 90)。
- Cancel 阶段:
- 订单服务:将订单状态改为 “已取消”。
- 库存服务:解冻冻结的库存(冻结 10 → 可用 100)。
- 幂等与重试:通过 “事务 ID” 保证 Confirm/Cancel 接口幂等(重复调用不影响结果),用定时任务重试失败的事务。
4、如果你负责维护一个日活百万的系统,需要做 “性能优化”,你会从哪些维度入手?比如代码层面、JVM 层面、架构层面,请结合具体案例说明。
一般来说,性能优化需遵循 “先定位瓶颈,再针对性优化” 的原则,从三个层面入手:
1)代码层面:通过sql优化或者代码逻辑优化。sql优化包含查看sql是否走了索引,减少sql中的关联查询,使用子查询代替,避免无效sql;代码逻辑方面包含减少一些循环嵌套,用对象池复用频繁创建的对象,如数据库连接,线程池等,避免频繁GC。
2)JVM层面:主要是对一些核心参数的调优,例如堆内存配置设置正确,垃圾收集齐选择正确,元空间和直接内存的参数设置正确等。
3)架构层面:首先是缓存优化,可以使用多级缓存的方式(本地缓存+分布式缓存),热点数据优先查本地缓存,以及对热点数据的预热,在系统启动时,主动加载热点数据到缓存中,避免缓存击穿;然后是异步化,针对非核心流程可采用异步的方式;然后是集群的优化,可以将单服务部署为多实例,通过NGINX或者k8s负载均衡分摊请求,数据库可实现读写分离或者分库分表。
5、谈谈你对 “微服务架构” 的理解?微服务拆分的原则是什么?拆分后会带来哪些问题?你在项目中用什么技术栈解决这些问题?
微服务架构核心理解
微服务架构是将单体应用拆分为多个独立,可独立部署,可独立扩展的小服务,每个服务聚焦于一个业务领域,服务间通过http/gRPC等轻量级协议通信。它的核心优势在于技术栈灵活,不同服务可选用不同语言/框架,且故障隔离,某服务宕机不影响其他服务,还可以按需扩展。
微服务拆分原则
单一职责原则:一个服务只负责一个业务领域
高内聚低耦合原则:服务间依赖尽可能少,通过接口通信,不直接操作其他服务的数据库
领域驱动设计原则:按业务领域拆分,如电商系统拆分为用户域,商品域,支付域,每个域对应一个或多个微服务
避免过度拆分:拆分粒度不宜过细,否则会导致服务间调用复杂,运维成本高
微服务拆分后带来的问题及解决方案
问题 | 解决技术栈 | 实战方案 |
---|---|---|
服务注册与发现 | Spring Cloud Nacos/Eureka | 服务启动时自动注册到 Nacos,其他服务通过 Nacos 获取服务地址(如订单服务通过 Nacos 找到商品服务地址) |
服务调用复杂 | Spring Cloud OpenFeign/gRPC | 用 OpenFeign 声明式调用(如 @FeignClient(name = "goods-service") ),简化 HTTP 调用 |
分布式配置管理 | Spring Cloud Nacos/Apollo | 配置集中存储在 Nacos,服务动态拉取配置(如不同环境的数据库地址),无需重启服务 |
服务限流与熔断 | Spring Cloud Sentinel/Hystrix | 用 Sentinel 为订单服务设置 QPS 限流(如 5000),服务异常时熔断(返回默认数据),避免级联故障 |
分布式事务 | Seata/TCC/SAGA | 用 Seata 框架实现 AT 模式(自动补偿),简化分布式事务开发(如订单服务与库存服务的事务) |
链路追踪 | SkyWalking/Pinpoint | 部署 SkyWalking 代理,追踪服务调用链路,定位跨服务调用的耗时瓶颈(如订单服务→支付服务耗时 1.5s) |
四、综合能力与工程实践(考察经验与职业素养)
1、过去五年的开发经历中,你认为自己做过的最有挑战性的项目是什么?请说说项目背景、你的角色、遇到的核心问题,以及你是如何解决的。
开放性问题,针对自己项目作答
2、在团队协作中,你是如何保证代码质量的?比如代码评审的流程、单元测试的覆盖率要求、使用过哪些静态代码分析工具?如果发现同事的代码有潜在问题,你会如何沟通解决?
CR流程:
前置检查:开发者提交代码前,先自查(是否符合编码规范、是否有注释、是否通过单元测试)。
提交规范:用 Git 提交模板,要求提交信息包含 “类型(feat/fix)+ 模块 + 描述”(如 feat(order): 新增订单取消接口
)
CR 执行:多人评审:至少 1 名资深工程师 + 1 名同模块开发者评审,重点检查逻辑正确性、性能问题、潜在 Bug。工具辅助:用 GitLab CI 自动触发 CR 流程,未通过评审的代码无法合并到主分支。
单元测试与覆盖率
单元测试框架:用 JUnit 5 + Mockito 编写单元测试,Mock 外部依赖(如用 Mockito 模拟 Redis 客户端,避免依赖真实 Redis)。
覆盖率要求:核心业务代码(如订单创建、支付逻辑)覆盖率 ≥ 80%,工具用 JaCoCo 生成覆盖率报告,SonarQube 监控覆盖率达标情况。
3、你平时是如何保持技术学习的?最近半年关注过哪些 Java 生态的新技术?有没有尝试在项目中落地过新技术,或者通过技术博客、开源贡献等方式输出过经验?
开放性问题,针对个人回答
4、假设你负责的服务需要从 “单体架构” 迁移到 “微服务架构”,你会如何制定迁移计划?如何平衡 “业务迭代” 和 “架构迁移” 的优先级?迁移过程中如何保证服务的平稳过渡?
迁移原则
循序渐进:不追求 “一刀切”,分阶段拆分,优先拆分低风险、高收益的模块(如先拆分用户服务,再拆分订单服务)。
业务优先:迁移期间保证业务正常迭代,新功能优先在微服务中开发,旧功能逐步迁移。
灰度发布:每个服务迁移后,通过灰度发布(如 10% 流量切到新服务)验证稳定性,无问题后全量切换。
迁移步骤(总6个月)
1)准备阶段(1个月):搭建微服务基础设施:部署 Nacos(服务注册与配置)、SkyWalking(链路追踪)、Sentinel(限流熔断);定义通用规范:接口规范(RESTful)、代码规范、数据库分库分表规范、日志规范。
2)核心服务拆分(3个月):拆分用户服务(用户注册、登录、信息查询),独立部署,通过 API 网关对外提供服务,旧单体系统调用新服务。拆分商品服务(商品管理、库存查询),与用户服务类似,逐步切换流量。拆分订单服务(核心,难度最高),需解决分布式事务(用 Seata)、库存扣减等问题,先在非峰值时段灰度切换。
3)非核心服务拆分(1个月):拆分日志服务、通知服务(短信、邮件),这些服务依赖少,迁移风险低。
4)收尾阶段:逐步下线单体系统功能,仅保留少量未迁移的旧功能,调整服务集群规模、优化缓存策略、完善监控告警。
平衡业务迭代
优先级排序:新业务需求若与迁移模块相关(如订单服务新功能),优先在微服务中开发;与迁移无关的需求(如后台管理功能),在单体系统中开发,后续再迁移
回滚机制:每个服务迁移后,保留单体系统的旧接口,若新服务出现问题,可通过网关快速切回旧接口(蓝绿部署),保证业务不中断
5、作为一名有五年经验的工程师,你未来 1-2 年的职业规划是什么?是倾向于 “技术专家”,还是 “技术管理”?为什么?
根据个人实际情况回答,下面举个例子
职业定位:技术专家
未来1-2年,倾向于深耕分布式系统架构和性能优化,成为团队的技术专家,而非纯管理岗。
选择原因:
兴趣驱动:相比团队管理,我更享受通过技术解决复杂问题的过程(如秒杀系统高并发优化、分布式事务一致性保障),对底层原理和架构设计有强烈的探索欲。
价值体现:在高并发、高可用场景下,技术专家能直接解决业务痛点(如将系统 QPS 从 1000 提升到 10000),为业务增长提供技术支撑,这种价值感更直接。
团队需求:当前团队微服务架构刚落地,在性能优化、分布式问题排查等方面缺乏资深专家,我希望通过深耕该领域,填补团队技术短板。
1-2年目标与行动计划
技术深耕:深入研究云原生技术(Kubernetes、Istio、Serverless),考取 CKA(Certified Kubernetes Administrator)认证。主导 1-2 个核心项目的架构优化(如将现有微服务迁移到 Kubernetes,实现弹性伸缩)。
团队赋能:编写《分布式系统问题排查手册》,沉淀团队经验,降低新人学习成本;每月组织 1 次技术分享,主题涵盖性能优化、架构设计等,提升团队整体技术水平。
外部输出:在行业会议(如 ArchSummit)分享微服务实践经验,提升个人行业影响力。参与开源项目(如 Spring Cloud Alibaba),贡献代码或文档,拓展技术视野。