互联网大厂Java面试实录:谢飞机大战面试官——电商系统架构篇

📅 2026/7/5 3:03:27
互联网大厂Java面试实录:谢飞机大战面试官——电商系统架构篇
互联网大厂Java面试实录谢飞机大战面试官——电商系统架构篇面试官严肃脸技术深度拉满 谢飞机时而灵光乍现时而水货附体 场景某互联网大厂Java高级工程师岗位电商业务线前情提要谢飞机江湖人称飞哥五年Java开发经验简历上写着精通微服务、高并发、分布式实际上...emmm懂的都懂。今天他来到了某一线互联网大厂面试Java高级工程师岗位面试官是传说中的灭绝师太——技术VP老张人称张怼怼。面试间里老张推了推眼镜目光如刀。老张谢飞机是吧坐。我们直接开始技术面。谢飞机擦了擦汗好的张哥您问第一轮Java核心与JVM · 电商商品系统业务场景设计一个电商商品详情页系统需要考虑缓存、并发、性能优化问题1你说说Java8和Java11在内存管理上有啥区别别背书结合业务讲。谢飞机思考片刻Java8是永久代PermGenJava8之后是元空间Metaspace...嗯...8的永久代在堆内存里容量有限容易OOM。11的元空间在本地内存默认不受限制...还有就是Java11有一些GC优化比如ZGC的引入。老张嗯基本概念对了。那结合电商场景你负责的商品详情页大量SKU信息、图片URL、促销活动数据怎么设计内存管理谢飞机这个...可以设个最大元空间大小-XX:MaxMetaspaceSize防止无限增长。然后商品信息用本地缓存用Caffeine或者Guava Cache设置过期时间和最大条目数避免堆内存被打满。老张微微点头思路可以。那你说说商品详情页QPS突然飙升比如双11秒杀JVM层面你怎么调优谢飞机呃...调大堆内存...年轻代调大点...用G1GC设置最大停顿时间...老张具体参数呢结合场景说。谢飞机有点慌了-Xms4g -Xmx4g -Xmn2g -XX:UseG1GC -XX:MaxGCPauseMillis200...嗯大概这样。老张参数背得还行。但你没说为什么用G1什么时候用CMS更没说你打算怎么监控GC。下一个问题。问题2HashMap在多线程环境下会出现什么问题如何解决ConcurrentHashMap的底层原理是什么在电商购物车场景中怎么用谢飞机这个我会HashMap在多线程put时会导致死循环Java8之前是头插法扩容时可能形成环形链表。Java8改成了尾插法避免了死循环但依然有数据丢失、size不准确的问题。所以并发场景用ConcurrentHashMapConcurrentHashMap底层...Java7是Segment分段锁默认16个Segment每个Segment是一个小HashMap。Java8改成了CAS synchronized对每个数组节点加锁粒度更细了。老张眼前一亮不错。那说说电商购物车——用户有购物车里面有商品列表、数量、价格还要支持多人同时操作你怎么设计数据结构谢飞机购物车用ConcurrentHashMap存储key是商品SKU IDvalue是购物车条目对象包含商品ID、数量、价格快照。用户维度的话可以用一个MapString, ConcurrentHashMapString, CartItem外层key是用户ID。public class ShoppingCart { // userId - (skuId - cartItem) private ConcurrentHashMapString, ConcurrentHashMapString, CartItem userCarts; public void addItem(String userId, String skuId, int quantity) { userCarts.computeIfAbsent(userId, k - new ConcurrentHashMap()) .merge(skuId, new CartItem(skuId, quantity), (oldVal, newVal) - { oldVal.setQuantity(oldVal.getQuantity() newVal.getQuantity()); return oldVal; }); } }老张嗯computeIfAbsent和merge用的不错知道CAS操作。那有个问题价格快照怎么处理用户加购时是100块过会儿降价到80了要不要给用户退差价谢飞机挠头这个...价格是在下单时根据SKU实时查询的购物车里的价格只是个展示参考。真正算钱在订单系统用Redis缓存最新价格下单时RedisDB双重校验防止价格被篡改。老张还行算你有基本电商认知。问题3电商系统中如何设计一个高可用的分布式锁Redis分布式锁有什么坑怎么避免谢飞机嗯...Redis分布式锁用SETNX EXPIRE...但可能会死锁如果SETNX成功了EXPIRE没执行的话。所以要用SET key value NX EX 30这种原子命令。但还有个问题如果锁过期了业务还没执行完A线程锁过期了B线程拿到锁A执行完了释放了B的锁。解决方案是value用唯一ID比如UUID线程ID释放锁时用Lua脚本检查是不是自己的锁再删除。// 加锁 String lockKey lock:order: orderId; String requestId UUID.randomUUID().toString(); Boolean locked redisTemplate.opsForValue() .setIfAbsent(lockKey, requestId, 30, TimeUnit.SECONDS); // 释放锁 - Lua脚本保证原子性 String luaScript if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end; redisTemplate.execute(new DefaultRedisScript(luaScript, Long.class), Arrays.asList(lockKey), requestId);老张那如果业务执行超过30秒呢锁自动释放了怎么办谢飞机这个...用Redisson它有个Watchdog机制每10秒自动续期一次只要业务没执行完锁就不会过期。还可以用Redlock算法实现高可用的分布式锁。老张Redlock有什么问题Martin Kleppmann那篇文章看过吗谢飞机额头冒汗呃...看过一点...Redlock依赖于时钟同步如果节点时钟发生跳跃会导致问题...还有GC pause可能导致锁的竞争判断出错...所以有些场景更适合用Zookeeper的临时顺序节点做锁CP模型更可靠...老张行Redlock的争议这个问题暂且放过。但至少你知道什么时候用Redis锁什么时候用ZK锁。问题4追问电商下单扣库存并发扣减怎么保证不超卖说具体方案。谢飞机这个几种方案MySQL乐观锁update sku set stock stock - #{num} where sku_id ? and stock #{num}Redis原子扣减lua脚本或decrBy命令扣减前检查库存消息队列串行化将扣库存请求放入MQ单线程消费最常用的是Redis缓存库存异步DB持久化Redis做预扣MQ异步同步到DB。-- Redis Lua脚本原子扣库存 local stock redis.call(get, KEYS[1]) if not stock or tonumber(stock) tonumber(ARGV[1]) then return -1 end redis.call(decrby, KEYS[1], ARGV[1]) return redis.call(get, KEYS[1])老张嗯。但你考虑过Redis在极端情况下丢数据吗如果Redis宕机内存里的库存数据丢了怎么办谢飞机可以开启AOF持久化每秒钟fsync一次。再配合数据库兜底如果Redis库存数据和DB不一致有定时任务做校对恢复。还有...预热阶段从DB加载库存到Redis设置过期时间到期后重新加载。老张不错这个回答有深度了。问题5Java SPI机制是什么在Jakarta EE和Spring中怎么用的谢飞机SPI就是Service Provider Interface服务发现机制。在META-INF/services/目录下放接口全限定名的文件文件内容是实现类的全限定名。ServiceLoader加载。JDBC驱动加载就是SPI实现的MySQL的com.mysql.cj.jdbc.Driver通过SPI注册到DriverManager。Jakarta EE原来是Java EE里用的更多比如Bean Validation、JAX-RS等规范都有SPI扩展点。Spring Boot的spring.factories和Spring 3.4的spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports也是类似的SPI思想。老张手写过SPI扩展吗谢飞机心虚呃...看过源码自己没写过...但原理知道。老张也没让你手写知道原理就行。第一轮结束问你第二轮。第二轮Spring全家桶与微服务 · 订单与支付系统业务场景分布式订单系统、支付回调、分布式事务问题1Spring Boot自动配置原理是什么手写一个starter你该怎么设计谢飞机Spring Boot启动时SpringBootApplication包含EnableAutoConfiguration它通过Import(AutoConfigurationImportSelector.class)导入配置。AutoConfigurationImportSelector会加载META-INF/spring.factories或spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件里的所有自动配置类。每个配置类上有ConditionalOnClass、ConditionalOnMissingBean等条件注解满足条件才生效。手写starter的话创建一个autoconfigure模块定义配置类创建starter模块引入autoconfigure在autoconfigure的spring.factories里注册配置属性用ConfigurationProperties绑定// 自定义Starter示例 Configuration ConditionalOnClass(RedisClient.class) EnableConfigurationProperties(RedisProperties.class) public class RedisAutoConfiguration { Bean ConditionalOnMissingBean public RedisClient redisClient(RedisProperties properties) { return new RedisClient(properties.getHost(), properties.getPort()); } }老张那Spring Boot 3.x相比2.x有什么核心变化你项目迁移时遇到什么问题谢飞机擦了擦汗Spring Boot 3基于Spring Framework 6最低要求Java 17。最大的变化是...Jakarta EE替换Java EEjavax.*包变成了jakarta.*包。还有AOT编译支持为GraalVM原生镜像铺路。迁移问题的话...主要是第三方组件的兼容性有些老版本的MyBatis、Shiro不支持Jakarta命名空间得升级版本。还有就是移除了Spring.factories的自动配置注册方式...老张只说对一部分。Spring Boot 3.0移除了spring.factories的自动配置支持3.4又加回来了但推荐新方式。其次Spring 6的声明式事务底层从AspectJ换成了...算了这个后面再说。问题2订单系统的状态机怎么设计从创建到完成经历哪些状态怎么防止状态跳转错误谢飞机订单状态一般有待支付→已支付→已发货→已收货→已完成还有取消、退款等分支。用Spring Statemachine或者自己实现状态机模式。最简单是用枚举Map定义状态流转规则public enum OrderStatus { PENDING_PAYMENT, // 待支付 PAID, // 已支付 SHIPPED, // 已发货 DELIVERED, // 已收货 COMPLETED, // 已完成 CANCELLED, // 已取消 REFUNDING, // 退款中 REFUNDED; // 已退款 private static final MapOrderStatus, SetOrderStatus TRANSITIONS new HashMap(); static { TRANSITIONS.put(PENDING_PAYMENT, Set.of(PAID, CANCELLED)); TRANSITIONS.put(PAID, Set.of(SHIPPED, REFUNDING)); TRANSITIONS.put(SHIPPED, Set.of(DELIVERED)); TRANSITIONS.put(DELIVERED, Set.of(COMPLETED, REFUNDING)); // ... } public boolean canTransitionTo(OrderStatus target) { return TRANSITIONS.getOrDefault(this, Collections.emptySet()).contains(target); } }然后结合数据库乐观锁更新时校验当前状态Update(UPDATE orders SET status #{newStatus} WHERE order_id #{orderId} AND status #{oldStatus}) int updateStatus(String orderId, OrderStatus oldStatus, OrderStatus newStatus);返回0表示状态不对事务回滚。老张很好这个回答非常扎实。那支付回调怎么处理支付宝/微信回调到了怎么保证幂等谢飞机支付回调必须保证幂等关键点回调处理使用分布式锁同一个订单ID加锁数据库唯一约束支付流水号transaction_id做唯一索引状态机前置校验只有待支付状态才能处理支付成功回调回调结果异步确认收到回调后先返回SUCCESS给支付平台业务异步处理// 幂等处理核心逻辑 Transactional public void handlePaymentCallback(PaymentCallback callback) { String lockKey lock:payment: callback.getOrderId(); RLock lock redissonClient.getLock(lockKey); try { if (lock.tryLock(5, 10, TimeUnit.SECONDS)) { // 1. 检查流水号是否已处理 if (paymentRecordDao.existsByTransactionId(callback.getTransactionId())) { return; // 已处理过直接返回 } // 2. 校验订单状态 Order order orderDao.selectById(callback.getOrderId()); if (order.getStatus() ! OrderStatus.PENDING_PAYMENT) { throw new BusinessException(订单状态异常); } // 3. 更新订单状态 记录支付流水同一个事务 orderDao.updateStatus(callback.getOrderId(), OrderStatus.PENDING_PAYMENT, OrderStatus.PAID); paymentRecordDao.insert(callback.toPaymentRecord()); } } finally { lock.unlock(); } }老张不错知道分布式锁事务唯一约束三板斧。问题3分布式事务你们怎么做的Seata AT模式原理是什么有什么局限性谢飞机我们用的是Seata AT模式。原理是一阶段业务SQL执行前Seata拦截SQL解析SQL语义生成前镜像数据修改前快照和后镜像数据修改后快照然后在业务表所在的库创建undo_log表记录镜像二阶段提交全部RM成功删除undo_log二阶段回滚有RM失败根据undo_log的前镜像生成反向SQL恢复数据局限嘛...不支持所有SQL比如一些复杂JOIN、批量操作事务隔离级别是读未提交脏读因为一阶段就提交了本地事务性能开销大每条SQL都要解析和生成镜像不适用于长事务场景老张如果不用Seata你有其他分布式事务方案吗比如TCC、SAGA谢飞机TCC的话Try-Confirm-Cancel模式每个服务要实现这三个接口。Try阶段预留资源Confirm执行Cancel回滚。比如下单场景Try冻结库存、冻结余额Confirm扣减库存、扣减余额Cancel释放冻结库存、释放冻结余额SAGA适用于长事务每个步骤有补偿操作比如旅游订单订机票→订酒店→租车每个步骤如果失败就执行补偿。但说实话...我们业务对一致性要求没到那个级别大部分场景用本地消息表MQ最终一致性就搞定了。老张你很实在。确实90%的场景用消息最终一致性就够了别为了分布式事务而分布式事务。问题4OpenFeign和Dubbo的区别是什么在电商系统中服务间调用怎么选型谢飞机OpenFeign是声明式HTTP客户端基于RESTful API和Spring Cloud深度集成。Dubbo是RPC框架基于TCP长连接性能更高。电商场景的话内部高并发调用如订单→库存Dubbo性能好可做服务治理对外暴露API如BFF层对前端OpenFeignRESTful更标准异构系统集成如对接第三方物流OpenFeign// OpenFeign声明式调用 FeignClient(name inventory-service, path /api/inventory) public interface InventoryClient { PostMapping(/deduct) ResultBoolean deductStock(RequestBody DeductRequest request); GetMapping(/query/{skuId}) ResultStockVO queryStock(PathVariable(skuId) String skuId); }老张Dubbo的服务发现和负载均衡底层怎么实现的谢飞机Dubbo服务发现支持多种注册中心Zookeeper、Nacos、Consul。负载均衡有随机、轮询、最少活跃调用等策略。还可以做权重配置给性能好的机器分配更多流量。如果用了gRPC的话基于HTTP/2支持双向流比Dubbo的序列化更灵活...不过我们没用过。老张行了第二轮结束。来第三轮。第三轮高并发架构与数据库 · 促销秒杀系统业务场景双11秒杀、热点商品缓存、数据库优化问题1设计一个秒杀系统应该从哪些层面做流量控制从网关到应用的完整链路讲一下。谢飞机深吸一口气秒杀系统核心是削峰填谷从入口到最终落单做全链路保护CDN层静态资源图片、HTML走CDN不要打到应用服务器Nginx/Gateway层限流Nginx lua限流、Spring Cloud Gateway的RequestRateLimiter拦截非法请求校验Token、User-Agent灰度分流部分流量导到新版本业务层前端限时按钮置灰、答题验证、随机延迟后端Redis预减库存、请求队列化消息队列MQ异步削峰下单请求先进MQ再慢慢消费数据库层数据库乐观锁扣库存读写分离读从库、写主库// Gateway层限流 - Token Bucket Bean public RouteLocator customRouteLocator(RouteLocatorBuilder builder) { return builder.routes() .route(seckill_route, r - r.path(/api/seckill/**) .filters(f - f.requestRateLimiter(config - config.setRateLimiter(redisRateLimiter()) .setKeyResolver(userKeyResolver()))) .uri(lb://seckill-service)) .build(); } // RedisRateLimiter默认实现基于令牌桶 Bean public RedisRateLimiter redisRateLimiter() { return new RedisRateLimiter(100, 200); // 每秒100个令牌突发200个 }老张很全面。那MQ削峰后消费端怎么保证不被打垮消息积压了怎么办谢飞机消费端要做好几点批量消费一次拉取一批消息批量处理动态线程池根据积压量动态调整消费线程数降级如果积压超过阈值拒绝新请求返回拥挤请稍候消息优先级秒杀消息优先处理积压了的话...提高分区数增加消费者实例或者临时写个脚本批量处理积压消息。老张嗯那你觉得单机Redis做秒杀库存够用吗热Key问题怎么解决谢飞机单机Redis肯定不行双11流量会把Redis打爆。热Key问题的话本地缓存Redis两级缓存热点商品数据在JVM本地也缓存一份减少Redis压力Key打散把同一个商品的库存分到多个Key上如stock:1001:0~stock:1001:9请求随机访问读写分离Redis Cluster主节点写从节点读// 本地缓存 Redis 两级缓存 public class StockCacheManager { private CacheString, Integer localCache Caffeine.newBuilder() .maximumSize(10000) .expireAfterWrite(5, TimeUnit.SECONDS) .build(); public Integer getStock(String skuId) { // 1. 先查本地缓存 Integer stock localCache.getIfPresent(skuId); if (stock ! null) return stock; // 2. 本地没有查Redis stock redisTemplate.opsForValue() .get(stock: skuId, Integer.class); if (stock ! null) { localCache.put(skuId, stock); } return stock; } }老张可以。但你说本地缓存不同节点的本地缓存数据不一致怎么办谢飞机用Redis的Pub/Sub或者消息广播某个节点扣减库存后通知其他节点失效本地缓存。不过秒杀场景几秒就结束了短暂的不一致可以接受...5秒过期时间过期重新加载。老张点头秒杀场景确实可以牺牲强一致性换取性能。问题2MySQL的InnoDB索引结构是什么样的B树相比B树优势在哪在订单查询场景中如何设计索引谢飞机InnoDB用B树做索引。B树和B树的区别B树只有叶子节点存数据B树所有节点都存数据B树叶子节点有链表指针范围查询快B树非叶子节点只存索引可以存更多索引条目树更矮优势磁盘IO少树更矮查询路径更短范围查询快叶子节点双向链表直接遍历排序好叶子节点天然有序订单查询索引设计-- 复合索引覆盖最常见的查询场景 ALTER TABLE orders ADD INDEX idx_user_status_time (user_id, status, create_time); -- 商家查订单 ALTER TABLE orders ADD INDEX idx_merchant_status (merchant_id, status); -- 订单号查询唯一 ALTER TABLE orders ADD UNIQUE INDEX idx_order_no (order_no); -- 时间范围查询如查询某天的订单 ALTER TABLE orders ADD INDEX idx_create_time (create_time);老张那我有个场景——SELECT * FROM orders WHERE user_id ? ORDER BY create_time DESC LIMIT 10你这个索引能走到吗文件排序还是索引排序谢飞机能走到因为索引是(user_id, status, create_time)user_id等值匹配后create_time天然有序是索引排序Using Index不需要文件排序Using filesort。但如果where条件是user_id ? AND status IN (?)那create_time的排序就失效了因为status用了IN查询导致索引跳变。老张非常好细节把握得很到位。问题3慢SQL你怎么排查和优化说具体工具和方法。谢飞机来劲了慢SQL排查三板斧开启慢查询日志SET GLOBAL slow_query_log ON; SET GLOBAL long_query_time 1; -- 超过1秒的记录 SET GLOBAL slow_query_log_file /var/log/mysql/slow.log;用EXPLAIN分析执行计划EXPLAIN SELECT * FROM orders o LEFT JOIN order_items oi ON o.id oi.order_id WHERE o.create_time 2024-01-01 AND o.status 0;关注typeALL→全表扫描、rows扫描行数、ExtraUsing filesort等Profile分析耗时SET profiling 1; -- 执行SQL SHOW PROFILES; SHOW PROFILE CPU, BLOCK IO FOR QUERY 1;优化方法加合适的索引复合索引、覆盖索引避免SELECT *只查需要的字段分页优化延迟关联、子查询优化大表分库分表ShardingSphere、MyCat老张分库分表后跨分片的分页查询和聚合查询怎么处理谢飞机额头冒汗这个...有两种方案全局视野法每个分片都查应用层聚合排序。比如每页20条如果有5个分片每片查20条合并后取前20条。但翻页越深性能越差。业务折中法限定查前N页或者用上一页最后一条的ID替代OFFSET分页每次只查大于某个ID的数据。实际项目中...大部分场景用Elasticsearch做搜索引擎MySQL只做事务性存储。ES天然支持分布式搜索聚合。老张ES方案确实更常见。那你用过ShardingSphere的分布式事务吗谢飞机呃...只是在项目里配置过没深入研究源码。ShardingSphere的分布式事务支持本地事务、XAAtomikos、Seata、BASE柔性事务...老张抬手打断不用说了看你这表情就知道你源码没看过。行吧第三轮结束。问题4你用过Resilience4j吗相比Hystrix有什么优势结合电商场景讲熔断降级。谢飞机用过Resilience4j是Netflix Hystrix停更后的替代品。优势轻量级不依赖其他框架Hystrix依赖Archaius模块化熔断器、限流器、重试、隔离、超时都是独立模块支持响应式对WebFlux友好Java8函数式用Supplier、Consumer等电商场景// 商品推荐服务熔断降级 Bean public CircuitBreaker recommendCircuitBreaker() { CircuitBreakerConfig config CircuitBreakerConfig.custom() .failureRateThreshold(50) // 50%失败率触发熔断 .waitDurationInOpenState(Duration.ofSeconds(30)) // 30秒后尝试半开 .slidingWindowSize(100) // 滑动窗口大小 .build(); return CircuitBreaker.of(recommendService, config); } // 使用 public ListProduct getRecommendProducts(String userId) { SupplierListProduct supplier CircuitBreaker.decorateSupplier( circuitBreaker, () - recommendClient.getRecommend(userId) ); // 降级方案返回热门商品 return Try.ofSupplier(supplier) .recover(throwable - hotProductService.getHotProducts(10)) .get(); }老张熔断器有几种状态半开状态是怎么工作的谢飞机三种状态CLOSED关闭、OPEN打开、HALF_OPEN半开。CLOSED正常状态请求正常通过OPEN熔断打开请求直接降级不调用真实服务HALF_OPEN过了一段时间waitDurationInOpenState允许少量请求通过测试如果成功就关闭熔断器失败就继续保持打开半开状态默认允许一次请求通过测试成功则关闭失败则重新打开。这个数量可以通过setPermittedNumberOfCallsInHalfOpenState配置。问题5终极问题如果让你从零搭建一个高可用、高并发的电商系统后端架构你会怎么设计画出核心架构图讲清楚每一层的职责和选型理由。谢飞机吸了一口气感觉这是送命题也是送分题老张怎么不会了谢飞机会我说说我的方案核心架构设计整体分层┌─────────────────────────────────────────────────────┐ │ API Gateway │ │ Spring Cloud Gateway Nginx Sentinel │ │ 限流 · 鉴权 · 路由 · 日志 · 灰度 │ ├─────────────────────────────────────────────────────┤ │ BFF (Backend For Frontend) │ │ 聚合服务Web端、App端各一套BFF │ ├────────────────────┬────────────────────────────────┤ │ 业务中台 │ 数据平台 │ │ │ │ │ 用户服务 │ ELK (日志) │ │ 商品服务 │ PrometheusGrafana (监控) │ │ 订单服务 │ SkyWalking (链路追踪) │ │ 支付服务 │ Flink (实时计算) │ │ 库存服务 │ Spark (离线分析) │ │ 促销服务 │ │ │ 物流服务 │ │ ├────────────────────┴────────────────────────────────┤ │ 中间件层 │ │ Kafka(消息) · Redis(缓存) · Elasticsearch(搜索) │ │ Seata(分布式事务) · XXL-Job(定时任务) │ ├─────────────────────────────────────────────────────┤ │ 基础设施层 │ │ MySQL(主从分片) · MongoDB · TiDB · OSS对象存储 │ └─────────────────────────────────────────────────────┘核心技术选型理由| 层次 | 组件 | 选型理由 | |------|------|----------| | 网关 | Spring Cloud Gateway | 响应式、性能好、集成Sentinel | | 注册中心 | Nacos | 支持AP/CP切换、配置中心一体化 | | RPC | Dubbo | 高性能、服务治理完善 | | 配置中心 | Nacos/Apollo | 实时推送、灰度发布 | | 消息队列 | Kafka | 高吞吐、持久化、分区消费 | | 缓存 | Redis Cluster | 高可用、自动分片 | | 数据库 | MySQL ShardingSphere | 分库分表、读写分离 | | 搜索引擎 | Elasticsearch | 商品搜索、订单查询 | | 链路追踪 | SkyWalking | 无侵入、APM监控 | | 监控 | Prometheus Grafana | 开源生态好、告警完善 | | 容器化 | Kubernetes | 弹性伸缩、服务编排 | | CI/CD | Jenkins GitLab CI | 自动化构建部署 |关键设计策略高可用服务多副本、异地多活、熔断降级高性能CDN加速、多级缓存、读写分离高扩展微服务拆分、水平扩展、无状态设计一致性最终一致性为主、强一致性为辅安全HTTPS、JWT鉴权、数据加密、防刷防爬老张沉默片刻眼神中带着一丝意外好家伙这一套还真让你给说明白了。虽然有些细节你前面回答得磕磕绊绊但整体架构设计能力确实不错。谢飞机松了口气谢谢张哥老张好吧今天的面试就到这。你的技术广度不错深度在某些方面还可以但JVM调优、分布式事务源码这些还需要加强。我们的面试结果会在三个工作日内通知你。谢飞机忐忑那张哥我大概...有戏吗老张面无表情回家等通知吧。谢飞机内心OS完蛋又是回家等通知…… 面试题深度解析小白必看第一轮Java核心与JVMQ1: Java8 vs Java11 内存管理技术要点Java8的永久代PermGen在堆内存中默认大小有限-XX:MaxPermSize容易OOMJava8的元空间Metaspace在本地内存Native Memory默认无上限避免永久代OOMJava11引入了ZGC可伸缩低延迟垃圾收集器停顿时间不超过10ms不受堆大小影响Java11引入了Epsilon GC无操作GC用于性能测试和短暂任务电商场景应用商品详情页使用Caffeine作为本地缓存设置maximumSize和expireAfterWrite防止堆内存打满元空间设置上限 -XX:MaxMetaspaceSize256m防止类加载过多导致内存泄漏双11大促时使用G1GC设置-XX:MaxGCPauseMillis100控制GC停顿时间使用JMX、Micrometer Grafana监控GC频率和停顿时间Q2: HashMap与ConcurrentHashMap技术要点HashMap Java7头插法→Java8尾插法解决死循环问题ConcurrentHashMap Java7分段锁→Java8 CASsynchronized锁粒度更细computeIfAbsent、merge等原子方法的使用ConcurrentHashMap的size()方法先无锁求和有修改则加锁重试电商场景应用购物车使用ConcurrentHashMapString, CartItemkey为SKU ID用户维度用ConcurrentHashMapString, ConcurrentHashMapString, CartItem加购操作使用merge方法保证原子性价格在购物车中为快照下单时实时查询Redis最新价格Q3: 分布式锁技术要点Redis SETNX EXPIRE → SET key value NX EX seconds原子操作唯一Value Lua脚本释放锁get和del原子操作Watchdog自动续期机制Redisson实现Redlock算法向大多数Redis节点申请锁过半成功则加锁成功Redlock争议依赖时钟同步、GC pause导致判断错误电商场景应用下单锁lock:order:{orderId}防止重复下单库存锁lock:stock:{skuId}保证扣库存原子性支付锁lock:payment:{orderId}保证支付回调幂等Q4: 库存扣减方案技术要点乐观锁UPDATE stock SET versionversion1 WHERE sku_id? AND version?原子扣减UPDATE sku SET stock stock - ? WHERE sku_id ? AND stock ?Redis Lua脚本检查扣减原子执行异步同步Redis预扣→MQ同步→DB落盘定时校对电商场景应用秒杀场景Redis预扣库存承受高并发普通下单直接操作DB乐观锁Redis宕机兜底AOF持久化 DB校对定时任务第二轮Spring全家桶与微服务Q1: Spring Boot自动配置原理技术要点EnableAutoConfiguration → Import(AutoConfigurationImportSelector) → 加载META-INF/spring.factoriesConditionalOnClass、ConditionalOnMissingBean、ConditionalOnProperty等条件注解Spring Boot 3.0移除spring.factories自动配置3.4重新支持但推荐AutoConfiguration.importsSpring Boot 3.xJava 17、Jakarta EE、AOT编译、GraalVM支持Q2: 订单状态机技术要点状态模式定义状态流转规则每个状态有明确的可达状态集合数据库乐观锁校验状态UPDATE ... WHERE status ?Spring Statemachine框架状态、事件、行为、守卫完整实现Saga模式长事务场景的状态补偿电商场景应用订单状态流转待支付→已支付→已发货→已收货→已完成逆向流程已支付→退款中→已退款状态校验乐观锁保证数据一致性Q3: 支付回调幂等技术要点分布式锁同一订单ID加锁防止并发唯一约束支付流水号transaction_id做唯一索引状态校验只有待支付状态才能处理支付成功幂等表记录已处理的回调流水号Q4: Seata AT模式与分布式事务技术要点Seata AT一阶段准备前镜像后镜像undo_log二阶段提交/回滚TCCTry预留资源、Confirm确认、Cancel取消SAGA步骤补偿操作适合长事务本地消息表业务操作消息入库在同一事务异步消费最终一致性MQ可靠消息定时补偿电商场景应用下单场景订单服务库存服务积分服务使用Seata AT支付场景使用TCCTry冻结金额、Confirm扣款、Cancel释放非核心链路如发短信使用MQ异步最终一致性第三轮高并发架构与数据库Q1: 秒杀系统设计技术要点全链路保护CDN→Nginx/Gateway→业务层→数据库限流算法令牌桶Token Bucket、漏桶Leaky Bucket、滑动窗口Redis预扣库存 Lua脚本MQ削峰生产者快速返回消费者异步处理热Key处理本地缓存Redis两级、Key打散、读写分离Q2: MySQL B树索引技术要点B树特点非叶子节点只存索引叶子节点存数据双向链表优势磁盘IO少、范围查询快、天然排序复合索引最左前缀原则覆盖索引查询字段都在索引中避免回表索引下推ICP在存储引擎层过滤减少回表次数电商场景应用订单查询复合索引(user_id, status, create_time)商家查询复合索引(merchant_id, status)时间范围查询单列索引(create_time)避免SELECT *使用覆盖索引Q3: 慢SQL排查技术要点慢查询日志long_query_time慢查询阈值EXPLAINtypeALL/INDEX/RANGE/REF/CONST、rows、ExtraProfileCPU、Block IO耗时分析优化策略加索引、覆盖索引、小表驱动大表、分页优化Q4: Resilience4j熔断降级技术要点三种状态CLOSED→OPEN→HALF_OPEN滑动窗口统计失败率/慢调用率隔离模式线程池隔离、信号量隔离重试超时限流熔断组合使用电商场景应用商品推荐服务熔断失败率50%熔断30秒后尝试半开降级方案返回热销商品缓存数据第三方物流查询设置超时500ms超时降级返回缓存数据 写在最后谢飞机虽然有些地方回答得不够深入但整体架构思维和技术广度还是可圈可点的。作为面试官老张关注的点其实很明确知其然更知其所以然不仅要会用还要理解原理业务场景结合技术脱离业务就是空中楼阁异常边界处理99%的bug都出在边界条件上架构设计能力从点→线→面的全局视角如果你是谢飞机这些题你能答上来几道欢迎在评论区分享你的面试经历如果这篇文章对你有帮助欢迎点赞、收藏、转发让更多Java同学看到