互联网大厂Java面试实战:Spring Boot、Redis、Kafka、微服务、JWT、Elasticsearch与Kubernetes全栈技术
互联网大厂Java面试实战:Spring Boot、Redis、Kafka、微服务、JWT、Elasticsearch与Kubernetes全栈技术
面试官:请进,坐吧。先介绍一下你做过的最有代表性的项目。
谢飞机:老师好,我叫谢飞机,三年Java开发。我做了一个宠物内容社区“喵次元”,用户可以发猫咪图片、短视频,也能点赞评论关注。我主要负责后端,技术栈用了Spring Boot、MyBatis、MySQL、Redis、Kafka,搜索用的Elasticsearch,部署在Docker上。
面试官:你们用了Spring Boot,那你说说Spring Boot自动配置的原理?
谢飞机:(搓手)呃……自动配置就是“约定大于配置”嘛。Spring Boot启动的时候,会读一个叫spring.factories的文件,里面有一堆带@Configuration的类。比如引入了Redis,就会自动配一个RedisTemplate。但是……具体怎么根据classpath判断的,我当时没细看,反正用起来挺香。
面试官:Kafka和RabbitMQ的区别是什么?
谢飞机:Kafka是高吞吐,RabbitMQ是低延迟?我觉得Kafka像瀑布,RabbitMQ像水库。嗯……Kafka有partition,可以横向扩展,RabbitMQ有exchange和routing key,做路由很灵活。别的我一下子想不全。
面试官:在“喵次元”里,用户上传一个视频,怎么保证接口不卡死?
谢飞机:我们用了异步转码。上传后马上返回“处理中”,视频转码的消息丢到Kafka,有个消费程序慢慢转。客户等转好了,会收到WebSocket通知。这样接口很快,不会卡死。
面试官:好,第二轮。内容表数据量大了,你怎么优化?
谢飞机:先加索引,再上Redis缓存。如果超过千万,就分库分表。不过分库分表我只懂个大概,用ShardingSphere配置过逻辑表,真正的分布式事务没碰过。
面试官:缓存穿透、击穿、雪崩怎么解决?
谢飞机:这个我熟!穿透就缓存空值或布隆过滤器;击穿就是热点key过期,用互斥锁;雪崩就是大量key同时过期,给过期时间加随机数。这个问题我面试前刚背过。
面试官:背得挺熟练。那Redis有哪些数据结构?在社区里怎么用?
谢飞机:String、List、Hash、Set、ZSet我都用过。String存验证码,Hash存帖子内容,Set存粉丝列表,ZSet存热门排行榜,List存关注人的动态流。
面试官:怎么用Elasticsearch实现对文章和视频标题的搜索?倒排索引知道吗?
谢飞机:就是先建索引,然后搜索。倒排索引……我听腾讯课的讲师讲过,就是“单词->文档列表”的映射。但是和MySQL的索引区别我有点说不清。
面试官:第三轮。你们的服务怎么拆分的?服务发现用的什么?
谢飞机:按业务拆的,用户、内容、评论、消息、审核。服务发现用了Nacos,之前也用Eureka,Nacos还管配置。
面试官:服务A调用服务B,如果B挂了,A怎么避免雪崩?
谢飞机:使用Resilience4j做熔断限流,也加了Fallback方法。B挂了,A就直接返回缓存或提示“稍后重试”,不让线程一直等。
面试官:JWT和Session有什么区别?
谢飞机:Session存在服务器,JWT存在客户端。JWT是base64编码的三段,最后是签名,服务端验签就行。但是JWT不能强制下线,所以我们用Redis黑名单解决。
面试官:线上如何监控?Kafka消息积压了怎么办?
谢飞机:我们有Prometheus和Grafana,JVM、CPU、QPS都监控。日志用的ELK。毫秒级的链路追踪,用了Jaeger。积压的话……可以看Kafka的Lag指标,我们设了阈值,超过就报警。但具体命令有点忘了,好像是“kafka-consumer-groups.sh”吧。
面试官:很好。最后一个,你们CI/CD怎么设计的?
谢飞机:(眼睛一亮)这个我会!我们用GitLab CI,代码合并到main自动触发。流水线包括:maven打包、跑单测、docker build、推镜像、然后helm部署到K8s集群。K8s里用Deployment管理副本,滚动升级,可以自动回滚。
面试官:(点点头)好,我这边基本清楚了。你回去等通知吧。
谢飞机:好的,谢谢老师!
答案解析
第一轮
问题1:请介绍一个最有代表性的项目,它的整体架构和技术栈是什么?
这是一道典型的“项目自我介绍”题。面试官想看你的项目经验、技术深度和表达逻辑。回答时要按照“背景 → 架构 → 角色 → 亮点”来展开。
以“宠物内容社区”为例:
- 背景:一个UGC平台,用户能发图文、视频,可以点赞、评论、关注、搜索。
- 架构:前端App/H5 → Nginx → Spring Boot微服务集群(用户服务、内容服务、评论服务、消息服务、审核服务) → MySQL(主从/分片) + Redis(缓存) + Kafka(异步消息) + Elasticsearch(搜索) + OSS(文件存储) + WebSocket(消息推送)。
- 职责:负责内容发布链路和消息服务,设计了视频异步转码流程。
- 亮点:用Kafka削峰,用Redis缓存热点榜单,用Elasticsearch解决全文检索,用JWT做无状态登录。
给面试官讲项目时,不要只报技术名词,也要说清楚为什么选这个技术、解决了什么问题。
问题2:解释Spring Boot自动配置的原理。
Spring Boot自动配置的核心是@EnableAutoConfiguration注解。这个注解会通过@Import(AutoConfigurationImportSelector.class)导入一批“自动配置类”。
Spring Boot会在项目所有依赖的jar包中扫描META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件(Spring Boot 2.7之前叫spring.factories),拿到所有候选的自动配置类。
每个自动配置类上都有条件注解,例如:
@ConditionalOnClass(RedisOperations.class):只有当classpath中有这个类时,才配置Redis。@ConditionalOnProperty(prefix = "spring.redis", name = "enabled", havingValue = "true"):需要指定配置项。@ConditionalOnMissingBean:如果用户已经自己定义了Bean,默认配置就不再生效。
比如RedisAutoConfiguration,它检测到项目中有Redis相关类时,会创建一个RedisTemplate的Bean。如果用户自己写了RedisTemplate,就自动跳过默认配置。
简单说:自动配置 = 配置类扫描 + 条件注解 + 默认Bean装配。排查问题可以使用--debug启动查看自动配置报告。
问题3:Kafka和RabbitMQ在消息队列设计上有哪些核心区别?
它们都是成熟的消息中间件,但设计理念不同:
- 模型不同:RabbitMQ基于AMQP协议,有Exchange交换机、Queue队列、RoutingKey路由。Kafka是分布式流平台,有Topic、Partition分区、Consumer Group消费组。
- 消费方式不同:RabbitMQ是push推模型,消息到达队列后主动推给消费者;Kafka是pull拉模型,消费者自己从分区拉取消息,可以控制速率和offset重放。
- 消息确认不同:RabbitMQ依赖ACK确认,消费者处理完返回ACK,否则重新投递;Kafka通过提交offset表示已经消费,可能重复消费,需要业务幂等。
- 能力侧重不同:RabbitMQ功能强大,支持延迟队列、死信队列、优先级队列,适合复杂路由和低延迟任务;Kafka吞吐量高,天然持久化,适合日志采集、埋点数据、用户行为流、削峰填谷。
- 可靠性实现不同:Kafka通过分区副本(Replica)和ISR机制保证高可用,生产端设置
acks=all可以保证不丢消息;RabbitMQ支持镜像队列或Quorum Queue。
在UGC场景中,如果只是“发视频后通知转码”这种异步任务,RabbitMQ足够;但如果是海量日志、用户行为流、大数据管道,Kafka更合适。
问题4:在UGC音视频场景中,如何设计一个上传后异步转码的流程?
核心思路是“同步接口只接收,异步任务做重活”。
流程如下:
- 客户端请求后端获取“预签名上传地址”,然后直接上传到OSS对象存储。
- 上传完成后,客户端调用后端接口
POST /videos,传入文件URL、标题、大小等元数据。 - 后端创建视频记录,状态设为“待处理”,同时向Kafka的
video-transcodetopic发送一条消息,包含视频ID和源文件URL。 - 接口立刻返回,用户前端看到“处理中”。
- 后台转码服务订阅Kafka,收到消息后调用FFmpeg进行转码,同时抽帧生成封面。
- 转码完成后更新数据库状态为“已发布”,并通过WebSocket通知用户。
- 如果转码失败,把消息投递到死信队列,方便人工补偿。
用Kafka的好处是削峰填谷:用户同时上传大量视频,生产者写入很快,消费者根据自己的能力慢慢消费,不会瞬间压垮转码服务。即使转码服务宕机,Kafka也能保存一段时间消息,服务恢复后可以继续消费。
第二轮
问题5:内容表数据量达到千万级,如何优化数据库?分库分表怎么做?
数据库优化顺序是:索引 → SQL优化 → 缓存 → 读写分离 → 分库分表。
- 索引优化:对查询条件中常用的
user_id + created_at建联合索引。避免在索引列上做函数操作,遵循最左前缀原则。 - 缓存优化:把热点内容存入Redis,比如首页Feed流可以直接缓存文章摘要列表,减少MySQL压力。
- 读写分离:把写请求打到主库,读请求打到从库,用ShardingSphere或MyCat实现主从数据源切换。
分库分表有两种拆法:
- 垂直拆分:把一个宽表拆成多个表,比如把文章正文、图片URL等大字段拆到单独表,主表只留核心字段。
- 水平拆分:按某个分片键,如
content_id或user_id,通过哈希/取模分布到多个库表中。例如拆成4个库,每个库8张表,总共32张表。
分库分表后要注意:
- 全局唯一ID:用雪花算法生成,不能在多个表之间重复。
- 跨库join:不允许,要改成查两次,或者冗余数据。
- 分布式事务:跨库写操作需要引入Seata等分布式事务框架。
- 全局搜索:分表后MySQL不适合搜索了,引入Elasticsearch。
社区项目通常按content_id分片,同时用ES解决全文搜索,用Redis解决热点缓存,这样MySQL的压力就能降下来。
问题6:缓存穿透、击穿、雪崩分别是什么?如何解决?
缓存穿透:查询一个数据库也不存在的key,请求绕过缓存直接打数据库,恶意攻击时会导致数据库压力巨大。
- 解决1:缓存空值,即使查询结果是null,也写入缓存并设置较短过期时间。
- 解决2:使用布隆过滤器(Bloom Filter),在缓存前判断key是否存在,不存在直接返回。
- 解决3:接口层对请求参数做合法性校验。
缓存击穿:一个热点key在过期的瞬间,大量并发请求同时访问数据库。
- 解决1:互斥锁。只允许一个请求去查数据库,其他请求等待重试。
- 解决2:热点key“永不过期”,后台线程定时重建缓存。
缓存雪崩:大量key在同一时间集中过期,或者Redis整体不可用,导致大规模请求压垮数据库。
- 解决1:过期时间加随机值,比如
TTL = 24小时 + random(0, 5分钟)。 - 解决2:多级缓存,本地使用Caffeine,Redis作为第二级,请求先查本地再查Redis。
- 解决3:Redis部署为Cluster高可用。
- 解决4:开启服务降级和限流,保护下游数据库。
- 解决1:过期时间加随机值,比如
这三个概念一定要记牢,是Java面试高频问题。
问题7:Redis常见数据结构有哪些?在社区里分别适用什么场景?
- String:最常用。存验证码、用户token、计数器、缓存对象。比如
SET login:code:13800000000 1234 EX 300。 - Hash:适合存对象。比如用户信息
HSET user:10086 name "cat" age 2。也适合点赞状态,field=用户ID,value=1/0。 - List:有序列表。可以做关注人的动态流,发布动态时
LPUSH user:timeline:10086 contentId。 - Set:去重集合。适合关注列表、粉丝列表、共同关注。比如
SADD follower:10086 user1,然后SINTER求交集。 - ZSet:有序集合。每个成员带一个score,适合排行榜。热门文章榜:
ZINCRBY hot:article 100 articleId,然后ZREVRANGE hot:article 0 9 WITHSCORES。 - HyperLogLog:基数统计。统计今日UV。
- Bitmap:位图。适合用户签到、在线状态,一个bit就能存一个用户。
- Geo:地理位置。可以算附近的人、附近的宠物店。
在“喵次元”中,String存验证码和Token,Hash存用户资料,Set存粉丝关系,ZSet存热门排行榜,List存时间线。
问题8:如何用Elasticsearch实现全文搜索?倒排索引是什么?
关系型数据库MySQL的索引是正排索引,比如文章表有字段post_id、title、content,搜索LIKE '%猫%'会扫全表。ES用的是倒排索引,结构是“单词 → 文档ID列表”。
举个例子:
- 文档1:“猫咪在吃饭”
- 文档2:“狗狗在睡觉”
经过中文分词后,得到词语:猫咪、吃饭、狗狗、睡觉。倒排表就是:
- 猫咪 → [文档1]
- 吃饭 → [文档1]
- 狗狗 → [文档2]
- 睡觉 → [文档2]
搜索“猫咪”时,直接查倒排表得到文档1,非常快。
在实际项目中,我们会把MySQL中的文章数据同步到Elasticsearch。同步方式:
- 业务双写:在写MySQL的同时,把数据写入ES。
- Canal监听binlog:MySQL数据变更后,Canal读取binlog,通过Kafka发送给ES同步服务。
- 定时任务批量同步。
在Spring Boot中集成ES,可以用Spring Data Elasticsearch:
- 实体类上使用
@Document(indexName = "content")。 - Repository接口继承
ElasticsearchRepository。 - 方法上使用
@Query或直接声明findByTitleLike。
搜索时调用ES返回文章ID列表,然后再去Redis/MySQL查详情,减少ES压力。
第三轮
问题9:微服务如何拆分?服务发现和注册用什么?
微服务拆分要围绕“业务边界”和“团队边界”。比如一个UGC社区可以拆成:
- 用户服务:注册、登录、用户资料。
- 内容服务:发帖、发视频、文章查询。
- 评论服务:评论、回复、点赞。
- 消息服务:站内信、通知、WebSocket推送。
- 审核服务:图片/文本/视频的内容安全审核。
拆分原则:每个服务能独立部署、独立扩展、独立维护,服务之间通过HTTP/RPC通信。
服务注册与发现常用组件:
- Eureka:Netflix出品,Spring Cloud早期方案,已经进入维护状态。
- Nacos:阿里出品,主打注册中心和配置中心,国内使用广泛。
- Consul:HashiCorp出品,支持多数据中心。
- Zookeeper:分布式协调系统,也能做服务注册中心。
以Nacos为例,服务启动后把自己注册到Nacos,消费者从Nacos拿到服务提供者列表,再通过OpenFeign或Ribbon做负载均衡调用。Nacos还支持配置中心,可以动态修改配置而不重启应用。
问题10:服务A调用服务B,如果B挂了,A怎么避免雪崩?
服务雪崩是指一个服务故障,导致调用方线程堆积,最终拖垮整个系统。防雪崩的手段有三层:
- 超时:设置连接超时和读取超时。比如OpenFeign的
connectTimeout=3s,readTimeout=5s,不能让线程无限等待。 - 熔断:使用Resilience4j或Sentinel。例如A调用B,10秒内有50%请求失败,熔断器打开,后续请求直接返回fallback方法,不再访问B。
- 限流:使用Sentinel控制A的最大QPS,超过直接拒绝,保护A自己不被打垮。
- 隔离:给不同的外部依赖分配独立的线程池,比如调用B和调用C分别使用不同线程池,B卡死不会占满C的线程池。
Resilience4j的核心概念:
- 熔断器状态:关闭 → 打开 → 半开 → 关闭。
- 降级:
@CircuitBreaker(name = "内容服务", fallbackMethod = "getContentFallback")。 - 半开时,允许少量请求试探,如果成功,熔断器关闭;如果失败,重新打开。
问题11:JWT认证和传统Session有什么区别?如何用Spring Security实现登录?
Session原理:
- 用户登录后,服务器生成Session并保存到内存或Redis,把SessionID通过Cookie发给浏览器。
- 后续请求带着Cookie,服务器根据SessionID查Session。
- 可以主动销毁Session,实现强制下线,因为状态在服务端。
JWT原理:
- JWT由三部分组成:Header(声明算法)、Payload(用户信息、过期时间)、Signature(签名)。
- 用户登录后,服务器签名生成Token返回给客户端,客户端保存。
- 每次请求在
Authorization: Bearer <token>中携带,服务器验签后直接从Token中取用户信息。 - 服务器不保存状态,所以天然适合分布式和微服务。
- 缺点是Token本身不能主动失效,只能等过期时间。如果要强制下线,需要用Redis维护黑名单,把Token的
jti存进去。
Spring Security + JWT登录流程:
- 引入
spring-boot-starter-security和jjwt。 - 配置
SecurityFilterChain:关闭CSRF,设置SessionCreationPolicy.STATELESS。 - 实现
UserDetailsService从数据库加载用户信息。 - 登录接口中,使用
AuthenticationManager校验用户名密码,成功后用Jwts.builder()生成Token。 - 实现一个
OncePerRequestFilter,从请求头解析Token,验签后把UsernamePasswordAuthenticationToken放入SecurityContextHolder。 - 在
SecurityConfig中注册过滤器,并用@PreAuthorize("hasRole('USER')")控制接口权限。
问题12:线上监控怎么做?如何监控Kafka消息积压和链路追踪?
监控体系可以分为四层:
- 指标监控:应用使用Micrometer库收集指标,暴露给Prometheus,Grafana做可视化。常见指标:JVM内存、GC次数、线程数、QPS、RT、错误率。
- 日志监控:使用Logback输出日志,Filebeat采集日志到Logstash,再送到Elasticsearch,最后用Kibana查询。就是ELK技术栈。
- 链路追踪:微服务调用链需要TraceId串联。使用Spring Cloud Sleuth + Zipkin或Jaeger,可以看到一次请求从网关到用户服务再到评论服务的完整耗时。
- 告警:Prometheus的Alertmanager根据规则发送邮件、钉钉、Webhook。
Kafka消息积压监控:
- 使用Kafka自带命令:
kafka-consumer-groups.sh --bootstrap-server localhost:9092 --group 消费组名 --describe。 - 查看每个分区的
LAG(未消费消息数量)。如果Lag持续增长,说明消费者跟不上,需要扩容消费者实例或者增加分区数。 - 可以使用Burrow工具自动监控Lag并暴露成Prometheus指标,在Grafana上设置“Lag > 阈值”的告警。
没有监控的系统是很危险的,面试中如果能说出“Prometheus + Grafana + ELK + Jaeger+Kafka Lag”这套组合,会加分不少。
问题13:CI/CD流程如何设计?Docker和Kubernetes在其中的角色?
CI是持续集成,CD是持续部署。一个完整的CI/CD流程可以考虑以下阶段:
- 提交代码:开发push到
main分支,触发GitLab CI。 - 静态检查:执行
mvn sonar:sonar,扫描代码质量。 - 单元测试:执行
mvn test,使用JUnit 5、Mockito、AssertJ。 - 构建打包:执行
mvn package -DskipTests,生成可执行Jar包。 - 构建镜像:编写Dockerfile,将Jar包打入镜像,比如基于
eclipse-temurin:17-jre-alpine。 - 推送镜像:将镜像推送到Harbor私有仓库。
- 部署:使用Helm或
kubectl set image把新镜像更新到Kubernetes集群。
Docker的作用:
- 将应用和依赖环境打包成镜像,保证“本地能跑,线上也能跑”。
- 常见命令:
docker build、docker run、docker push。
Kubernetes的作用:
- 编排容器。通过Deployment管理Pod副本数,Pod挂了自动重启。
- Service提供负载均衡和稳定访问入口。
- Ingress暴露HTTP/HTTPS域名路由。
- 支持滚动更新,新Pod启动成功后再杀掉旧Pod,可以快速回滚。
数据库版本迁移可以在应用启动前使用Flyway或Liquibase执行SQL脚本,这样发布新版本不需要手动去数据库执行脚本,实现“数据库即代码”。
