电商订单系统设计:Java高并发与分布式事务实战

发布时间:2026/8/9 17:43:19
电商订单系统设计:Java高并发与分布式事务实战 1. 为什么电商订单系统是Java面试的经典考题电商订单处理系统作为Java技术栈的集大成者几乎涵盖了企业级开发的所有核心要素。从我的面试官经历来看这套系统能全面考察候选人的技术广度与深度。订单系统看似简单实则暗藏玄机——它需要处理高并发写入、保证数据一致性、实现分布式事务还要应对秒杀等极端场景。大厂特别青睐这类考题的原因有三首先电商业务与公司营收直接挂钩系统稳定性至关重要其次订单系统涉及的技术栈与公司实际技术架构高度吻合最后这类系统有足够的复杂度来区分候选人水平。我曾见过两位候选人对同一个订单超卖问题给出截然不同的解决方案这正是面试官想看到的思维差异。2. 订单系统的核心模块拆解2.1 订单状态机设计订单状态流转是系统的核心逻辑。一个健壮的状态机应该具备public enum OrderStatus { CREATED(1), PAID(2), SHIPPED(3), COMPLETED(4), CANCELLED(5), REFUNDED(6); // 状态流转规则 private static final MapOrderStatus, SetOrderStatus allowedTransitions Map.of( CREATED, Set.of(PAID, CANCELLED), PAID, Set.of(SHIPPED, REFUNDED), SHIPPED, Set.of(COMPLETED, REFUNDED) ); public static boolean canTransition(OrderStatus from, OrderStatus to) { return allowedTransitions.getOrDefault(from, Set.of()).contains(to); } }关键点状态变更必须通过统一入口进行校验避免出现已取消的订单又被发货这类业务异常。建议采用状态模式(State Pattern)实现每个状态对应一个处理类。2.2 分布式ID生成方案订单号作为系统唯一标识需要满足全局唯一趋势递增利于数据库索引高可用避免单点故障Snowflake算法是常见选择但在容器化环境需要注意workerId的分配问题。我们改进的方案是public class DistributedIdGenerator { private static final long START_TIMESTAMP 1609459200000L; // 2021-01-01 private static final long WORKER_ID_BITS 10L; private static final long SEQUENCE_BITS 12L; private long workerId; private long sequence 0L; private long lastTimestamp -1L; public synchronized long nextId() { long timestamp timeGen(); if (timestamp lastTimestamp) { throw new RuntimeException(时钟回拨异常); } if (lastTimestamp timestamp) { sequence (sequence 1) ((1 SEQUENCE_BITS) - 1); if (sequence 0) { timestamp tilNextMillis(lastTimestamp); } } else { sequence 0L; } lastTimestamp timestamp; return ((timestamp - START_TIMESTAMP) (WORKER_ID_BITS SEQUENCE_BITS)) | (workerId SEQUENCE_BITS) | sequence; } // 动态获取workerId基于K8s StatefulSet的hostname private void initWorkerId() { String hostname System.getenv(HOSTNAME); this.workerId Long.parseLong(hostname.substring(hostname.lastIndexOf(-) 1)); } }3. 高并发下的技术应对策略3.1 库存扣减方案对比方案实现复杂度性能一致性适用场景数据库悲观锁低差强低频次商品乐观锁版本号中中最终常规秒杀Redis原子操作高极好弱超高并发秒杀预扣库存异步确认极高好最终大促活动实际项目中我们采用分级策略前端限流按钮置灰随机延迟网关层令牌桶限流服务层Redis Lua脚本扣减local stock tonumber(redis.call(GET, KEYS[1])) if stock 0 then return 0 end if stock tonumber(ARGV[1]) then redis.call(DECRBY, KEYS[1], ARGV[1]) return 1 end return 0持久层最终通过MQ异步同步到数据库3.2 分布式事务实践订单创建涉及多个服务调用扣减库存生成订单创建支付单我们采用Seata的AT模式实现GlobalTransactional public Order createOrder(OrderDTO orderDTO) { // 1. 扣减库存 inventoryService.reduceStock(orderDTO.getSkuId(), orderDTO.getQuantity()); // 2. 创建订单 Order order convertToOrder(orderDTO); orderMapper.insert(order); // 3. 创建支付单 paymentService.createPayment(order.getOrderNo(), order.getAmount()); return order; }避坑指南Seata的全局锁默认超时时间是60秒对于长事务需要调整client.tm.degrade-check-period参数。我们曾在促销活动时因为事务超时导致大量订单卡在处理中状态。4. 典型面试问题深度解析4.1 订单分库分表策略当订单表达到千万级时需要考虑分片。我们的分片键选择经历了三个阶段初期按用户ID哈希分片优点用户查询体验好缺点大客户数据集中导致热点中期时间范围用户ID复合分片按季度分库用户ID哈希分表需要处理跨库查询问题当前基因法分片从用户ID提取基因片段融入订单号既能分散数据又保证同一用户数据相对集中分片后订单号改造示例原订单号20230809123456 改造后09(用户基因)202308091234564.2 支付回调处理支付回调是订单系统的关键路径必须处理好以下问题幂等性通过唯一事务号状态机校验并发控制数据库行锁乐观锁补偿机制定时任务扫描待支付订单我们的回调处理流程Transactional public void handlePaymentCallback(PaymentNotifyDTO notify) { // 1. 幂等校验 Order order orderMapper.selectByOrderNo(notify.getOrderNo()); if (order.getStatus() ! OrderStatus.CREATED) { log.warn(订单已处理: {}, notify.getOrderNo()); return; } // 2. 更新订单状态 int updated orderMapper.updateStatus( notify.getOrderNo(), OrderStatus.CREATED, OrderStatus.PAID ); if (updated 0) { throw new OptimisticLockException(订单状态变更冲突); } // 3. 触发后续流程 eventPublisher.publishEvent(new OrderPaidEvent(order)); }5. 性能优化实战技巧5.1 查询优化方案订单列表查询的典型痛点多表关联用户信息、商品快照等复杂筛选条件时间范围、状态、关键词等我们的解决方案使用Elasticsearch构建订单宽表通过Logstash同步MySQL数据对常用字段建立组合索引分页优化避免LIMIT 10000,10式分页改用游标分页SELECT * FROM orders WHERE id ? AND status ? ORDER BY id ASC LIMIT 10冷热数据分离3个月前的订单归档到Tidb5.2 JVM参数调优订单系统的GC优化经验对象特点大量短生命周期的DTO对象订单缓存对象存活时间中等关键参数-XX:UseG1GC -XX:MaxGCPauseMillis200 -XX:InitiatingHeapOccupancyPercent45 -XX:NewRatio2 -XX:SurvivorRatio8 -XX:MaxTenuringThreshold15监控发现Young GC频繁 → 增大Eden区CMS失败 → 切换G1收集器大对象分配失败 → 调整Region大小6. 全栈视角下的系统设计6.1 前端优化策略订单提交防重复按钮点击后禁用本地生成临时订单ID请求失败自动重试机制长轮询查询状态function checkOrderStatus(orderNo) { const poll () { fetch(/api/orders/${orderNo}/status) .then(res { if (res.status completed) { // 跳转结果页 } else { setTimeout(poll, 2000); } }); }; poll(); }6.2 部署架构演进我们的架构迭代路径单体架构Spring Boot MySQL服务拆分订单服务库存服务支付服务云原生改造K8s部署Service Mesh流量管理多活数据中心当前技术栈开发框架Spring Boot 3 Spring Cloud Alibaba数据库MySQL 8主从 TiDB归档缓存Redis Cluster消息队列RocketMQ 5.0监控Prometheus Grafana SkyWalking在面试中展示架构演进思维往往能获得加分。我常问候选人如果让你从零设计这个系统会考虑哪些关键决策点 优秀的回答应该包含可扩展性、容错设计、成本控制等维度。