Java大厂面试系列:电商支付系统架构与高并发实战解析

📅 发布时间:2026/7/14 4:04:33 👁️ 浏览次数:
Java大厂面试系列:电商支付系统架构与高并发实战解析
Java大厂面试系列 - 电商支付系统架构篇场景设定某互联网大厂电商事业部Java高级工程师岗位面试 面试官张总监10年架构经验严肃认真 候选人谢飞机自称全栈高手简历写得很华丽第一轮面试基础功底摸底张总监翻看简历谢飞机是吧简历上写着精通Spring全家桶还有高并发电商项目经验谢飞机挺胸是的张总我之前在某电商公司做过秒杀系统日活百万那种张总监好那我们从基础开始。你说说Spring Boot的自动配置原理是什么SpringBootApplication注解背后做了哪些事情谢飞机这个我知道SpringBootApplication是个组合注解里面包含了SpringBootConfiguration、EnableAutoConfiguration和ComponentScan。自动配置就是通过spring.factories文件加载配置类然后用条件注解判断是否生效。张总监点头不错基本原理说清楚了。那我问你Spring Boot 2.7版本之后自动配置的加载方式有什么变化谢飞机呃...这个...应该是有变化的好像改了文件位置具体记不太清了...张总监行那我换个问题。在我们的电商系统中订单服务需要调用库存服务和用户服务你会用什么方式实现服务间调用OpenFeign的底层原理是什么谢飞机用OpenFeign啊声明式调用写个接口加个注解就行。底层原理嘛...是基于动态代理把接口方法转成HTTP请求...张总监动态代理只是其中一层。Feign Client的创建流程你清楚吗它和Ribbon是怎么配合做负载均衡的谢飞机这个...我知道能负载均衡具体实现细节...我主要关注业务代码底层没怎么看...张总监记笔记好最后一个问题。你在秒杀系统中是怎么做库存扣减的数据库层面如何保证超卖问题谢飞机先查库存库存够就减一然后创建订单。用数据库乐观锁version字段控制张总监如果并发量很大单纯用数据库乐观锁会有什么问题你有没有考虑过用Redis预扣减谢飞机Redis...用过就是用decr原子操作库存预热到Redis扣减成功再写数据库...大概这么个流程。第二轮面试架构设计深挖张总监刚才聊到Redis我想深入了解一下。在支付场景中用户发起支付请求后如果网络超时导致用户重复点击你怎么保证不会重复扣款说说幂等性设计。谢飞机生成支付流水号用唯一索引控制重复请求直接返回之前的结果张总监如果用分布式锁来实现Redisson的看门狗机制了解吗锁的续期是怎么做的谢飞机看门狗...就是会自动续期吧默认30秒每10秒续一次应该是这样...张总监那Redis集群模式下分布式锁可能有什么问题Redisson是怎么解决的谢飞机冒汗集群模式...主从切换的时候可能会丢锁RedLock算法...大概就是向多个节点加锁超过半数成功才算成功张总监嗯说对了一部分。那我再问你我们的支付系统需要对接多个支付渠道微信、支付宝、银联你会怎么设计支付路由策略用什么设计模式谢飞机用策略模式定义统一的支付接口每个渠道实现一下然后...可以用工厂模式根据参数创建具体的支付实现...张总监不错。那在支付回调处理中如果回调消息乱序到达比如退款回调比支付成功回调先到你怎么处理谢飞机这个...用状态机支付状态要有流转顺序不符合状态机的回调就丢弃张总监基本思路对了。那我问个具体的技术问题Spring的状态机框架用过吗怎么集成到项目中的谢飞机没...没用过Spring StateMachine我们是自己写的状态枚举加校验...第三轮面试生产实战拷问张总监最后问几个生产环境的问题。线上支付系统出现大面积超时你的排查思路是什么会用到哪些监控工具谢飞机先看监控大盘用Prometheus和Grafana看系统指标然后看ELK日志...如果是数据库慢看慢查询日志如果是GC问题看GC日志...张总监那如果用Arthas进行在线诊断怎么快速定位到慢方法Thread命令和Trace命令分别怎么用谢飞机Arthas...用过一点。thread -n 3看最忙的线程trace命令跟踪方法耗时...张总监好。最后一个问题我们的支付系统每天有上亿笔交易流水数据库单表已经扛不住了你会怎么设计分库分表方案用什么中间件谢飞机用ShardingSphere按用户ID分片保证同一个用户的订单在同一个分库分表中...这样关联查询方便。张总监分片键选用户ID确实不错。那分库分表之后全局唯一的订单号怎么生成雪花算法的原理你清楚吗谢飞机雪花算法...就是时间戳机器ID序列号64位长整型。具体怎么实现...我只知道用现成的...张总监合上笔记本好的谢飞机今天的面试就到这里。你对一些基础概念有了解但深度还需要加强特别是源码层面和生产实战经验。回去等通知吧有消息HR会联系你。谢飞机好的张总我回去一定好好补补源码等您消息 面试题目详细解析第一轮答案详解1. Spring Boot自动配置原理详解业务场景在电商系统中我们引入Spring Boot后只需要少量配置就能快速启动应用这是因为Spring Boot提供了大量自动配置。技术原理SpringBootApplication是组合注解Target(ElementType.TYPE) Retention(RetentionPolicy.RUNTIME) SpringBootConfiguration EnableAutoConfiguration ComponentScan(excludeFilters { ... }) public interface SpringBootApplication { }自动配置核心流程// 1. EnableAutoConfiguration 通过 Import 导入配置 Import(AutoConfigurationImportSelector.class) public interface EnableAutoConfiguration { } // 2. AutoConfigurationImportSelector 加载配置 public class AutoConfigurationImportSelector implements DeferredImportSelector { Override public String[] selectImports(AnnotationMetadata annotationMetadata) { // 获取所有自动配置类 ListString configurations getCandidateConfigurations(annotationMetadata, attributes); return configurations.toArray(new String[0]); } }Spring Boot 2.7的重大变化# 旧版本2.7之前加载 spring.factories META-INF/spring.factories # 新版本2.7改为加载新文件 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports配置类的条件注解AutoConfiguration ConditionalOnClass(DataSource.class) // 类路径存在DataSource才生效 ConditionalOnMissingBean(DataSource.class) // 容器中没有DataSource才生效 EnableConfigurationProperties(DataSourceProperties.class) public class DataSourceAutoConfiguration { Bean ConditionalOnMissingBean public DataSource dataSource(DataSourceProperties properties) { // 自动配置数据源 return DataSourceBuilder.create() .url(properties.getUrl()) .username(properties.getUsername()) .password(properties.getPassword()) .build(); } }2. OpenFeign调用原理与负载均衡业务场景在电商系统中订单服务需要调用库存服务扣减库存、调用用户服务查询用户信息OpenFeign让这些远程调用像本地方法一样简单。使用示例// 1. 定义Feign客户端接口 FeignClient(name inventory-service, fallback InventoryFallback.class) public interface InventoryClient { PostMapping(/api/inventory/deduct) ResultBoolean deductInventory(RequestBody InventoryDTO dto); GetMapping(/api/inventory/{skuId}) ResultInventoryVO getInventory(PathVariable(skuId) Long skuId); } // 2. 降级处理 Component public class InventoryFallback implements InventoryClient { Override public ResultBoolean deductInventory(InventoryDTO dto) { return Result.fail(库存服务降级); } Override public ResultInventoryVO getInventory(Long skuId) { return Result.fail(库存服务降级); } } // 3. 在订单服务中使用 Service public class OrderService { Autowired private InventoryClient inventoryClient; public Result createOrder(OrderDTO orderDTO) { // 调用库存服务就像调用本地方法 ResultBoolean result inventoryClient.deductInventory( new InventoryDTO(orderDTO.getSkuId(), orderDTO.getQuantity()) ); if (!result.isSuccess()) { return Result.fail(库存扣减失败); } // 创建订单逻辑... return Result.success(orderId); } }OpenFeign创建流程1. EnableFeignClients 扫描带有 FeignClient 的接口 2. FeignClientFactoryBean 为每个接口创建代理对象 3. 调用时动态代理拦截方法调用 4. Contract 解析方法上的注解GetMapping等 5. RequestTemplate 构建HTTP请求模板 6. Client.execute() 发送HTTP请求默认使用HttpURLConnection 7. 与Ribbon集成实现负载均衡3. 秒杀系统的库存扣减方案业务场景秒杀活动商品库存100件但可能有10万用户同时抢购需要保证不会超卖。Redis预扣减方案Service public class SeckillService { Autowired private StringRedisTemplate redisTemplate; Autowired private RabbitTemplate rabbitTemplate; // 秒杀接口 public Result seckill(Long userId, Long activityId) { String lockKey seckill:lock: activityId; String stockKey seckill:stock: activityId; // 1. 使用分布式锁防止同一用户重复秒杀 Boolean locked redisTemplate.opsForValue() .setIfAbsent(lockKey : userId, 1, 10, TimeUnit.SECONDS); if (!locked) { return Result.fail(请勿重复提交); } try { // 2. Redis原子扣减库存 Long stock redisTemplate.opsForValue().decrement(stockKey); if (stock 0) { // 3. 库存不足恢复库存 redisTemplate.opsForValue().increment(stockKey); return Result.fail(已被抢光); } // 4. 发送MQ消息异步创建订单 SeckillMessage message new SeckillMessage(userId, activityId); rabbitTemplate.convertAndSend(seckill.exchange, seckill.order, message); return Result.success(秒杀成功订单创建中); } finally { redisTemplate.delete(lockKey : userId); } } }最终方案对比| 方案 | 优点 | 缺点 | 适用场景 | |------|------|------|----------| | 数据库乐观锁 | 实现简单 | 性能差 | 低并发 | | Redis预扣减 | 高性能 | 需要额外维护Redis | 高并发秒杀 | | 分段锁 | 并行度高 | 实现复杂 | 超高并发 |第二轮答案详解4. 支付系统幂等性设计业务场景用户在支付页面点击确认支付由于网络抖动用户多次点击系统不能重复扣款。基于支付流水号的幂等方案Service public class PaymentService { Autowired private PaymentOrderMapper paymentOrderMapper; Transactional public ResultPaymentResult createPayment(PaymentRequest request) { // 1. 生成全局唯一支付流水号 String payNo generatePayNo(request); // 2. 先查询是否已存在该流水号 PaymentOrder existing paymentOrderMapper.selectByPayNo(payNo); if (existing ! null) { // 已存在直接返回之前的结果 return Result.success(existing.getPaymentResult()); } // 3. 创建支付订单唯一索引保证幂等 PaymentOrder paymentOrder new PaymentOrder(); paymentOrder.setPayNo(payNo); paymentOrder.setOrderNo(request.getOrderNo()); paymentOrder.setAmount(request.getAmount()); paymentOrder.setStatus(PayStatus.INIT); try { paymentOrderMapper.insert(paymentOrder); } catch (DuplicateKeyException e) { // 唯一索引冲突并发场景下另一个请求已创建 existing paymentOrderMapper.selectByPayNo(payNo); return Result.success(existing.getPaymentResult()); } // 4. 调用支付渠道 PaymentResult result callPaymentChannel(paymentOrder); // 5. 更新支付结果 paymentOrder.setStatus(result.getStatus()); paymentOrder.setResult(result); paymentOrderMapper.updateById(paymentOrder); return Result.success(result); } }5. Redisson分布式锁与看门狗机制业务场景订单超时未支付需要取消多个实例同时执行定时任务需要分布式锁保证同一订单只被处理一次。看门狗WatchDog机制原理// RedissonLock核心代码 public class RedissonLock implements RLock { // 看门狗默认超时时间30秒 private long lockWatchdogTimeout 30 * 1000; private void renewExpiration() { // 开启定时任务续期 Timeout task commandExecutor.getConnectionManager() .newTimeout(new TimerTask() { Override public void run(Timeout timeout) { // 每10秒lockWatchdogTimeout / 3续期一次 renewExpirationAsync(threadId); // 递归调用持续续期 renewExpiration(); } }, lockWatchdogTimeout / 3, TimeUnit.MILLISECONDS); } }6. 支付路由策略设计业务场景电商平台需要对接微信、支付宝、银联等多个支付渠道需要根据金额、用户、优惠活动等因素智能选择支付渠道。策略模式实现// 1. 定义支付策略接口 public interface PaymentStrategy { // 支付渠道 PaymentChannel getChannel(); // 是否支持该支付请求 boolean support(PaymentContext context); // 执行支付 PaymentResult pay(PaymentOrder order); } // 2. 支付路由器 Component public class PaymentRouter { Autowired private ListPaymentStrategy strategies; public PaymentStrategy route(PaymentContext context) { // 1. 根据用户指定的渠道 if (context.getChannel() ! null) { return strategies.stream() .filter(s - s.getChannel() context.getChannel()) .filter(s - s.support(context)) .findFirst() .orElseThrow(() - new BizException(指定渠道不支持)); } // 2. 默认选择最优渠道 return strategies.stream() .filter(s - s.support(context)) .min(Comparator.comparing(s - s.getFee(context.getAmount()))) .orElseThrow(() - new BizException(无可用支付渠道)); } }第三轮答案详解7. 线上支付超时排查与Arthas诊断Arthas常用命令# 1. 查看最忙的线程CPU占用最高 thread -n 3 # 2. 查看阻塞线程 thread -b # 3. 方法耗时追踪trace命令 trace com.example.PaymentService createPayment -n 5 --skipJDKMethod false # 4. 方法入参出参监控watch命令 watch com.example.PaymentService createPayment {params, returnObj} -x 3 # 5. 火焰图分析找出性能瓶颈 profiler start profiler stop --format html8. 分库分表方案与雪花算法雪花算法Snowflake原理64位ID结构 ┌─────────────────────────────────────────────────────────────────┐ │ 0 │ 00000000 00000000 00000000 00000000 00000000 0 │ 00000 00000 │ 000000000000 │ │ │ 41位时间戳 │ 5位数据中心 │ 5位机器ID │ 12位序列号 │ └─────────────────────────────────────────────────────────────────┘ 各部分含义 - 1位符号位始终为0 - 41位时间戳毫秒级可使用约69年 - 5位数据中心支持32个数据中心 - 5位机器ID每个数据中心支持32台机器 - 12位序列号每毫秒支持4096个ID分布式ID方案对比| 方案 | 优点 | 缺点 | 适用场景 | |------|------|------|----------| | UUID | 简单、无依赖 | 无序、太长 | 日志追踪等 | | 数据库自增 | 有序、简单 | 性能瓶颈、单点故障 | 低并发 | | Redis自增 | 高性能 | 需要额外维护Redis | 中高并发 | | 雪花算法 | 高性能、有序、全局唯一 | 依赖时钟、需要规划机器ID | 高并发生产环境 | 面试总结学习建议深入源码不能只停留在会用的层面要理解框架的设计思想实战积累多参与生产环境问题排查积累实战经验系统学习建立完整的知识体系不能只学碎片化知识关注新技术保持学习热情跟上技术发展趋势面试经验大厂面试不仅考察基础知识更看重解决问题的思路和深度。简历上写的技术栈一定要做到真正掌握而不是停留在用过的层面。本文由Java面试宝典整理发布欢迎关注获取更多面试干货