Java大厂面试实录:Spring Boot + Kafka + Redis + Spring Security 全链路实战(谢飞机版)

面试官严肃如K8s Operator,谢飞机可爱似Spring Boot Actuator健康端点——一个答对@Transactional就笑出声,被问到Reactor背压机制当场掏出保温杯喝枸杞水的求职者。


🌟 第一轮:基础稳不稳?——音视频社区的登录与内容加载

面试官(推了推眼镜,屏幕右下角正跑着Prometheus监控面板):谢同学,请坐。我们是做AI驱动的UGC音视频社区,日活500万+,用户上传短视频后需实时生成封面、打标签、推送给关注者。先从基础开始:

Q1. Spring Boot 启动时自动配置的原理是什么?如果我想禁用DataSourceAutoConfiguration,但保留RedisAutoConfiguration,怎么写?请结合spring.factories@ConditionalOnClass说明。

谢飞机(挺直腰板):啊这个我知道!就是spring-boot-autoconfigure包里有META-INF/spring.factories,列了一堆EnableAutoConfiguration=开头的类……禁用的话,在application.yml里写:

spring:
  autoconfigure:
    exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration

它底层靠@ConditionalOnClass(DataSource.class)判断有没有HikariCP或Tomcat JDBC才加载——没jar包就不配!(小声)我连@SpringBootApplication其实是@EnableAutoConfiguration + @ComponentScan + @Configuration都背过!

面试官(微微颔首):很好。加分项:你提到了@ConditionalOnClass,那@ConditionalOnMissingBeanRedisAutoConfiguration里起什么作用?

谢飞机(眼睛一亮):就是说——如果我自己@Bean了一个RedisTemplate,Spring Boot就不会再帮你new一个啦!避免重复创建,像我们社区用Lettuce连接池,就得自己配LettuceClientConfigurationBuilder……

Q2. 用户登录后,JWT令牌存Redis做黑名单管理。若用StringRedisTemplate"blacklist:token:abc123",过期时间设为2小时,但用户提前退出登录,你怎么保证原子性地写入+设置过期?

谢飞机(挠头):呃……opsForValue().set(key, "1", Duration.ofHours(2))

面试官:这是两个操作。如果set成功但expire失败呢?

谢飞机(突然拍桌):Lua脚本!EVAL "redis.call('SET', KEYS[1], ARGV[1]); redis.call('EXPIRE', KEYS[1], ARGV[2])" 1 blacklist:token:abc123 1 7200!我们组长说,Redis单线程+Lua原子执行,比SET key value EX 7200还稳!

面试官(嘴角微扬):正确。下一题。

Q3. 登录接口返回UserDTO,字段含nickNameavatarUrlisVip。前端说isVip要显示“钻石会员”文字,但数据库只存boolean。你用哪种方式转换?为什么不用@JsonSerialize

谢飞机(自信):用@JsonValue在枚举里!

public enum VipLevel { NONE("普通用户"), DIAMOND("钻石会员");
  private final String desc;
  VipLevel(String desc) { this.desc = desc; }
  @JsonValue public String getDesc() { return desc; }
}

@JsonSerialize太重了,还要写Serializer类……而且@JsonValue能直接序列化整个枚举值,前端拿isVip字段就是字符串,不用再if-else!

面试官:逻辑清晰。第一轮通过。


🌟 第二轮:高并发怎么扛?——电商大促秒杀与风控联动

面试官(切换屏幕,展示JMeter压测曲线):现在场景切到双11秒杀。商品库存扣减+风控校验(设备指纹、行为图谱)必须在100ms内完成。你设计架构:

Q4. 库存用Redis原子扣减(DECR),但风控结果需异步落库并通知风控中心。你用Kafka还是RabbitMQ?为什么?如何保证“扣减成功 → 发送消息”不丢不重?

谢飞机(掏出手机翻笔记):我们选Kafka!因为——
✅ 吞吐量高(我们压测到5w+/s)
✅ 分区有序,风控事件按userId分区,同一个用户事件不乱序
✅ 支持事务消息(initTransactions() + sendOffsetsToTransaction()
❌ RabbitMQ虽然ACK可靠,但集群扩容不如Kafka平滑,且死信队列处理复杂……

至于不丢不重?我们用本地事务表+定时补偿:扣减Redis后,把消息写进MySQL的kafka_outbox表(同一本地事务),再由独立线程读表发Kafka,发成功就删记录——幂等消费+至少一次语义!

面试官(点头):务实。继续。

Q5. 秒杀请求突增,@RateLimiter注解(基于Resilience4j)限流1000qps,但发现大量RequestRejectedException。你如何快速定位是网关层限流还是应用层?日志该打在哪?

谢飞机(认真):先看GatewayFilter日志!我们用Spring Cloud Gateway,加LoggingFilter打印ServerWebExchangegetRequiredAttribute(GATEWAY_REQUEST_URL_ATTR)和响应状态。如果日志里全是429且没进@RestController,就是网关限流;如果进了Controller再抛异常,才是Resilience4j。日志打在io.github.resilience4j.ratelimiter包下,级别DEBUG——我们SRE同事说,ELK里搜rateLimiter就能看每秒拒绝数!

面试官:观察细致。第二轮通过。


🌟 第三轮:系统怎么稳?——医疗供应链的审计与可观测性

面试官(调出Grafana大盘,展示Jaeger链路追踪):最后场景:互联网医疗供应链系统,药品出入库需全链路审计(谁、何时、改了什么、IP、设备)。要求100%可追溯,且P99延迟<500ms。

Q6. 审计日志写ES,但ES写入慢。你如何异步解耦?用@Async行不行?为什么?

谢飞机(摇头):不行不行!@Async用的是SimpleAsyncTaskExecutor,默认无界队列+无限线程,OOM警告⚠️!我们改用ThreadPoolTaskExecutor,核心线程=CPU核数,队列用LinkedBlockingQueue(容量1000),拒绝策略设CallerRunsPolicy——让主线程自己执行,宁可慢也不能崩!更稳的是:审计消息发Kafka,另起一个Spring Boot服务消费→写ES,完全隔离!

Q7. 审计字段含敏感信息(如身份证号),需脱敏存储。你用@Field(type = FieldType.Text, store = true)存ES,但查询时要模糊匹配“张*”、“李*”。怎么实现?

谢飞机(兴奋):用ICU Analysis Plugin!建索引时指定analyzer

"settings": {
  "analysis": {
    "analyzer": {
      "masked_analyzer": {
        "tokenizer": "icu_tokenizer",
        "filter": ["icu_folding", "mask_char_filter"]
      }
    },
    "char_filter": {
      "mask_char_filter": {
        "type": "mapping",
        "mappings": ["* => \"*\""]
      }
    }
  }
}

然后查match_phrase_prefix,前端传"张*",ES自动匹配所有张开头+脱敏字段!

Q8. 全链路追踪中,TraceId需透传到Kafka消费者。你用Spring Cloud Sleuth,但发现下游服务TraceId为空。排查步骤是什么?

谢飞机(掰手指):1️⃣ 检查生产者是否用了TracingKafkaProducerFactory(自动注入TraceMessagingConverter);2️⃣ 查Kafka消息Headers里有没有sca-trace-id;3️⃣ 消费者是否配置了spring.sleuth.kafka.enabled=true;4️⃣ 最关键——消费者@KafkaListener方法参数里必须有@Header(Span.SPAN_CONTEXT_HEADER),否则Sleuth不自动重建Span!我们之前就漏了这行,Jaeger里断成两截……

面试官(合上笔记本):谢同学,你对技术细节有敬畏心,也懂落地权衡。今天就到这里。

谢飞机(站起来鞠躬):谢谢面试官!我回去就把Lombok的@SneakyThrows换成显式try-catch……

面试官(微笑):回家等通知吧。


📘 标准答案与学习指南(小白友好版)

Q1:Spring Boot自动配置原理

答案:基于spring.factories文件触发EnableAutoConfiguration,每个xxxAutoConfiguration类用@ConditionalOnXXX注解控制加载条件(如@ConditionalOnClass(DataSource.class)表示类路径存在DataSource才生效)。禁用某配置用spring.autoconfigure.exclude,支持全限定名或短名(如DataSourceAutoConfiguration)。
💡 业务链接:音视频社区启动快,需精准排除无用配置(如不用DB就禁用DataSourceAutoConfiguration),减少内存占用和启动时间。
📚 学什么:读spring-boot-autoconfigure源码,重点看org.springframework.boot.autoconfigure.jdbc包。

Q2:Redis原子写入+过期

答案:用SET key value EX seconds命令(Redis 2.6.12+),单命令原子执行。旧版本用Lua脚本封装SET+EXPIRE
💡 业务链接:JWT黑名单必须强一致,否则用户退出后令牌仍可用,引发安全风险。
📚 学什么RedisTemplate.opsForValue().set(key, value, timeout, TimeUnit)底层即调用SET ... EX

Q3:DTO字段语义转换

答案:优先用枚举+@JsonValue(简洁、类型安全、JSON序列化友好);次选用@JsonCreator反序列化;避免@JsonSerialize除非需复杂逻辑(如动态格式)。
💡 业务链接:音视频社区前端统一渲染会员标识,后端提供语义化字符串,降低前端判断成本。
📚 学什么:Jackson注解大全,对比@JsonValue/@JsonRawValue/@JsonUnwrapped

Q4:Kafka vs RabbitMQ选型

答案:高吞吐、大数据量、需水平扩展 → Kafka;低延迟、强事务、简单队列 → RabbitMQ。可靠性靠事务消息(Kafka)或Publisher Confirms(RabbitMQ)+ 消费者手动ACK。
💡 业务链接:电商秒杀事件量级达百万/分钟,Kafka分区+副本保障高可用,RabbitMQ镜像队列同步压力大。
📚 学什么:Kafka Exactly-Once语义实现原理(事务ID、PID、Epoch)。

Q5:限流问题定位

答案:分层打日志——网关层(GlobalFilter)、WebMvc层(HandlerInterceptor)、业务层(@RateLimiter拦截器)。用Micrometer暴露resilience4j.ratelimiter.calls指标,Grafana看failed/limited计数。
💡 业务链接:大促期间限流误伤正常用户,需快速区分是恶意刷量(网关拦)还是服务过载(应用层拦)。
📚 学什么:Resilience4j RateLimiter源码,理解AtomicLong计数器与滑动窗口算法。

Q6:异步审计解耦

答案:禁用@Async默认线程池!自定义ThreadPoolTaskExecutor,设合理corePoolSizemaxPoolSizequeueCapacity及拒绝策略。终极方案:消息队列(Kafka/RocketMQ)解耦,保障主链路SLA。
💡 业务链接:医疗数据审计合规强制要求,但不能拖慢核心出入库流程(P99<500ms)。
📚 学什么:线程池7大参数详解,CallerRunsPolicy如何保护主线程。

Q7:ES敏感字段模糊查询

答案:用character filter预处理(如mapping char_filter身份证号***),配合keyword类型+wildcard query;或用ngram tokenizer支持前缀搜索。脱敏与查询分离:原始字段加密存储,检索字段脱敏后建立倒排索引。
💡 业务链接:医疗数据受《个人信息保护法》约束,审计日志既要可查又要合规。
📚 学什么:ES分析器(Analyzer)三大组件:Char Filter / Tokenizer / Token Filter。

Q8:Sleuth链路透传

答案:Kafka生产者自动注入trace header(需spring-cloud-starter-sleuth+spring-cloud-starter-stream-kafka);消费者必须在@KafkaListener方法中声明@Header(Span.SPAN_CONTEXT_HEADER)参数,否则Sleuth无法重建上下文。
💡 业务链接:药品供应链需跨10+微服务追踪操作,缺失TraceId则审计失效。
📚 学什么:OpenTracing规范,Sleuth与Brave的SpanContext传递机制。

延伸学习地图

  • JVM:-XX:+UseZGC应对大内存审计日志堆
  • 安全:JWTRS256非对称签名,密钥存HashiCorp Vault
  • CI/CD:GitLab CI用docker:dind构建多阶段镜像,扫描CVE漏洞
  • 监控:Micrometer对接Prometheus,自定义Timer统计各环节耗时

🌈 谢飞机走出大楼,阳光正好。他打开手机备忘录,新增一条:
「明天学:R2DBC怎么让Spring Data不阻塞?——毕竟,医疗查询不能等!」

(全文完)

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐