尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

谢飞机勇闯大厂面试:从JVM到微服务,一场关于电商秒杀与云原生的技术修罗场

谢飞机勇闯大厂面试:从JVM到微服务,一场关于电商秒杀与云原生的技术修罗场 谢飞机勇闯大厂面试从JVM到微服务一场关于电商秒杀与云原生的技术修罗场面试开场“谢飞机是吧坐。我是今天的面试官你可以叫我老K。我们部门做的是电商核心交易链路高并发、秒杀、分布式事务、云原生都碰。今天咱们不聊虚的就聊你简历上写的那些。准备好没有”老K面无表情地翻了翻屏幕语气像在念死刑判决书。“准备好了K老师我虽然叫飞机但我绝不飘稳如老狗”谢飞机一紧张差点把椅子坐翻。老K没理他直接开了第一轮。第一轮基础热场与JVM与构建老K“先别急着表忠心。你简历上写Java SE 8/11/17都熟。那我问你——JDK 8、11、17之间你日常开发最关注哪个版本的什么特性说三个就行。”谢飞机“啊这个我熟。8的Lambda和Stream11的var17的密封类……哦不对密封类是17的但线程本地握手也是17的。反正就是越来越强”老K“……行算你蒙对一半。那第二个问题线上有个Java服务CPU飙到100%但内存没涨你怎么排查”谢飞机“先用top看PID然后top -Hp看线程再jstack转换线程号看堆栈。要是看到业务线程死循环或者GC线程疯狂跑就定位了。如果栈看不出就用arthas的thread -n 3看看。反正就是一条龙”老K“哦还知道arthas。那第三个问题——你们项目构建是用Maven还是GradleMaven的依赖冲突怎么解决如果A依赖B的1.0C依赖B的2.0最终生效哪个”谢飞机“用Maven。冲突当然是看依赖树mvn dependency:tree然后排除或者统一版本。最终生效看谁在dependencyManagement里声明或者看深度越浅越优先——不对是先声明者优先哎呀反正就是最近原则……我昨天还排查过呢”老K“最近原则你确定那我提醒一下Maven是‘谁先声明谁赢’Gradle才是‘谁依赖近谁赢’。不过你提到了tree命令不错。最后一个问题你打包的时候Ant、Maven、Gradle有什么区别”谢飞机“Ant是老太太手动拧螺丝Maven是流水线工人约定大于配置Gradle是机器人又灵活又快。我们公司已经全面拥抱Gradle了但我其实还是写Maven多。”老K嘴角微微抽动“行第一轮勉强算你过。这轮问题背后是让你知道Java版本、JVM排查、依赖管理是你在电商系统里写安稳代码的底线。下一轮上点硬菜。”第二轮Spring与数据库与缓存与消息老K“你们电商项目用的是Spring Boot吧。现在有个秒杀活动前一万个用户能抢到一件商品。你怎么设计接口才能避免超卖从Spring事务、数据库锁、Redis、MQ这几个层面给我说说。”谢飞机“秒杀我熟首先在Redis里用原子递减预减库存减到0就拒绝。然后请求丢到MQ异步下单。数据库层面用乐观锁更新库存UPDATE t_goods SET stockstock-1 WHERE id? AND stock0这样不会超卖。事务嘛Transactional把扣库存和生成订单包一起。Redis崩了就用分布式锁兜底。”老K“嗯居然说得有条理。那我再追问——如果Redis是单节点秒杀流量极高Redis的QPS顶不住了怎么办还有消息队列你自己怎么选Kafka和RabbitMQ在订单场景的区别是什么”谢飞机“顶不住……那就加Redis集群或者本地缓存先挡一层但一致性不好搞。消息队列……我们项目用的Kafka吞吐高RabbitMQ功能全延迟低。订单场景我听说RabbitMQ更稳但秒杀大流量用Kafka更爽。反正都行吧”老K“你那个都行让我血压上来了。第三个问题你们用MyBatis还是JPA如果我现在要写一个动态排序的分页查询条件有商品ID、价格区间、状态还要关联分类表你怎么实现”谢飞机“我们用的MyBatis-Plus直接LambdaQueryWrapper分页插件PageHelper条件用if判断。关联表就写XML SQL动态SQL用和。JPA也行Specification太想念咒语。”老K“行吧动态SQL是个基本功。刚才你提到缓存和MQ我再问一个——如果秒杀成功后用户订单已经创建但Redis里的库存因为某些原因没回滚导致数据不一致你怎么办或者说Redis、数据库、MQ三者之间怎么做最终一致性”谢飞机“最终一致性……那就发消息出去有人监听发现不一致就补偿。或者本地消息表先写数据库和消息表再发MQ。还有用事务消息RocketMQ支持。但我们用的Kafka不支持所以……嗯……我们有个定时任务扫对账表把Redis里的库存刷新成数据库的库存。”老K“定时任务对账这个方案土但能用。第二轮算你及格。记住秒杀不是堆中间件而是先想清楚数据一致性和最终吞吐的权衡。下一轮你可能会哭。”第三轮微服务与云原生与监控与安全老K“看来你确实背过八股。现在换个场景——我们这有个直播带货的订单服务用户同时在看弹幕、下订单、领优惠券。整个系统拆成了服务A用户、服务B订单、服务C营销。服务之间用OpenFeign调用注册中心用了Nacos。请问如果订单服务突然挂了调用方应该怎么办”谢飞机“用Feign的话那就配置超时和重试。挂了就报错呗再不行就熔断用Resilience4j。把降级逻辑写在FeignClient的fallback里。还有不能重试写操作防止重复下单。”老K“哦那你再说说订单服务恢复了怎么保证它不会把刚启动但还没缓过来的流量瞬间打死也就是你有没有听过优雅上线和预热”谢飞机“优雅上线就是启动后不要让流量立刻进来Spring Boot内置了优雅停机但上线预热……Kubernetes有readiness探针探针通过后才能接流量。还有Ribbon的weighted响应时间权重不过那是老黄历了。我们现在用的K8s所以……就靠探针呗。”老K“嗯。接下来你们服务都容器化了K8s部署。我想看一个服务的实时日志、CPU、内存、链路追踪你分别用什么Jaeger和Zipkin有什么区别Prometheus和Micrometer如何配合”谢飞机“日志用ELK指标用PrometheusGrafanaMicrometer链路用Jaeger。Jaeger和Zipkin都是分布式追踪不过Jaeger是CNCF项目Zipkin是Twitter捐的。Micrometer是一个门面像SLF4J一样把指标暴露给Prometheus。嗯……就这些。”老K“那安全呢你们的API用的是JWT还是OAuth2如果第三方合作方要调用你们的接口你会用哪种方案密钥怎么管理”谢飞机“内部用JWT第三方肯定要OAuth2的授权码模式。JWT的签名K、K8s Secret、Keyloak统一认证就这么干。”老K“好——最后一个问题。假设你现在发现系统里有个接口被刷攻击者通过脚本快速注册大量账号并领取新用户优惠券导致营销成本飙升。你怎么止损从风控、幂等、限流、网关层面说。”谢飞机“这个……IP限流接入层用令牌桶。设备指纹手机号验证码。幂等表同一个设备只能领一次。管理员封号总之就是多管齐下黑名单红线。”老K“可以。三轮问完了。谢飞机你还挺有意思——理论上能聊细节一问就飘典型的面试放大器。不过比那些完全不会的强。”面试结束老K合上电脑靠在椅背上“咱们今天聊了JVM、构建、Spring、数据库、缓存、消息、微服务、K8s、监控、安全。你回去等通知吧。”谢飞机心里一凉“K老师……‘等通知’是不是就是凉了”老K难得笑了一下“不一定。好好复盘今天的问题。回去把我问的每个点都能自己讲明白下一次面试你就有谱了。走吧。”谢飞机鞠躬退出会议室时还在嘀咕“等通知……到底有没有通知啊”附录面试问题深度解析小白学习版第一轮Java版本、JVM排查、构建工具1. JDK 8/11/17 各自的核心特性JDK 82014年实现了真正的函数式编程——Lambda表达式、Stream API、接口默认方法、Optional类、新的日期时间APILocalDate、LocalDateTime以及方法引用。这些特性让Java从面向对象语言变成兼顾函数式风格的语言。JDK 112018年LTS版本带来了局部变量类型推断var只能用于局部变量不能用于成员变量还引入了ZGC实验性、HTTP Client标准化的java.net.http同时移除了一些废弃的API。JDK 172021年又一个LTS版本引入了密封类sealed限制类的继承范围、强封装JDK内部元素提升了安全性、预览版虚拟线程正式版在21以及Switch表达式增强17中已成为正式特性。面试重点能说出8/11的经典特性说明你有工程经验能说17的密封类和后续虚拟线程说明你有技术好奇心。2. 线上CPU 100%排查方法这是一道经典的故障排查题标准步骤top找到CPU占用最高的Java进程PID。top -Hp PID找到该进程内CPU占用最高的线程TID。printf %x\n TID将十进制TID转换为十六进制。jstack PID | grep -A 20 0x十六进制线程号找到线程堆栈定位到具体的业务代码行。如果看不到业务调用可能是GC线程用jstat -gcutil PID看GC频率如果GC异常用jmap -dump:formatb,fileheap.hprof PID导出堆再用MAT分析。如果CPU高但内存不涨有可能是死循环、正则灾难、或者JIT编译较少见。生产环境推荐用Arthas的thread -n 3能直接输出最忙线程和堆栈。3. Maven依赖版本冲突与规则三个构建工具Ant没有约定需要手动定义目标、Maven约定目录结构使用XML配置依赖、Gradle基于Groovy/Kotlin DSL支持增量构建和并发缓存。Maven的依赖传递机制会导致同一个库出现多个版本。版本仲裁原则最短路径优先如果A→B→C→D 1.0且A→E→D 2.0那么D 2.0胜出路径更短。如果路径长度相同则先声明者优先在POM中谁先定义依赖谁传递的版本生效。强制覆盖使用dependencyManagement可以强制指定版本或者使用exclusions排除传递依赖。排查命令mvn dependency:tree可以加-Dverbose查看详细冲突。第二轮秒杀、事务、缓存、MQ、ORM1. 秒杀不超卖的完整设计方案秒杀系统需要“前端拦截 缓存扣减 削峰填谷 数据库最终一致”。前端/网关层验证码、答题、限流令牌桶/滑动窗口拦截大部分流量。Redis层用Lua脚本DECR/DECRBY原子扣减库存。注意先要判断库存是否大于0。Redis的原子操作能承受每秒10万级别的扣减。MQ层把成功扣减库存的用户ID发送到MQ返回“排队中”。订单服务消费MQ异步创建订单避免直接写数据库的高并发压力。数据库层使用乐观锁UPDATE t_sku SET stock stock - 1 WHERE id ? AND stock 0订单表插入时使用唯一键如user_id sku_id activity_id防止重复下单。事务边界Transactional方法内只做数据库操作不要在事务内调用RPC或等待MQ响应否则会拉长事务时间。分布式锁兜底如果Redis不可用可以使用Redisson的RLock基于Redis或ZooKeeper锁但注意性能。2. Redis顶不住流量怎么办Redis集群使用Redis Cluster分片把秒杀key的哈希槽分散到多个节点。但单key仍可能热点要设计key后缀如sku_id_1,sku_id_2把读流量分散。本地缓存前置秒杀商品数量很少可以在网关或应用层使用Caffeine本地缓存提前返回“已售罄”。但要注意一致性因为本地缓存是进程级的所以通常只用于“售罄”这种标志位而非真实库存。分层降级如果Redis整体不可用直接走数据库但要设置最大并发量如Semaphore限流并使用强事务锁SELECT ... FOR UPDATE不过性能会下降很多。3. Kafka vs RabbitMQ| 维度 | Kafka | RabbitMQ | |---|---|---| | 吞吐量 | 极高百万级/秒 | 中高万级/秒 | | 延迟 | 毫秒级但偏高批量发送 | 微秒级低延迟 | | 消息模型 | 分区消费者组 | ExchangeQueueBinding | | 消费模式 | 拉模式消费者主动拉取 | 推模式多协议 | | 消息顺序 | 分区内有序 | 单队列内有序 | | 可靠性 | 配置acksall可高可靠 | 支持Publisher Confirm和事务 | | 典型场景 | 日志、流量削峰、大数据管道 | 业务消息需要灵活路由和事务性 |秒杀场景用Kafka更合适因为吞吐优先订单支付结果通知用RabbitMQ更灵活。4. MyBatis vs JPA 与动态SQLMyBatis半自动ORMSQL写在XML或注解中灵活可控适合复杂查询和优化。动态SQL使用if、where、set、foreach。JPAHibernate实现全自动ORM根据实体映射自动生成SQL适合CRUD简单的系统。复杂查询可以用Specification、QueryDSL或Query。分页MyBatis-Plus用PageJPA用Pageable。注意数据库性能瓶颈往往在SQL和索引ORM只是工具。在面试中你能写出复杂的动态SQL比背诵ORM原理更重要。5. 缓存、数据库、MQ最终一致性常见方案本地消息表事务消息在订单服务本地数据库开启事务创建订单 写一条“库存扣减消息”到本地消息表。事务提交后后台线程把消息发送给MQ。库存服务消费消息执行库存扣减。如果消费失败MQ重试如果多次失败发告警人工处理。另一种事务消息RocketMQ在发送半消息后执行本地事务事务成功则commit消息事务失败则rollback。Kafka原生不支持事务消息需要用其他流程。补偿定期用对账任务扫数据库订单和Redis库存以数据库为准覆盖Redis。这属于“最终一致 主动修复”。第三轮微服务、云原生、监控、安全1. OpenFeign调用失败的处理超时、重试、熔断、降级超时connectTimeout建立连接超时和readTimeout等待响应超时要分别设置通常readTimeout比connectTimeout更长。重试Feign默认不重试。可以配置RequestInterceptor或FeignBuilder的retryer。注意写操作POST/PUT/DELETE不能盲目重试否则可能产生重复数据。读操作可以重试但要配合幂等。熔断引入resilience4j-spring-boot2在FeignClient的fallback属性指定降级类。熔断器有三种状态关闭正常、打开直接失败、半开放少量请求试探。限流Resilience4j还支持RateLimiter和Bulkhead信号量隔离/线程池隔离。2. 优雅上下线和预热优雅下线设置server.shutdowngraceful在Spring Boot 2.3支持。K8s中配合preStop钩子先停止接收流量再等待在途请求完成spring.lifecycle.timeout-per-shutdown-phase。优雅上线K8s的readinessProbe和livenessProbe。就绪探针返回200才将Pod加入Service Endpoint。可以自定义一个HealthIndicator检查依赖数据库连接池、Redis、MQ是否准备好。预热对于缓存服务服务启动后可主动加载热点数据到本地缓存或Redis避免刚启动时大量请求直接打穿数据库。高层网关的灰度发布功能也可以实现分批放流。3. 监控与链路追踪日志ELKElasticsearch Logstash Kibana或EFKFluentd替代Logstash。统一日志格式包含traceId。指标Micrometer是一个指标门面类似SLF4J。在Spring Boot中使用micrometer-registry-prometheus暴露/actuator/prometheusPrometheus定期拉取Grafana展示。链路追踪Jaeger和Zipkin都支持OpenTracing/OpenTelemetry标准。区别Zipkin由Twitter开源较早支持Cassandra/MySQL存储。Jaeger由CNCF托管支持Jaeger查询语言和更丰富的过滤器原生支持gRPC。在实际工程中通常是通过Spring Cloud Sleuth或Micrometer Tracing生成traceId然后自动上报到Zipkin或Jaeger。Sleuth Zipkin在日志里自动带上[app, traceId, spanId]通过MQ或HTTP发送到Zipkin Server。4. JWT vs OAuth2 vs KeycloakJWT是token格式不是协议。它包含header、payload、signature用HMAC或RSA/ECDSA签名。适合无状态认证但无法主动吊销。OAuth2是授权框架有四种模式授权码第三方网页登录、密码模式轻量级不推荐、客户端凭据服务间调用、隐式模式已废弃。对于“第三方合作方调用你们API”正确做法是使用OAuth2的客户端凭据模式合作方拿到client_id和client_secret换取访问token携带token调用API。为了区分用户授权则使用授权码模式。Keycloak是开源IAM提供统一登录、用户管理、SSO。企业可以使用Keycloak作为OAuth2授权服务器内部服务使用JWT校验权限认证和业务逻辑解耦。密钥管理JWT签名密钥可以用vault或K8sSecret存放不要写进代码仓库。5. 接口防刷的风控设计方案接入层限流Nginxlimit_req或网关层Spring Cloud Gateway使用令牌桶算法按IP、设备ID、用户ID限流。设备指纹通过前端生成设备ID结合浏览器指纹存储到Redis判断短时间内设备数量是否异常。行为特征分析请求频率、单位时间内注册账号数量、IP变化、点击路径等用规则引擎如Drools或风控算法。幂等校验前端每次访问领取优惠券接口时带上一个requestIdUUID。服务端用RedisSETNX或者数据库唯一索引保证同一设备同一活动只能领一次。验证码注册时用滑块验证码或手机验证码增加自动化成本。黑名单/红线发现异常流量后将IP或设备加入黑名单并限制该账号的提券资格。异步风控可以把风控判断放在MQ中异步处理不阻塞正常请求。谢飞机回家后把上面的笔记抄了三遍。后来他终于收到通知虽然没过但他说“至少把面试官问的题都学会了下一次我能飞得更高。”
返回列表