十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

互联网大厂Java面试:从Spring Boot到分布式消息队列,谢飞机“面”对电商高并发场景

互联网大厂Java面试:从Spring Boot到分布式消息队列,谢飞机“面”对电商高并发场景 互联网大厂Java面试从Spring Boot到分布式消息队列谢飞机“面”对电商高并发场景早晨十点某互联网大厂29楼会议室。阳光透过落地窗洒在长桌上面试官李工面无表情地翻开简历对面坐着一个头发微乱、背着双肩包的年轻人——谢飞机。他的简历上赫然写着“电商秒杀系统架构师”“精通Java全栈”“熟悉分布式高并发”。面试官扶了扶眼镜开始了这场“严肃”的面试。第一轮核心语言与框架的试探李工用手指敲了敲桌面沉声问“谢飞机你的简历上写着你做了电商秒杀系统。那我先问你一个基础问题在Java 8中如何使用Stream API对一个订单列表进行过滤只留下状态为‘待支付’且创建时间超过30分钟的订单然后批量关闭超时订单”谢飞机眼睛一亮这题他会他挺直腰板“简单用stream().filter()先把order.getStatus() 1过滤出来再判断创建时间是否小于当前时间减30分钟然后收集成一个List最后调用批量关闭方法。”李工微微点头“不错思路清晰。那再问你如果订单列表里可能包含null元素你想避免NullPointerException怎么用Optional处理”谢飞机搓了搓手“那就用Optional.ofNullable(order)先判断是不是null再进行后续操作。比如Optional.ofNullable(order).map(Order::getStatus).orElse(-1)。”“很好基础还算扎实。”李工话锋一转“那你说说Spring Boot的自动配置原理吧。为什么我们引入一个starter依赖就能直接用里面的Bean”谢飞机一愣额头冒汗“呃……自动配置就是……Spring Boot启动的时候会读取一个文件……叫META-INF/spring.factories里面有各种Configuration类的名字……然后通过EnableAutoConfiguration导入……具体我有点记不清反正就是会用ConditionalOnClass和ConditionalOnMissingBean这种东西……条件装配……呃……”李工没有打断但眉头微皱。接着问“那Spring MVC处理一个HTTP请求的核心流程是什么”谢飞机长舒一口气“这个我知道前端所有的请求都先进DispatcherServlet然后它根据请求路径去找HandlerMapping拿到对应的HandlerExecutionChain再通过HandlerAdapter调用Controller的方法返回ModelAndView最后通过ViewResolver解析视图或者如果你用了ResponseBody就直接返回JSON。”李工难得露出一丝笑意“说得还挺完整。那再问你一个数据库相关的问题在MySQL中哪些情况会导致索引失效”谢飞机掰着手指“对索引列进行函数运算、隐式类型转换、使用LIKE %xx以通配符开头、OR连接非索引列、还有!或有时候也不走索引……还有联合索引不满足最左前缀原则。”李工点点头在本子上写了点什么说“第一轮你过了我们进入第二轮。”第二轮缓存、分布式与微服务李工合上笔记本身体前倾“在你们的秒杀场景下Redis是核心缓存。如果秒杀开始瞬间有海量请求请求同一个不存在的数据比如一个已下架的商品ID你如何防止缓存穿透”谢飞机不假思索“可以缓存空值如果查数据库没查到就在Redis里存一个空字符串设置短过期时间。还可以用布隆过滤器把所有商品ID预先放进过滤器请求来了先判断ID是否存在不存在直接返回。”李工追问“布隆过滤器的原理是什么”谢飞机挠头“嗯……就是有几个哈希函数把元素映射到一个很长的bit数组里置为1。判断时也做同样的哈希看这几个位是不是都是1。如果有一个位不是1就肯定不存在如果都是1只能说明可能存在有误判。但说真的我项目里用的是Redisson的RBloomFilter底层原理是……就是那个……反正能用。”李工没有深究转而问“如果在高并发下缓存大量失效导致所有请求打到数据库你怎么办”谢飞机急中生智“这是缓存雪崩解决方案有设置过期时间加随机值避免同时失效或者热点数据设置永不过期后台异步更新还有就是用互斥锁只允许一个线程去查数据库重建缓存其他线程等待。”“嗯那如果缓存击穿呢某个热点key过期了一瞬间大量请求并发访问它。”“这个和雪崩类似对单一热点key用互斥锁。比如用Redis的SETNX获取锁的线程查库放缓存没获取到的线程sleep后重试。”李工追问“那分布式事务你了解吗比如用户下单后要扣库存、扣优惠券、生成积分这三个操作在两个微服务里你怎么保证一致性”谢飞机的笑容逐渐僵住“……我们用的是Seata……就是AT模式……通过全局事务ID把本地事务的undo log和redo log记录下来……如果某个分支失败就反向补偿……但回滚的具体流程有点复杂我当时是调用的现成接口……TCC模式的话……Try、Confirm、Cancel……嗯……概念知道写的话没写过。”李工叹了口气继续问“你们的微服务之间是怎么通信的”谢飞机立刻抢答“用OpenFeign加上EnableFeignClients然后定义接口加FeignClient(name order-service)内部映射到对方Controller的URL就行。”“那如果下游服务突然变慢你怎么防止线程被耗尽”“用Resilience4j可以加熔断和限流。熔断有三种状态CLOSED、OPEN、HALF_OPEN失败率超过阈值就打开之后请求直接短路过一段时间进入半开放几个请求试试恢复就关闭。”李工眼睛稍微亮了一下“你居然知道这些状态。好我们最后一轮聊聊消息队列和安全。”第三轮消息队列、安全与工程化李工递给他一瓶矿泉水“喝口水别紧张。我问你订单系统创建订单后需要通知库存系统扣减库存同时发送通知给用户。如果用Kafka怎么设计这个流程”谢飞机咕咚喝了一口“订单系统把订单消息发送到Kafka的‘order.created’主题库存系统和通知服务都订阅这个主题分别消费。消息里包含订单ID、商品ID、数量。库存系统消费后执行扣库存通知服务消费后发送短信或站内信。”李工点头“那如果库存系统处理失败消息被重复消费你怎么保证不会超卖”谢飞机来了精神“要幂等消费者这边可以建一张消费记录表用订单ID加商品ID做唯一索引处理前先查一下如果已经处理过就直接ack也可以用Redis的SET NX设置一个消费标记设置了就说明处理过。对于Kafka本身可以设置enable.auto.commitfalse手动提交offset业务处理成功后再提交。”李工追问“那Kafka能保证消息顺序吗”“嗯……Kafka只能保证分区内有序。如果要求同一订单的消息有序可以按订单ID做key让同一订单的消息进同一个分区。”李工点点头忽然话锋一转“我看你还写了JWT和OAuth2。你说说它们有什么区别在微服务网关中如何使用的”谢飞机放松了“JWT是一种Token格式由Header、Payload、Signature组成服务端不用存sessionOAuth2是一种授权框架解决‘用户授权第三方应用访问资源’的问题。区别就是JWT是令牌格式OAuth2是授权协议。通常可以结合使用用OAuth2拿授权码换TokenToken用JWT格式签发。网关就校验JWT的签名比如用jjwt解析还要校验Token是否过期。”“那Spring Security和Apache Shiro你选哪个”“选Spring Security因为和Spring Boot集成好用EnableWebSecurity自定义Filter支持OAuth2客户端、JWT解析功能更全。”李工喝了口水问“系统上线后你如何监控JVM和应用指标”谢飞机说道“我们用Micrometer暴露/actuator/prometheusPrometheus定期抓取指标比如jvm_memory_used_bytes、jvm_gc_pause_seconds存入时间序列数据库然后Grafana配置Dashboard展示。日志方面用SLF4J加Logback输出到ELK也就是Logstash收集、Elasticsearch存储、Kibana可视化。”李工又问“日志如果用Log4j2和Logback你怎么选”“选Logback因为Spring Boot默认就是它配置方便。Log4j2有异步日志性能更好但兼容性要小心漏洞现在都用Logback加异步Appender。”“最后一个问题你们的项目怎么部署的”谢飞机精神抖擞“用Maven打包成jar然后写Dockerfile用Docker做成镜像再写的Kubernetes YAML文件定义Deployment、Service通过Jenkins流水线自动构建、推送镜像到Registry然后kubectl rolling update滚更新。GitLab CI也可以做但Jenkins用得多。”李工听完靠在椅背上沉默了几秒。谢飞机手心出汗偷偷擦了擦。李工终于开口“谢飞机你的知识面很广但有些问题深度明显不够。比如自动配置的原理、Seata的底层回滚机制、布隆过滤器的数学原理这些你都得回去再挖一挖。我们的业务非常复杂需要真正能吃透原理的人。”谢飞机紧张地点头“李工您放心我学习能力特别强我今晚就回去深入研究”李工站起来微微点头“这样吧你先回家等通知。我们还有几个候选人如果合适会在一周内联系你。”谢飞机如释重负站起来和李工握手“好的好的我等您通知谢谢李工您要的Offer我一定会等到”李工看着他走出门默默在简历上写了两个字“待定”。然后摇了摇头又加了一个备注“但态度不错可以培养。”面试问题详解业务场景与技术点全解析为了让像谢飞机一样的小白能真正看懂下面将面试中的问题逐一展开结合电商、秒杀、支付等业务场景把技术原理讲透。1. Java 8 Stream API 如何过滤订单并批量关闭在电商后台经常需要把“待支付超时”的订单自动关闭。示例代码如下ListOrder expiredOrders orderList.stream() .filter(Objects::nonNull) .filter(o - o.getStatus() OrderStatus.PENDING_PAYMENT.getCode()) .filter(o - o.getCreatedAt().plusMinutes(30).isBefore(LocalDateTime.now())) .collect(Collectors.toList()); orderService.batchClose(expiredOrders);filter接收一个Predicate函数式接口返回布尔值。Objects::nonNull过滤掉null元素。LocalDateTimeJava 8新增的时间API避免Date的线程安全问题。Collectors.toList()把Stream结果收集为List。业务小提示不要把全部订单加载到内存再过滤生产环境应该用SQLUPDATE ... WHERE status? AND create_time NOW() - INTERVAL 30 MINUTE或先查询再批量更新。Stream适合内存中的集合处理。2. Optional 如何防止空指针Optional是一个容器可以表述“值可能不存在”。用法Optional.ofNullable(order) .map(Order::getStatus) .orElse(-1); // 如果order为null返回-1ofNullable允许传入null。map如果存在值则应用转换函数如果为null返回Optional.empty()。orElse如果为空返回默认值。也可以用orElseGet延迟计算和orElseThrow抛出异常。业务场景用户查询订单后可能需要取用户手机号但用户可能不存在。用Optional可以优雅地避免NullPointerException但注意不要滥用——例如在字段上直接赋值Optional那是反模式。3. Spring Boot 自动配置原理Spring Boot通过SpringBootApplication组合了Configuration、EnableAutoConfiguration、ComponentScan实现自动配置。核心机制EnableAutoConfiguration会导入AutoConfigurationImportSelector。该Selector会读取所有Jar包中的META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件新版本或spring.factories文件老版本。文件里列出了所有候选的自动配置类例如DataSourceAutoConfiguration、RedisAutoConfiguration。自动配置类上通常有ConditionalOnClass类路径存在某个类才生效、ConditionalOnMissingBean容器中不存在该Bean才创建默认Bean、ConditionalOnProperty配置项是否存在等条件注解。最后通过Import导入并注册为Spring容器中的Bean。举例当你引入spring-boot-starter-data-redis类路径上有RedisTemplateRedisAutoConfiguration生效它会创建一个默认的RedisTemplate和StringRedisTemplate。面试加分点可以手写一个自定义Starter包含自动配置类、spring.factories或AutoConfiguration.imports并设计条件注解。4. Spring MVC 请求处理流程核心流程如下客户端发起请求被DispatcherServlet接收。DispatcherServlet调用HandlerMapping根据URL找到对应的HandlerExecutionChain包含一个Handler和多个拦截器。通过HandlerAdapter适配并执行HandlerController中的方法。Controller执行完后返回ModelAndView或ResponseBody直接返回对象由HttpMessageConverter序列化为JSON。如果是视图ViewResolver解析视图并渲染如果是JSON直接写回响应。最后执行拦截器的afterCompletion。关键组件DispatcherServlet前端控制器。HandlerMapping负责URL到Handler的映射。HandlerAdapter负责调用具体的Controller方法。HttpMessageConverter做HTTP消息和Java对象的转换。ViewResolver视图解析器。5. MySQL 索引失效场景常见索引失效或不可用的情况对索引列使用函数WHERE SUBSTR(name,1,2)xx。隐式类型转换WHERE phone 123456phone是varchar。LIKE以通配符开头WHERE name LIKE %xxx。OR连接非索引列WHERE id1 OR age20age无索引。联合索引不满足最左前缀原则例如联合索引(a,b,c)查询WHERE b1无法使用该索引。**NOT IN、!、在某些情况下可能不是恒失效但优化器可能选择全表扫描。数据量太小优化器认为全表扫描更快这是“失效”的误传。索引优化思路使用EXPLAIN查看执行计划关注type、key、rows字段尽可能达到ref或range级别。6. 缓存穿透、击穿、雪崩这三个概念是Redis面试必考。缓存穿透场景查询一个根本不存在的数据导致每次请求都打到数据库。解决缓存空对象把“不存在”也缓存设置短TTL如2分钟防止恶意攻击。布隆过滤器把所有可能存在的数据ID存到布隆过滤器。布隆过滤器原理是多个哈希函数映射到一个bit数组能判断“一定不存在”或“可能存在”。缺点是会有误判率且不支持删除可用布谷鸟过滤器。参数校验比如ID必须为正整数、长度有限制。缓存击穿场景某个热点key在过期的瞬间大量并发请求同时读取发现缓存没有直接涌向数据库。解决互斥锁分布式锁第一个线程拿锁去查库并设置缓存其他线程等待。用Redis的SET lock NX EX 10。热点数据永不过期物理上不设置过期时间但在value里存逻辑过期时间后台异步更新。加逻辑过期缓存里存一个过期时间获取时发现已过期就异步重建同时返回旧值。缓存雪崩场景大量key同时过期或Redis宕机导致全部请求落到数据库。解决过期时间增加随机值setTime(300 Random.nextInt(60))。多级缓存本地缓存Caffeine加Redis避免全部打到数据库。熔断降级如果是Redis宕机使用Hystrix/Resilience4j降级直接返回默认值或错误提示。Redis集群高可用主从、Sentinel、Cluster。7. 分布式事务Seata AT模式与TCC在微服务架构下订单、库存、账户属于不同服务各自有独立数据库不能再依赖本地事务。Seata AT模式核心组件Transaction Coordinator (TC)全局事务协调者。Transaction Manager (TM)发起全局事务。Resource Manager (RM)每个分支事务资源。执行流程TM向TC申请全局事务IDXID。每个分支事务执行本地SQL时RM记录undo_log修改前的数据镜像和redo_log修改后的数据镜像。实际上Seata通过拦截JDBC先把数据快照存到undo_log表再执行业务SQL然后生成反向SQL。分支提交时RM向TC上报状态但本地事务先不提交或先提交取决于模式。如果所有分支成功TC通知各分支提交RM删除undo_log如果某分支失败TC通知各分支回滚RM用undo_log生成反向补偿SQL恢复原数据。AT模式对代码侵入小但因为它依赖全局锁在高并发下性能较差。TCC模式三个操作Try预留资源。例如库存服务预扣库存创建一个“预扣库存记录”。Confirm确认提交。把预留资源真正扣减删除记录。Cancel取消释放。把预扣库存加回来标识失败。TCC完全没有隔离问题但需要业务侵入每个接口都要写三个方法。适合高并发场景。MQ最终一致性也可以用Kafka/RocketMQ事务消息实现。例如订单服务发送“半消息”本地事务执行成功后再提交消息消费者消费成功后再ack。如果本地事务失败则回滚消息。8. OpenFeign 服务调用与负载均衡OpenFeign是声明式HTTP客户端适用于Spring Cloud微服务。EnableFeignClients开启Feign。接口上使用FeignClient(name order-service)name对应注册中心的服务名Eureka/Nacos。接口方法用RequestMapping或GetMapping等映射到目标服务的Controller。调用时OpenFeign会通过内部的负载均衡器Ribbon或Spring Cloud LoadBalancer选择实例。生产环境可以配置超时、重试、熔断。注意OpenFeign线程隔离不它本身没有隔离需要配合Sentinel或Resilience4j使用。9. Resilience4j 熔断器Resilience4j是轻量级容错库主要功能熔断器三种状态CLOSED关闭、OPEN打开、HALF_OPEN半开。默认请求正常时Closed当失败率超过阈值如50%进入Open直接拒绝请求经过等待时间后进入HalfOpen放部分试探请求如果成功则Closed失败则重新Open。限流RateLimiter控制每秒允许的请求数。隔离Bulkhead控制并发线程数或信号量。超时TimeLimiter限制调用时间。重试Retry配置重试次数和退避策略。在Spring Cloud中可以用CircuitBreaker注解配置resilience4j.instances。10. Kafka 生产消费流程与消息保证流程Producer按Topic发送消息可按key分区。Broker多副本存储Leader负责读写Follower同步。Consumer Group内的每个Consumer消费不同的分区保证每条消息只被组内一个Consumer消费。Consumer保存offset偏移量记录消费位置。重复消费与幂等Kafka的“至少一次”投递语义可能产生重复。需要消费者幂等数据库唯一键插入消费记录时冲突则跳过。RedisSETNX设置标记为“已处理”值为订单ID。业务本身幂等例如扣减库存前检查是否已扣减。顺序性全局严格有序需要将Topic分区数设为1但吞吐低。局部有序按订单ID或用户ID作为key取key.hashCode() % partitionNum保证相同key进入同一个分区分区内有序。11. JWT 与 OAuth2 的区别与结合JWTJSON Web Token由三段Base64编码组成Header算法、Payload用户信息、过期时间exp等、Signature签名。特点无状态服务端不保存session。可验证签名防止篡改。不可撤销无法主动让它失效需要黑名单或短TTL。OAuth2授权框架解决“用户授权第三方应用访问自己资源”的问题。流程用户访问第三方应用应用引导用户到授权服务器。用户同意授权授权服务器返回授权码Authorization Code。第三方应用拿授权码换Token。第三方应用用Token访问资源服务器。Token可以是任意形式但实际常用JWT。结合在微服务中常见做法使用Spring Authorization Server或Keycloak作为授权服务器签发JWT。资源服务器业务服务配置JWT验签校验iss、exp、scope。API网关统一校验JWT并将用户信息透传到下游。12. 监控体系Micrometer Prometheus Grafana ELKMicrometer一个监控门面类似SLF4J支持多种注册中心Prometheus、Datadog等。Spring Boot Actuator集成后通过/actuator/prometheus暴露指标。常见指标JVM内存、GC、线程。HTTP请求次数、耗时、错误率。自定义业务计数器、缓存命中率。Prometheus时间序列数据库通过pull方式抓取指标使用PromQL查询。支持告警规则AlertManager。Grafana可视化面板连接Prometheus数据源制作仪表盘。热门面板有JVM监控、Spring Boot监控。ELKLogstash收集日志过滤、格式化。Elasticsearch存储日志支持全文检索。Kibana可视化日志查询。现在也流行Loki轻量级日志系统或Filebeat直接采集。13. Logback 与 Log4j2 的选择LogbackSpring Boot默认配置简单性能足够支持异步Appender。Log4j2性能极其优秀异步日志吞吐高但需要额外排除冲突依赖。历史上出过Log4Shell漏洞注意版本。建议Spring Boot项目直接用Logback配置logback-spring.xml使用asyncAppender包装即可。14. Docker与Kubernetes部署Docker通过Dockerfile构建镜像如FROM openjdk:17-jdk-alpineCOPY app.jar /app.jarCMD java -jar /app.jar。docker build -t app:v1 .docker push到镜像仓库。Kubernetes定义Deployment副本数、镜像、资源限制、健康检查readiness/liveness。定义Service负载均衡暴露服务。滚动更新kubectl set image deployment/app appapp:v2K8s会逐步替换Pod实现零停机。CI/CDGit提交代码 → Jenkins/GitLab CI触发流水线。Maven/Gradle构建jar → 单元测试JUnit5Mockito → 打包Docker镜像 → 推送到Registry → kubectl更新K8s。15. Spring Security 与 Apache Shiro两者都是安全框架。Spring Security与Spring深度集成提供认证、授权、CSRF防护、Session管理、OAuth2客户端/资源服务器支持。学习曲线陡但功能强大。Apache Shiro轻量API友好容易嵌入非Spring应用但功能相对较少。现代微服务中常用Spring Security JWT OAuth2组合SecurityFilterChain配置放行URL增加JWT过滤器解析请求头Authorization: Bearer xxx并把用户信息塞入SecurityContext。以上就是这场面试中所有问题的详细答案。如果你也像谢飞机一样“管中窥豹”建议按照流程写一个电商秒杀DemoSpring Boot MyBatis Redis Kafka Seata OpenFeign Sentinel把每一步都亲手实现才能在面试官面前真正做到“飞机起飞而不是飞机打滑”。
返回列表