面试官与谢飞机的Java大厂面试对决:从Spring Boot到微服务架构

场景:某互联网大厂会议室,阳光透过百叶窗洒在长桌上。一位表情严肃、戴着眼镜的面试官(简称“面”)正翻看着简历,对面坐着的是自称“全栈通”的求职者——谢飞机(简称“谢”),他穿着格子衫,头发略显凌乱,眼神中透着一丝紧张和自信交织的光芒。


第一轮:基础不牢,地动山摇?

面:谢先生,请介绍一下你在项目中使用 Spring Boot 的经验。

谢:当然!我用过 Spring Boot 做过用户中心系统,整合了 MyBatis 和 Redis,还用了 Swagger 生成 API 文档。

面:很好。那你说说,Spring Boot 自动装配是怎么实现的?

谢:这个……就是 @SpringBootApplication 注解,它包含了 @EnableAutoConfiguration,然后 Spring 就会自动加载配置类。

面:嗯,那这些配置类是从哪来的?

谢:应该是从 META-INF/spring.factories 文件里读的吧?我记得有个 key 叫 org.springframework.boot.autoconfigure.EnableAutoConfiguration。

面:不错,看来你看过源码。那你来说说,如果我想自定义一个 Starter,该怎么做?

谢:呃……建个 Maven 工程,写个配置类,再在 spring.factories 里注册一下就行了吧?

面:基本思路是对的。不过要注意条件装配,比如 @ConditionalOnClass、@ConditionalOnMissingBean,这样才能避免冲突。

谢:哦对对对,条件注解我也用过!

面:很好,继续下一个问题。


第二轮:进阶挑战,水货现形?

面:假设我们现在要做一个电商秒杀系统,你会如何设计缓存层?

谢:用 Redis 啊!把商品信息、库存都放进去,速度快。

面:如果缓存雪崩了怎么办?

谢:雪崩?就是很多 key 同时失效吗?那我可以给不同的 key 设置随机过期时间,比如基础时间加上 0~300 秒的随机值。

面:还可以呢?

谢:嗯……可以用多级缓存,本地缓存加 Redis,这样即使 Redis 挂了,还有本地兜底。

面:那缓存穿透呢?

谢:穿透?就是查不到的数据一直打到数据库?那我可以用布隆过滤器提前拦截无效请求!

面:很好。那如果布隆过滤器误判了怎么办?或者数据更新后怎么同步?

谢:这……一般不会误判吧?至于更新,我可以让服务在更新数据库的同时,也更新缓存……

面:那你有没有考虑过双写一致性问题?

谢:双写?应该……问题不大吧?

面:……


第三轮:微服务风云,谁能笑到最后?

面:现在系统要拆分成微服务架构,订单、用户、商品各自独立部署。订单服务调用用户服务获取用户信息,你怎么保证调用的稳定性?

谢:用 OpenFeign 调用呗,简单方便。

面:如果用户服务响应慢或者宕机了呢?

谢:那……订单就失败呗。

面:用户体验很差。有没有更好的方案?

谢:可以加个超时设置!Feign 支持设置连接和读取超时。

面:还不够。我们希望即使下游服务不可用,也能返回降级数据。

谢:降级?是不是可以用 Hystrix?我记得它可以做熔断和降级。

面:Hystrix 已经停更了,现在主流是什么?

谢:啊?那……Sentinel?Resilience4j?我听说过,但没用过……

面:Resilience4j 是基于函数式编程的轻量级容错库,适合 Spring Boot 微服务。你可以结合 OpenFeign 使用,通过切面或装饰器实现重试、熔断、限流。

谢:原来如此!学到了学到了!


面:今天的面试就到这里。你的基础知识还算扎实,但在高并发、分布式系统的设计上还需要加强。我们会综合评估,后续 HR 会通知你结果。

谢:好的好的,谢谢面试官!我回去一定好好学习!

(谢飞机走出会议室,长舒一口气,掏出手机打开“牛客网”,搜索:“Resilience4j 实战教程”……)


【附录】面试题解析与技术深度剖析

一、Spring Boot 自动装配原理

1. 核心机制

Spring Boot 的自动装配依赖于以下三个核心注解:

  • @SpringBootConfiguration:标记为主配置类
  • @EnableAutoConfiguration:启用自动配置
  • @ComponentScan:扫描组件

其中最关键的是 @EnableAutoConfiguration,它通过 @Import(AutoConfigurationImportSelector.class) 导入了一组自动配置类。

2. 加载流程

  1. Spring Boot 启动时,AutoConfigurationImportSelector 会去所有 jar 包下的 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(新版本)或 spring.factories(旧版本)文件中查找自动配置类。
  2. 这些配置类通常带有 @ConditionalOnXxx 条件注解,只有满足条件才会被加载。
  3. 例如 DataSourceAutoConfiguration 会在 classpath 中存在 DataSource 类且没有用户自定义的 DataSource Bean 时才生效。

3. 自定义 Starter 示例

// 配置类
@Configuration
@ConditionalOnClass(RedisTemplate.class)
@EnableConfigurationProperties(RedisProperties.class)
public class CustomRedisAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean(RedisTemplate.class)
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        // 设置序列化方式等
        return template;
    }
}
# META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.example.starter.CustomRedisAutoConfiguration

二、电商秒杀系统的缓存设计

1. 缓存雪崩

问题:大量缓存同时失效,导致请求直接打到数据库。

解决方案:

  • 随机过期时间:expireTime + random(0~300s)
  • 多级缓存:Caffeine(本地) + Redis(分布式)
  • 缓存预热:系统启动时主动加载热点数据

2. 缓存穿透

问题:查询不存在的数据,缓存不命中,请求直达 DB。

解决方案:

  • 布隆过滤器:快速判断 key 是否可能存在
  • 缓存空值:对查询结果为空的 key 也缓存一段时间(注意设置较短 TTL)

3. 缓存击穿

问题:某个热点 key 失效瞬间,大量并发请求击穿缓存。

解决方案:

  • 互斥锁:使用 SETNX 或 Redisson 分布式锁重建缓存
  • 永不过期:后台异步更新缓存,前台始终返回旧值

4. 双写一致性

问题:数据库和缓存同时更新时可能出现不一致。

常用策略:

  • 先更新数据库,再删除缓存(Cache Aside Pattern)
  • 延迟双删:更新 DB → 删除缓存 → 延迟几百毫秒 → 再次删除缓存
  • 使用 Canal 监听 MySQL binlog,异步更新缓存

三、微服务调用的稳定性保障 —— Resilience4j

1. 核心组件

  • CircuitBreaker(熔断器):防止雪崩,自动隔离故障服务
  • RateLimiter(限流器):控制每秒请求数
  • Retry:自动重试失败请求
  • Bulkhead(舱壁):限制并发数,防止资源耗尽
  • TimeLimiter:设定调用超时时间

2. 与 OpenFeign 集成示例

resilience4j.circuitbreaker.instances.user-service.failure-rate-threshold=50
resilience4j.circuitbreaker.instances.user-service.minimum-number-of-calls=10
resilience4j.retry.instances.user-service.max-attempts=3
@FeignClient(name = "user-service", fallback = UserFallback.class)
public interface UserClient {
    @GetMapping("/users/{id}")
    User findById(@PathVariable("id") Long id);
}

@Component
public class UserFallback implements UserClient {
    @Override
    public User findById(Long id) {
        return new User(id, "默认用户", "offline");
    }
}

3. 熔断状态机

  • CLOSED:正常调用,统计失败率
  • OPEN:达到阈值,拒绝所有请求
  • HALF_OPEN:冷却时间后尝试恢复,成功则 CLOSED,失败则继续 OPEN

结语:一场面试,既是技术的较量,也是成长的契机。谢飞机虽然暴露了知识盲区,但他愿意学习的态度值得肯定。而作为开发者,我们应不断深入底层原理,理解设计思想,才能在复杂业务场景中游刃有余。

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐