ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

大厂Java面试:Spring Boot自动配置、MySQL索引、Redis扣库存、Kafka消息不丢失、微服务治理与云原生监控——谢飞机智斗铁面面试官

大厂Java面试:Spring Boot自动配置、MySQL索引、Redis扣库存、Kafka消息不丢失、微服务治理与云原生监控——谢飞机智斗铁面面试官

大厂Java面试:Spring Boot自动配置、MySQL索引、Redis扣库存、Kafka消息不丢失、微服务治理与云原生监控——谢飞机智斗铁面面试官

一家互联网大厂的面试间,白炽灯亮得有些刺眼。面试官表情严肃,翻开面前写着“精通Java”的简历,又抬头看了看对面的求职者——谢飞机。谢飞机穿着格子衬衫,头发有点乱,正努力挤出职业微笑。

面试官:“谢飞机是吧?既然是Java岗位,我们直接开始。假设你在做电商下单业务,服务端用Spring Boot。第一个问题:Spring Boot的自动配置原理是什么?它和传统Spring有什么区别?”

谢飞机:“这个我会!Spring Boot用@EnableAutoConfiguration,通过META-INF/spring.factories加载配置类,还有@Conditional条件注解,实现按需加载。比传统Spring省了XML配置,约定大于配置!”

面试官微微点头:“不错,基础还算扎实。那第二个问题,项目里用Maven还是Gradle?说说两者的区别。”

谢飞机:“我们组用Maven,因为公司老项目多。Maven用pom.xml,Gradle用Groovy或Kotlin DSL。Gradle构建快,支持增量构建,但学习成本高。”

面试官:“好的。第三个问题,用户下单后要打印系统日志,如果让你从Logback和Log4j2里选一个,你怎么选?”

谢飞机:“我选SLF4J+Logback,因为Spring Boot默认就是Logback,简单不用配。Log4j2性能更高但要用Disruptor,有点复杂。”

面试官:“能分清门面和实现,第一轮先这样。下面我们深入业务。”

面试官:“订单数据最终要存MySQL,假设订单表有几千万数据。请说说哪些情况下会导致索引失效?”

谢飞机:“唔,查询条件里用了函数、隐式类型转换、左模糊,还有OR连接的时候如果有一个条件没索引,整个可能不走索引。还有!在索引列上做运算也会失效。”

面试官:“说得不错。那为了避免超卖,你们用Redis保存库存。假如一个商品库存有10件,现在10000人同时抢购,你会怎么设计扣库存方案?”

谢飞机:“这个……我会用Redis的decr命令?但是要判断不能变负数。可以用Lua脚本原子操作。呃,好像还得加分布式锁?反正不能先查再减。”

面试官:“你还知道Lua脚本,但临界条件和细节你说清楚了吗?那订单创建后要发Kafka消息通知下游,怎么保证消息不丢失?”

谢飞机:“啊?Kafka不丢失?需要设置acks=all,生产者重试,消费者关闭自动提交,手动提交offset……嗯……还要注意重平衡导致重复消费。但保证不丢失很难吧?唉我有点乱。”

面试官:“能说出部分,但整体不够清晰。我们继续下一个话题。”

面试官:“服务多了以后,需要服务治理。你们用Spring Cloud,服务注册中心选什么?”

谢飞机:“选过Eureka和Consul。Eureka是AP,用在不需要严格一致的场景;Consul是CP,数据一致性更好,还带KV存储。我们后来用的Nacos,呃不过您没问。”

面试官:“好。那还有安全。请说一下JWT和OAuth2到底什么关系?”

谢飞机:“JWT是一种token格式,OAuth2是授权框架。JWT可以当OAuth2的access token用。比如登录后签发JWT,请求时带着,然后Spring Security过滤器解析。”

面试官:“那如果服务部署在Kubernetes里,节点要升级,需要把Pod优雅地排空。你会怎么给Spring Boot应用做优雅停机?直接kill -9行不行?”

谢飞机:“直接kill -9肯定不行,数据会丢。优雅停机要……Spring Boot 2.3之后有graceful shutdown参数?然后还要在K8s里配preStop钩子,让Pod从Endpoints摘掉,再sleep几秒再退出。但具体怎么配合我有点模糊……”

面试官:“最后一个问题,你打算怎么监控这套微服务?”

谢飞机:“用Actuator暴露端点,Micrometer收集指标,Prometheus拉取,然后Grafana画图。还可以用Zipkin/Jaeger做链路追踪。”

面试官:“嗯,这个你倒是背得挺熟。好了,三轮面试结束,你先回去等通知吧。”

谢飞机:“啊?这就结束了?那……我是不是没戏了?”

面试官:“回去等通知,我们会综合评估。”

谢飞机垂头丧气地站起来:“好吧,我回去等‘感谢信’了。”


面试问题答案解析

以下是本次面试中涉及的技术场景与问题详解,结合电商下单、库存扣减、消息通知、微服务治理等业务场景,适合Java小白系统学习。

1. Spring Boot自动配置原理是什么?与传统Spring有什么区别?

答案:

Spring Boot 的核心是“自动配置”。@SpringBootApplication注解包含了@EnableAutoConfiguration,这个注解通过@Import(AutoConfigurationImportSelector.class)去读取META-INF/spring.factories(Spring Boot 2.7 之前)或META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(Spring Boot 2.7 之后)文件中的配置类全名。

然后,Spring Boot 会结合@ConditionalOnClass@ConditionalOnMissingBean@ConditionalOnProperty等条件注解,判断当前类路径下是否存在对应的依赖,以及用户是否已经手动配置过。只有满足条件时,自动配置类才会生效。

业务场景:你在电商订单服务中引入spring-boot-starter-web,Spring Boot 会自动配置 DispatcherServlet、Tomcat、Jackson、ViewResolver 等。如果你自己定义了一个RestController,Spring Boot 就不会再自动装配一个默认的DispatcherServlet,这就是“约定优于配置”。

传统 Spring 需要手动在 XML 中配置组件扫描、数据源、事务管理器等,十分繁琐。Spring Boot 把这些重复劳动通过自动配置消除了。

2. Maven 和 Gradle 的区别是什么?

答案:

  • Maven:基于 XML 的pom.xml描述项目依赖和构建配置。有固定的生命周期(clean、default、site),构建过程相对标准,依赖管理约定俗成。优点:生态庞大,大部分公司都在用;缺点:XML 冗长,构建速度较慢,增量构建能力弱。
  • Gradle:基于 Groovy 或 Kotlin DSL,构建脚本是编程式的,更加灵活。支持增量构建、构建缓存、并行执行、按需构建,所以大型项目构建速度更快。Android 官方默认使用 Gradle。缺点:学习成本高,构建脚本灵活也意味着容易写乱。

业务场景:大厂里很多老系统用 Maven,因为维护成本低;新项目或对构建速度有要求的项目,可能选 Gradle。面试时能说出“Gradle 用 DAG(有向无环图)组织任务,Maven 用线性生命周期”会显得更加专业。

3. Logback 和 Log4j2 怎么选?SLF4J 是什么?

答案:

SLF4J 是日志门面(Facade),它本身不输出日志,只提供统一接口。Logback 和 Log4j2 是具体实现。使用slf4j-api+ 实现框架,可以在不修改业务代码的情况下切换底层日志引擎。

  • Logback:Spring Boot 默认使用 Logback。配置简单,支持自动重载。性能足够应对大多数业务,但异步日志能力弱于 Log4j2。
  • Log4j2:在异步日志方面非常强,基于 LMAX Disruptor 实现无锁异步记录,吞吐量高,也支持无垃圾(Garbage-free)日志。但配置复杂,需要额外引入依赖。

业务场景:电商下单的高并发场景下,如果每笔订单打日志可能造成 IO 瓶颈。此时可以使用 Log4j2 的异步日志,或 Logback 的 AsyncAppender。但要注意,日志框架影响的是系统稳定性,不能因为引入复杂依赖导致启动问题。所以默认推荐 Logback,遇到性能瓶颈再换 Log4j2。

4. MySQL 索引失效的常见场景有哪些?

答案:

在基于 MySQL 的订单表查询中,索引失效意味着全表扫描,性能急剧下降。常见导致索引失效的情况如下:

  1. 对索引列使用函数WHERE DATE(create_time) = '2025-01-01'会导致create_time索引失效。应该改为WHERE create_time >= '2025-01-01 00:00:00' AND create_time < '2025-01-02'
  2. 隐式类型转换:例如phone是 varchar 类型,查询时用WHERE phone = 13800001111,数字会被转换为字符串,可能导致索引失效。
  3. LIKE 以通配符开头WHERE name LIKE '%张'无法使用普通 B+Tree 索引。可以改为LIKE '张%'
  4. OR 连接条件WHERE status = 1 OR price > 100,如果只有status有索引而price没有,整个查询可能会走全表扫描。可以把 OR 改成 UNION 或为price加索引。
  5. 索引列参与运算WHERE price + 100 < 200不会用索引,应把运算移到赋值侧。
  6. 不等值查询WHERE status != 1,优化器可能会放弃索引。
  7. 联合索引不满足最左前缀原则:联合索引(a, b, c),如果直接查bc条件,索引失效。
  8. 优化器判断全表扫描更快:小表或数据显示极小比例时,MySQL 可能故意不走索引。

业务场景:订单查询经常按user_idcreate_time建立联合索引,要避免在create_time上套 DATE 函数,否则千万级订单表会被打垮。

5. Redis 如何设计库存扣减方案来避免超卖?

答案:

可以用 Redis 的DECR命令,但需要配合 Lua 脚本保证原子性。因为多个并发请求“先读后写”会出现资源竞争,导致超卖。

一个基本的 Lua 脚本思路:

local stock = tonumber(redis.call('GET', KEYS[1])) if stock == nil then return -1 -- 表示库存不存在 end if stock <= 0 then return 0 -- 表示已被买完 end redis.call('DECR', KEYS[1]) return 1 -- 扣减成功

为什么用 Lua?因为 Redis 的 Lua 脚本是原子执行的,整个脚本不会中途插入其他命令,天然避免了并发扣减的竞态条件。

更完善的方案:

  • 使用 Hash 结构存储库存和已售数量,方便管理。
  • 扣减成功后,通过消息队列发异步消息,由订单服务持久化到数据库。
  • 数据库最终使用乐观锁(UPDATE stock SET LEFT = LEFT - 1 WHERE GOODS_ID = ? AND LEFT >= 1)兜底。
  • 也可以用 Redisson 分布式锁,但锁会降低吞吐,不如 Lua 脚本高效。

业务场景:秒杀活动中,10000 个用户同时抢 10 件商品,Redis 单线程加 Lua 脚本可以在毫秒级完成原子扣减,而不会出现超卖。

6. Kafka 如何保证消息不丢失?

答案:

Kafka 保证不丢失需要从生产端、Broker 端、消费端三个层面配合。

生产端:

  • 设置acks=all(或-1):Producer 发送消息后,需要等待 ISR 中所有副本都写入成功才返回成功。
  • 设置retries > 0:网络抖动时自动重试。
  • 开启幂等enable.idempotence=true:避免重试导致消息重复。

Broker 端:

  • 设置min.insync.replicas=2(假设副本书为 3),确保至少有一个 Follower 和 Leader 保持同步。如果同步副本数低于该值,Broker 会拒绝写入。
  • unclean.leader.election.enable=false:不允许落后过多的副本成为 Leader,避免丢已提交消息。

消费端:

  • 关闭自动提交:enable.auto.commit=false
  • 在业务处理成功后再手动提交 offset。如果处理失败,不提交 offset,消息会被重复消费(此时需要保证消费幂等)。

业务场景:订单服务产生“订单创建成功”事件,需要发给积分服务、短信服务。如果消息丢失,用户可能不收到积分或短信。使用上述配置可以最大限度保证消息不丢,但还可能出现重复消费,因此消费者要做幂等。

7. Spring Cloud 服务注册中心:Eureka 和 Consul 有什么区别?

答案:

  • Eureka(Netflix OSS):AP 模型,优先保证可用性,容忍节点间数据不一致。服务实例启动时向 Eureka 注册,其他服务通过拉取注册表发现服务。Eureka 通过心跳保活,但服务列表可能出现短暂不一致。Eureka 2.0 已停止开发,Spring Cloud Netflix 也进入维护模式。

  • Consul:CP 模型,基于 Raft 协议保证强一致性。当发生网络分区时,为了一致性可能牺牲可用性。Consul 内置健康检查、KV 存储、多数据中心能力,功能比 Eureka 更丰富。

  • Nacos:国内常用的服务注册中心,既能支持 AP 也能支持 CP,还提供动态配置管理,在很多企业实际项目中替代了 Eureka 和 Consul。

业务场景:如果你希望服务发现很快,且能容忍注册中心之间短暂不一致,Eureka 合适;如果对数据一致性要求很高,比如金融转账服务,可能要求跨节点注册信息完全一致,那 Consul 更好。但实际大厂更多使用 Nacos 或自研注册中心。

8. JWT 和 OAuth2 到底什么关系?

答案:

  • JWT(JSON Web Token)是一种 token 格式,分为 Header、Payload、Signature 三部分。JWT 自包含用户信息,服务端无状态验证,适合认证场景。
  • OAuth2是一种授权框架,解决“第三方应用如何获得用户授权去访问资源”的问题。有四种授权模式:授权码、密码、客户端凭证、简化模式(已弃用)。它不规定 token 必须是 JWT。

关系:JWT 可以作为 OAuth2 的 access token。比如,客户端向认证服务器申请授权,认证服务器签发一个 JWT,客户端拿着 JWT 去访问资源服务器,资源服务器验证签名后放行。

业务场景:电商系统的 Web 端登录可以是“用户名密码换 JWT”,Spring Security 过滤器解析 JWT 并设置 SecurityContext。如果要用微信登录,就是 OAuth2 授权码模式:前端跳到微信授权页,微信回调 code 后,后端用 code 换取 access_token 和用户信息,再生成自己的 JWT。所以两者可以配合使用。

注意:JWT 无法主动失效,除非引入黑名单或短过期时间。如果发生用户注销,需要等 JWT 过期或维护 Redis 黑名单。

9. Kubernetes 中如何实现 Spring Boot 应用优雅停机?

答案:

Kubernetes 在结束 Pod 时,默认向容器中的 1 号进程发送 SIGTERM 信号,然后等待terminationGracePeriodSeconds(默认 30 秒),超时后发送 SIGKILL。如果应用不处理 SIGTERM,进程会被强杀,正在处理的请求会中断,数据可能丢失。

Spring Boot 2.3 以后支持优雅停机,配置:

server.shutdown=graceful spring.lifecycle.timeout-per-shutdown-phase=30s

这样应用接收 SIGTERM 后,会等待处理中请求完成再退出,并停止接收新请求。

但只配置这个还不够。K8s 中 Service 的 Endpoints 列表更新不是瞬间的,如果 Pod 已经退出但流量还在转发,还是会断。因此需要:

  1. 配置readinessProbe(就绪探针),让应用在停止接收流量时返回失败状态。Spring Boot 2.6+ 可以使用livenessreadiness状态组,通过/actuator/health/readiness指示是否 ready。
  2. 配置preStop 钩子:在 SIGTERM 之前执行sleep,例如:
lifecycle: preStop: exec: command: ["sh", "-c", "sleep 10"]

这 10 秒用于让 Pod 从 Service Endpoints 中移除,并把正在处理的请求完成。

  1. 设置terminationGracePeriodSeconds大于应用优雅停机所需的超时时间。

业务场景:电商大促后的服务发版,如果直接 kill -9,会导致大量正在提交的订单丢失。正确做法是:先摘流量,再等存量请求完成,最后进程退出。K8s 的 rolling update 会自动执行这个流程,前提是应用必须支持优雅停机。

10. 如何监控 Spring Boot 微服务?

答案:

主流监控方案是:Micrometer + Spring Boot Actuator + Prometheus + Grafana

  • Micrometer是指标门面,类似 MyBatis 的 API 封装。它提供 Counter、Timer、Gauge、Histogram 等指标类型。
  • Spring Boot Actuator暴露端点和指标。引入spring-boot-starter-actuator后,可以访问/actuator/health检查健康,/actuator/metrics查看 JVM、线程、数据库连接池等指标。
  • 引入micrometer-registry-prometheus后,Actuator 会暴露/actuator/prometheus端点,输出 Prometheus 格式的指标。
  • Prometheus通过配置scrape定时拉取该端点,存储到时间序列数据库。
  • Grafana连接 Prometheus,将指标可视化,还可以设置告警。

业务场景:在订单微服务中,可以自定义一个 Counter 记录“订单创建数”,一个 Timer 记录“下单接口耗时”。监控平台发现下单耗时突增或订单量归零时,马上告警。分布式链路追踪可以用 Jaeger 或 Zipkin,方法是在服务间传递 traceId,把一次请求经过的所有服务串成链路,定位性能瓶颈。

常见配套:

  • 日志监控:Filebeat 收集日志 → Logstash 解析 → Elasticsearch 存储 → Kibana 展示(ELK)。
  • 容器监控:Kubelet 暴露容器指标,Prometheus 拉取,可以监控 CPU、内存。
  • 告警:Prometheus AlertManager 发送钉钉或邮件。

掌握这套方案后,即使服务很多,也能通过“指标 + 日志 + 链路”三维度快速定位问题。


谢飞机最终没有得到“通过”的通知,但这份面试题和他的“半吊子”回答,恰好成了 Java 学习路上的反面教材。如果看到这里的你,能把这 10 个问题认真吃透,下次面对大厂面试官,至少会比谢飞机多一分底气。

返回列表