大厂Java面试实录:从Spring Boot到微服务、缓存、消息队列、分布式事务与监控——谢飞机历险记
“下一个。”面试官王工头也不抬,盯着简历上的名字,“谢飞机?这名字有点意思。”
谢飞机搓着手走进来,西装皱巴巴的,领带歪到一边,嘿嘿一笑:“王工好,我是来面Java后端开发的。”
王工推了推眼镜,目光锐利:“看你简历写了电商项目,咱们就聊聊电商。先说说你项目里负责的核心模块吧。”
第一轮:项目与底层基础
王工:别紧张,先介绍下你最近做的电商项目,你负责了哪些模块?
谢飞机:啊,就是那个……卖货的电商平台。我负责订单、商品还有支付这一块。主要是写接口,用Spring Boot,然后数据库用MySQL,缓存用Redis……
王工:(皱眉)具体一点,订单状态是怎么流转的?支付回调怎么处理?
谢飞机:订单状态就是……待付款、待发货、已发货、已完成,还有已取消。支付回调就是微信/支付宝回调,改订单状态,但有时候会重复通知,我当时用了……好像是幂等?哎呀,反正就是判断一下状态。
王工:(不置可否)那你说说,Spring Boot 和 Spring MVC 有什么区别?
谢飞机:(眼睛一亮)这个我知道!Spring Boot 是一个快速开发框架,内置了Tomcat,能自动配置,不用写一堆XML。Spring MVC 是Spring里的Web层框架,处理HTTP请求的,比如Controller、DispatcherServlet那套。Spring Boot 底层其实就是用Spring MVC,但帮我们把配置都弄好了。
王工:(微微点头)嗯,基础还行。那订单表是怎么设计的?有哪些核心字段?
谢飞机:订单表……肯定有order_id主键,user_id用户ID,然后总金额total_amount,状态status,下单时间create_time。还要一个订单明细表order_item,存商品ID、数量、单价。如果按微服务拆,订单服务和商品服务是分开的。
王工:订单表数据量大了怎么办?比如单表到了几千万。
谢飞机:(挠头)就分库分表吧?比如按用户ID取模,分成几个库几个表。但具体怎么分……我们当时用了ShardingSphere?还是MyCat来着?反正就是路由到不同的表。要是数据再大,可能要一致性哈希?具体我记不太清了。
王工:好,第一轮先到这里。我们进入第二轮。
第二轮:高并发、缓存与消息队列
王工:好,既然你们是电商,那肯定有商品详情页和下单场景。我有个商品详情查询接口,QPS很高,用户一看爆款商品,数据库就扛不住了。你会怎么优化?
谢飞机:加缓存!用Redis缓存热点商品数据,先查Redis,没有就查数据库再回填。这个我会。
王工:那如果用户恶意刷数据,频繁查询一个不存在的商品ID,每次都穿透到数据库怎么办?
谢飞机:(楞了一下)那就……用布隆过滤器?把所有商品ID放到布隆过滤器里,查不到就直接返回404。布隆过滤器可能会有误判,但不会漏掉。……嗯,就是会有一定概率把不存在的东西误判为存在,但存在的一定不会被判断为不存在。不过实现起来有点麻烦,我们当时好像直接设置null缓存了。
王工:(眼角抽动)行。那假设现在有一万个用户同时抢购一个手机,库存只有100台,你怎么防止超卖?
谢飞机:这个我熟!用Redis的原子操作!减库存用DECR,判断返回值,如果大于等于0就成功,否则恢复库存。或者用乐观锁,在数据库更新时加个版本号,update……where version = #{version}。但这样可能会失败很多次,还是Redis靠谱。
王工:不错。下单成功之后,系统还需要发短信通知用户、给用户加积分、通知财务系统。你怎么办?
谢飞机:发消息队列!我用Kafka。下单成功之后,把订单事件发到Kafka,然后短信服务、积分服务、财务系统各订阅各的。
王工:那Kafka宕机或者消息丢了怎么办?
谢飞机:(开始冒汗)Kafka不是有副本机制吗?Leader挂了会重新选主。生产端可以设置acks=all,消费端手动提交offset,这样就不会丢了吧?但是……如果处理到一半,服务重启了,那可能会重复消费。所以要幂等。嗯,大概是这样。
王工:那用一个更复杂的场景:订单创建和库存扣减,这两个操作要保证要么都成功,要么都失败。比如用户下单,订单数据写入订单库,同时扣减库存库的库存。你怎么保证一致性?
谢飞机:(眼神飘忽)这个……可以用分布式事务。我们用过Seata,有AT模式,就是自动补偿。还有TCC,就是Try-Confirm-Cancel,但TCC很难写。我当时主要负责订单侧,库存是另一个团队管的,我们通过MQ发送“扣减库存”消息,然后如果失败就发一条“回滚”消息……
王工:(叹气)好,时间关系,进入第三轮。
第三轮:微服务、安全与运维
王工:你们服务是怎么拆分的?服务发现用什么?
谢飞机:我们用了微服务,有订单服务、用户服务、商品服务、支付服务这些。服务发现最开始用的Eureka,后来换了Nacos。因为Eureka已经停止维护了,Nacos还有配置中心功能。
王工:Eureka和Nacos核心区别是什么?
谢飞机:Eureka是AP模型,Nacos支持AP和CP,可以做配置中心。还有Nacos支持临时和持久化实例……持久化实例用Raft协议,临时实例用临时节点。嗯……大概就是这样吧。
王工:用户登录认证,你用的什么?密码怎么存的?
谢飞机:Spring Security加JWT。用户登录成功就返回一个JWT token,之后请求带上token,我们解析验证。密码用BCrypt加密,不能明文存。还有授权用OAuth2,但那个流程我有点记不清,大概就是授权码发给前端,然后换取token……
王工:(顿了一下)假设线上有个订单服务突然CPU飙升到100%,你怎么排查?
谢飞机:用top命令看进程号,然后top -Hp看线程,再用jstack导出线程栈,找“RUNNABLE”的线程,看看是不是在GC或者有死循环。还要看一下是不是有Full GC频繁。
王工:那你们的项目怎么部署?怎么做健康检查?
谢飞机:用Docker打包镜像,然后用Kubernetes部署,做pod副本。健康检查的话,Spring Boot有Actuator,暴露/actuator/health端口,然后K8s配置liveness和readiness探针,去请求这个端口。如果返回UP就是健康,DOWN就是不健康,然后重启。
王工:(沉默良久)好,谢飞机,今天面试就到这吧。你的情况我已经了解了,后续有结果会通知你,你先回去等通知吧。
谢飞机:(站起身,挠头)好的好的,王工,您看我还有机会吗?
王工:(挤出微笑)等通知吧。
答案与知识点详解:让小白也能学会的面试题全解析
以下就是谢飞机面试中每道题的完整答案,包括业务场景、技术原理、以及在实际大厂中的应用。请小白同学逐一对照学习。
第一轮详解
1. 请介绍一个电商项目,负责的核心模块是什么?
业务场景:电商系统一般包含用户(用户注册/登录/个人信息)、商品(分类/详情/库存)、订单(购物车/下单/状态流转)、支付(对接微信/支付宝)、营销(优惠券/秒杀)、物流(发货/轨迹)等模块。面试官问这个问题,不是想听你背“我负责写了CRUD”,而是想了解你能否将业务模块和对应的技术方案关联起来。
标准回答思路:
- 一句话介绍项目:这是一个B2C电商平台,包含用户端、商家端、后台管理,日活xx万,订单量xx万。
- 说出自己负责的模块:我主要负责订单、商品、支付。
- 说出每个模块的核心功能和挑战:比如订单要处理状态机,支付要处理回调幂等,商品要支撑高并发查询。
- 技术栈:Spring Boot + MySQL + Redis + Kafka + Nacos + Docker/K8s。
知识点:不要只罗列“我写了什么”,要突出“我怎么解决业务难题”。例如:“支付回调我使用了Redis分布式锁 + 状态机校验,保障了重复通知情况下的幂等性。”
2. Spring Boot 和 Spring MVC 有什么区别?
技术点:Spring MVC 是 Spring Framework 的一部分,是一个 Web MVC 框架,提供了控制器(Controller)、视图解析器(ViewResolver)、处理器映射(HandlerMapping)等组件,用于处理 HTTP 请求和响应。Spring Boot 是一个“快速开发脚手架”,它基于 Spring Framework,通过自动配置简化了 Spring 应用的搭建和部署,内嵌 Tomcat/Jetty,无需外置容器,并且提供了大量的 Starters 来快速整合第三方库。
核心对比:
- Spring MVC 只是 Spring 中的一个模块(或者独立项目),你仍然需要配置 XML 或 JavaConfig 来装配 Bean、开启注解驱动等。
- Spring Boot 可以看成是“Spring 全家桶的整合器”,默认内置了 Spring MVC 作为 Web 层,只要添加一个
spring-boot-starter-web依赖,你就能得到一个可运行的 Web 应用。 - 面试加分话术:Spring Boot 的自动配置原理基于
@EnableAutoConfiguration和META-INF/spring.factories(或新的AutoConfiguration.imports),它会根据 classpath 下的依赖自动创建对应的 Bean。
3. 订单表怎么设计?核心字段有哪些?
业务场景:订单表通常分成两层:订单主表(order)和订单明细表(order_item),这是因为一个订单可能包含多个商品,订单本身的信息(总金额、状态、收货信息)和商品条目是 1:N 的关系。
核心字段设计:
订单主表t_order:
id主键(常使用雪花算法生成,避免使用自增主键,因为分库分表后全局唯一)order_sn业务订单号(用户可读,对外展示)user_id用户ID(分库分表键)actual_amount实付金额total_amount总金额discount_amount优惠金额status状态(0待支付 1已支付 2已发货 3已完成 4已取消 5已退款)address_snapshot收货地址快照(避免地址修改影响订单)create_time、update_time时间字段
订单明细表t_order_item:
id主键order_id订单主表IDgoods_id商品IDgoods_name商品名称快照(防止商品改价/改名影响订单)goods_price下单时单价quantity数量total_price小计金额
技术点:
- 快照字段(商品名、地址)是面试官关心的,因为订单一旦生成,必须保留当时的商品信息和地址。
- 状态字段用tinyint或int,不用枚举字符串,节省空间且查询快。
- 永远不要使用物理外键,应用层保证一致性,这样分库分表时才不会受限。
4. 订单表数据量大了,怎么分库分表?
业务场景:单表数据超过几千万,查询和写入性能会急剧下降。分库分表常见中间件有 ShardingSphere、MyCat,但大厂通常自研或者改造,因为中间件有性能损耗和分布式事务问题。
核心方案:
- 垂直分库:把一个大的订单数据库拆成订单库、用户库、商品库,微服务独立数据库。
- 水平分库分表:按订单 ID 或用户 ID 取模(
user_id % 64),分成 64 张表,或者 64 个库。取模简单但扩容需要迁移数据;一致性哈希可以支持动态扩容,但会有虚拟节点和迁移问题。 - 常见的分片键是
user_id,因为用户查询自己的订单最频繁。但如果运营后台需要查全表订单,就要通过中间件穿行聚合。 - 分片后无法使用数据库主键自增,所以需要分布式 ID,常见方案:雪花算法(Sawngflower)、美团 Leaf、滴滴 Tinyid。
面试加分话术:除了分库分表,我还会讲读写分离,主库负责写,从库负责读。MyCat/ShardingSphere 同时支持读写分离和分片。分库分表带来的问题:分布式事务、跨节点 Join、分页/排序、唯一性约束,需要通过“全局唯一主键”、“冗余字段”、“字段冗余”或“应用层聚合”来解决。
第二轮详解
1. 商品详情查询慢,怎么优化?缓存穿透、缓存雪崩、缓存击穿分别怎么解决?
业务场景:热门商品详情页的 QPS 可能达到每秒几万甚至几十万,数据库不堪重负,必须在 Redis 上做高并发缓存。
标准优化思路:
- Redis 缓存热点数据:
Redis cache-aside,读请求优先查 Redis,没有则查数据库,回填 Redis,并设置过期时间。 - 使用本地缓存(Caffeine)做多级缓存,减少 Redis 网络 IO。
- 缓存预热:通过后台任务或定时任务,在秒杀开始前把商品信息加载到 Redis。
- 对于数据库,可以使用索引优化、读写分离、连接池调整、慢 SQL 日志监控。
需要背熟的三类经典问题:
缓存穿透:查询一个不存在的 key,每次都会穿透到数据库。
- 解决①:缓存空值。将 null 也缓存起来,设置短过期时间,比如 5 分钟。
- 解决②:布隆过滤器(Bloom Filter)。将所有可能存在的商品 ID 存入布隆过滤器,查不到直接拦截。注意布隆过滤器有误判,但误判会导致请求到 DB,不会产生穿透。
缓存击穿:一个热点 key 过期瞬间,大量并发请求同时打向 DB。
- 解决:互斥锁(分布式锁)。在缓存过期后,只有拿到锁的线程能查 DB 并重建缓存,其他线程等待或读取旧值(逻辑过期),避免全部打爆 DB。
缓存雪崩:大量 key 在同一时间过期,或 Redis 宕机,导致所有请求冲向 DB。
- 解决①:过期时间设置随机值,比如
3600 + Random(600),避免同一时间集体失效。 - 解决②:Redis 高可用,使用哨兵或集群模式,主从切换。
- 解决③:服务降级,加限流,DB 侧做熔断保护。
- 解决④:持久化+快速恢复,AOF/RDB。
谢飞机虽然答出了“布隆过滤器”,但实际工作一定要会原理:布隆过滤器是一个 bit 数组,添加元素时用多个 hash 函数将对应 bit 设为 1;查询时检查这些 bit,只要有一个为 0,则一定不存在,全部为 1 则可能存在。不能删除,因为多个 hash 位可能重叠。
2. 高并发下如何防止超卖?
业务场景:秒杀,库存只有 100,但下单请求有 1 万。超卖就是卖出的数量超过了库存,这是绝对不允许的。
常用方案(要按性能从高到低排序):
方案一:Redis 原子扣减(性能最高,最常用)
Long stock = redisTemplate.opsForValue().decrement("stock:10086", 1); if (stock < 0) { // 扣多了,恢复并拒绝 redisTemplate.opsForValue().increment("stock:10086", 1); return "已售罄"; }注意:这种方式扣减的是 Redis,不是 DB。后续需要异步将实际扣减持久化到 DB,但要保证最终一致。如果 Redis 和 DB 不一致,比如 Redis 扣了但 DB 扣减失败,需要可靠消息或对账去补偿。
方案二:数据库乐观锁
UPDATE t_stock SET stock = stock - 1 WHERE goods_id = #{goodsId} AND stock > 0;如果影响行数为 0,说明库存不足,返回失败。由于这条 SQL 是原子性的,数据库行锁保证了并发安全。但注意这里实际上是在数据库层面串行化了,性能不如 Redis。
方案三:数据库悲观锁(for update)
SELECT stock FROM t_stock WHERE goods_id = #{goodsId} FOR UPDATE;锁住行,防止其他事务修改。但会阻塞,性能最差,不推荐高并发场景。
面试加分话术:在真正秒杀场景中,还会结合“限流”、“令牌桶”、“脱机排队”、“异步削峰”等手段。用户点击秒杀后,请求先到达 MQ,由后台 worker 消费队列再完成真正的下单扣库存,这样可以保护订单和支付服务。
3. 下单成功后要通知多个系统,怎么做?Kafka 怎么保证消息不丢失?
业务场景:订单微服务创建订单成功后,需要发送“订单创建成功事件”到 Kafka。短信服务、积分服务、财务服务各自订阅该事件。这样订单服务不需要同步调用这些服务,实现了服务解耦和削峰填谷。
消息队列选型:Kafka 适合高吞吐大众场景;RabbitMQ 适合低延迟高可靠性;RocketMQ 支持事务消息(适合订单与库存一致性场景)。大厂常使用 RocketMQ 或 Kafka,面试中提 Kafka 即可。
Kafka 消息不丢失的三方保证:
生产者端:
- 使用带回调的 send 方法,比如
send(record, callback),失败则重试。 - 设置
acks=all或acks=-1,意味着所有副本都写入成功才算成功。 - 设置
retries(重试次数)和enable.idempotence=true(幂等性,防止重试导致重复消息)。
- 使用带回调的 send 方法,比如
Broker服务端:
- 设置
replication.factor >= 3(至少3个副本)。 - 设置
min.insync.replicas >= 2(至少2个副本同步)。 - 注意:acks=all 配合 min.insync.replicas,才能保证 leader 挂了之后数据不会丢。
- 设置
消费者端:
- 默认 Kafka 使用的是“自动提交 offset”,在消息处理完成前就提交了 offset,如果 consumer 挂了,消息可能丢失。
- 需要设置为
enable.auto.commit=false,并在业务处理完成后手动提交 offset。 - 同时要保证消费逻辑的幂等性,因为手动提交也可能出现“处理成功但提交失败,导致下次重复拉取”的情况。
面试中容易混淆的点:消息“不重复”和“不丢失”是两个问题。Kafka 可以做到 At Least Once(至少一次)或 Exactly Once(精确一次)——需要幂等和事务 API。面试官问到,你要回答:“我通过幂等设计(比如业务表唯一约束、Redis setnx)来应对重复消息。”
4. 分布式事务如何保证订单和库存一致?
业务场景:电商下单涉及订单服务、库存服务,可能还有优惠券服务、钱包服务。订单写订单库,库存扣减库存库,两者分属不同数据库,本地事务无法覆盖。
主流解决方案:
方案一:2PC(两阶段提交,X/Open Distributed Transaction Processing):
- Prepare 阶段:事务管理器询问所有参与者“可以提交吗?”
- Commit/Abort 阶段:如果所有参与者都返回成功,则全局提交;只要有一个人失败,则全局回滚。
- 问题:同步阻塞、协调者单点、数据不一致风险。Spring 也支持 JTA(Java Transaction API)实现 2PC,但是性能差,互联网大厂很少用。
方案二:TCC(Try-Confirm-Cancel):
- Try:尝试执行,完成业务检查,锁定预留资源。例如库存服务先冻结库存。
- Confirm:确认执行,用 Try 阶段锁定的资源完成真正扣减。
- Cancel:取消执行,释放 Try 阶段的资源。
- 优点:最终一致,无需长事务;缺点:侵入性强,需要每个业务接口实现 Try/Confirm/Cancel 三套逻辑。
方案三:可靠消息最终一致性(本地消息表 + 消息队列或事务消息):
- 订单服务在本地事务中,将“扣库存事件”插入本地消息表,然后执行订单创建。
- 本地事务提交后,后台进程把消息表中的消息发送到 MQ。
- 库存服务消费 MQ 执行扣库存,执行成功后发送 ack 给订单服务,订单服务更新消息状态。
- 如果库存服务执行失败,可以重试,或者通过 Kafka/RocketMQ 的“事务反向接口”来触发补偿。
- RocketMQ 原生支持事务消息(半消息 + 本地事务状态回查),用起来最方便。
方案四:Seata AT 模式:
- Seata 是阿里开源的分布式事务框架,AT 模式本质上是 2PC 的演进——它通过代理数据库数据源,自动记录前后镜像和全局锁,在业务代码中看起来还是只用了一个 @GlobalTransactional 注解。
- 第一阶段:业务数据操作 + 回滚日志(undo_log)在同一个本地事务提交。
- 第二阶段:如果全局提交,删除 undo_log;如果全局回滚,根据 undo_log 反向补偿。
- 优点:对业务侵入很小;缺点:有全局锁,并发较低,不适合秒杀场景。
面试回答示范:我会说,在秒杀场景中,我们不追求强一致性,而是采用最终一致性。下单时先扣减 Redis 库存(性能),然后发送“订单创建消息”和“扣减库存消息”。如果扣库存失败,通过定时对账任务发现不一致,再执行回滚。针对要求强一致的资金类场景,才会用 TCC 或 Seata。
谢飞机的回答过于含糊,他甚至把 TCC 和 MQ 混在一起。面试官希望他能清楚地说出“TCC 三个阶段各自做什么”“为什么 MQ 方案能保证最终一致”“分布式事务的优缺点”。
第三轮详解
1. 微服务怎么划分?Eureka 和 Nacos 有什么区别?
业务场景:大厂微服务通常按“业务能力”或“领域”划分,例如用户、商品、订单、支付、物流、营销等。每个服务独立数据库,团队独立开发和部署。
服务发现组件:
- Eureka(Netflix OSS 成员):AP 模式,强调可用性,允许出现短暂的不一致。所有节点平等,任何节点挂了不会影响服务注册发现,但可能有一个服务实例被另一个服务拉取不到的情况。
- Consul:CP 模式,基于 Raft 协议,强一致性,但可用性稍差。
- Nacos(阿里):同时支持 AP 和 CP,默认 AP。服务端支持临时实例(AP)和持久化实例(CP),同时内置了小工具做配置管理。
- Zookeeper:CP 模式,经典的一致性保证,但作为服务注册中心时,遇到网络分区可能导致不可用。
Nacos 相比 Eureka 的核心优势:
- Nacos 负载均衡更灵活,支持 gRPC 和权重;Eureka 更简单。
- Nacos 有配置中心功能,支持配置的动态发布和监听。
- Eureka 2.x 已经停止开发,而 Nacos 还在快速迭代。
- Nacos 支持服务端主动检测实例状态,而 Eureka 只靠客户端心跳。
面试回答要点:说清楚 CAP 模型,以及为什么服务注册中心通常偏 AP(注册中心短暂不一致没关系,但要保证可用,否则服务间调用直接失败);但配置中心必须是 CP(配置不一致会出大问题)。
2. 用户登录认证:JWT、OAuth2、密码加密
业务场景:在电商系统中,用户登录后访问个人中心、下单、支付等接口都需要身份认证和权限控制。常见技术栈是 Spring Security + JWT + Redis,实现登录、token 刷新、登出、权限校验。
JWT(JSON Web Token):
- 分为 Header、Payload、Signature 三部分,用 Base64Url 编码。
- Header 包含算法和 Token 类型;Payload 包含
sub(用户ID)、iat(签发时间)、exp(过期时间)以及自定义权限。 - Signature 用服务端密钥对 Header+Payload 做哈希签名(HS256)或 RSA 签名(RS256)。
- 优点:无状态,服务端不需要存储 session,适合分布式微服务。
- 缺点:无法主动让 token 失效(黑名单需要额外 Redis),payload 泄露风险,密钥要放安全环境。
OAuth2 授权流程(标准 Authorization Code 模式):
- 用户访问客户端,客户端把用户引导至授权服务器。
- 用户登录并同意授权,授权服务器返回一个授权码 code 给客户端(回跳参数)。
- 客户端拿着 code 自己 + client_id + client_secret 向授权服务器请求 token。
- 授权服务器返回 access_token(短期有效)和 refresh_token(长期有效)。
- 客户端使用 access_token 调用资源服务器接口。
- access_token 过期后,通过 refresh_token 获取新的 access_token。
密码加密:
- 绝不能 MD5 直接存,更不能明文。因为 MD5 速度太快,可以被碰撞、彩虹表攻击。
- BCrypt 是合适的哈希算法,自带盐和随机扰动,每次加密结果不同,强度可调。Spring Security 的
BCryptPasswordEncoder可以直接使用。 - 不要用 SHA-256 直接哈希,因为无盐容易字典攻击。
谢飞机说“BCrypt”是加分项,但说不清 OAuth2 流程是减分项。大厂面试官会问“JWT 如何解决登出问题和 token 自动续期”,你需要答:JWT 存 Redis,key 为用户ID,value 为当前 token,登出时删除;每次请求校验 Redis 中的 token 是否一致;或者引入 refresh token 实现滑动过期。
3. 线上服务 CPU 飙升,如何排查?
这是一个高频运维题,标准排查步骤:
top命令查看哪个进程 CPU 占用最高。- 如果是 Java 进程,记录 PID。
- 用
top -Hp <PID>查看进程内哪个线程 CPU 最高,记录线程 TID(十进制)。 - 将 TID 转换为十六进制:
printf "%x\n" <TID> - 使用
jstack <PID> > stack.txt导出线程栈。 - 在 stack.txt 中查找对应的十六进制线程 ID,即可看到运行栈。常见问题:
- 业务代码死循环(比如
while(true)) - Object.wait / LockSupport.park 异常
- 频繁 GC:线程卡在 GC 上,或
GC task thread#0占用高
- 业务代码死循环(比如
- 用
jstat -gcutil <PID> 1000查看 GC 情况,如果 Full GC 频繁,说明堆内存不足或内存泄漏。 - 用
jmap -dump:format=b,file=heap.hprof <PID>导出堆,使用 MAT/JProfiler 分析。
大厂中还可能遇到:线程池配置不当导致创建了大量线程、JIT 编译异常、JVM 老年代碎片等。最好回答时带上“我上次排查了一个问题:是某服务中一个批量任务死循环导致,修复后 CPU 下降。”
4. Docker 和 Kubernetes 部署,健康检查怎么做?
业务场景:现代 Java 应用打包成 Docker 镜像,使用 Kubernetes 部署,包括 Deployment、Service、Ingress、ConfigMap、Secret 等资源。为了保证服务滚发布时不断流量,K8s 需要知道应用是否就绪,因此要有健康检查机制。
Spring Boot Actuator:
- 添加
spring-boot-starter-actuator依赖后,开放/actuator/health端点。 - 默认返回
{"status":"UP"}或{"status":"DOWN"}。 - 可以启用详细健康信息:
management.endpoint.health.show-details=always - 也可以自定义 HealthIndicator,比如检查 Redis、数据库连接、Kafka 连接等。
- 其他端点:
/actuator/info、/actuator/metrics、/actuator/prometheus(配置 Micrometer 后暴露给 Prometheus)。
K8s 探针:
- livenessProbe(存活探针):决定容器是否被重启。如果 liveness 失败,Kubelet 会杀掉容器并重启。
- readinessProbe(就绪探针):决定 Service 是否将流量转发到 Pod。如果 readiness 失败,Pod 会被从 EndpointList 中剔除,不再接收请求。
- startupProbe(启动探针):保护慢启动容器,在启动期间内不触发 liveness,避免老年代 GC 导致探针失败被重启。
配置示例:
livenessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 5 periodSeconds: 5面试加分话术:不使用/actuator/health而使用/actuator/health/readiness和/actuator/health/liveness时,需要单独配置。但实际上默认端口只暴露 health,且如果依赖不可用,health 返回 DOWN,会导致业务正常启动但无法接管流量。大厂一般会做更精细的探针:liveness 只检查进程是否存活,readiness 检查业务依赖(DB、Redis、MQ)是否可用。
总结:谢飞机的心路历程
面试官王工的每个问题,其实都围绕着一个真实的电商高并发业务场景:
- 项目与领域模型:考验你是否做过系统设计。
- 数据库与分库分表:考验你的数据层基本功。
- 缓存与并发控制:考验你面对高并发的能力。
- 消息队列与分布式事务:考验你拆解复杂业务、保证最终一致性的能力。
- 微服务与安全:考验你的服务治理意识。
- 故障排查与云原生部署:考验你线上运维能力。
谢飞机能答出“Spring Boot 和 Spring MVC 区别”“Redis 防超卖”“JWT/BCrypt”,说明他有一定的刷题积累。但一旦涉及具体细节,比如“布隆过滤器原理”“Kafka 三端可靠性”“TCC 三个阶段”“Eureka 与 Nacos CAP 模型”“CPU 排查完整步骤”,他就只能含糊其辞,这正是大厂面试中的大忌——面试官要的不是你背过名词,而是你真的能落地解决这些问题。
希望你通过学习这篇文章,不只学会了“标准答案”,更能理解背后的业务场景和技术原理。下一次面试官问出同样的问题,你就能像谢飞机一样自信地——只不过这次,你要真的会。
最后,王工很客气地让谢飞机回去等通知。你知道,这通常是面试已通过的人才会收到的短信。
加油,Java 打工人。