作为一名经历过多次大厂面试的老兵,我深知电商订单系统是Java技术栈面试的必考题。今天我就以模拟面试的形式,带大家深入拆解这个经典场景。不同于网上泛泛而谈的文章,我会结合自己参与过的真实项目,分享那些面试官真正想听的实战细节。
订单系统看似简单,实则涵盖了Java核心技术、Spring生态、分布式架构等全栈知识点。一个日均百万订单的系统,需要考虑多线程并发、事务一致性、服务熔断等复杂问题。接下来我们就从三个技术维度,层层深入这个经典面试题。
面试中常被问及线程池,但大多数候选人只会背四种线程池类型。在实际项目中,我推荐使用ThreadPoolExecutor自定义创建,这样能更精准控制线程行为。以下是电商订单处理的典型配置:
java复制ThreadPoolExecutor executor = new ThreadPoolExecutor(
10, // 核心线程数
50, // 最大线程数
60L, TimeUnit.SECONDS, // 空闲线程存活时间
new LinkedBlockingQueue<>(1000), // 任务队列
new ThreadFactoryBuilder().setNamePrefix("order-process-").build(), // 线程命名
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);
关键点:核心线程数建议设置为CPU核数的2-3倍;队列容量需要根据业务峰值评估;线程命名便于问题排查;CallerRunsPolicy保证高峰时不会丢失请求。
订单创建后的异步处理流程包含多个步骤:库存扣减、优惠券核销、物流单生成等。这里分享一个更完整的实现:
java复制public class OrderProcessor {
private final ThreadPoolExecutor executor;
public void asyncProcess(Order order) {
CompletableFuture.runAsync(() -> {
try {
// 1. 扣减库存
inventoryService.deduct(order);
// 2. 核销优惠
couponService.consume(order);
// 3. 生成物流单
logisticsService.create(order);
} catch (Exception e) {
log.error("订单处理失败: {}", order.getId(), e);
// 失败补偿逻辑
compensate(order);
}
}, executor);
}
}
在订单支付场景中,常见的死锁情况是:支付回调锁订单表,同时定时任务也在锁表。我们通过以下措施避免:
java复制Lock orderLock = new ReentrantLock();
Lock paymentLock = new ReentrantLock();
public void handlePayment() {
if (orderLock.tryLock(1, TimeUnit.SECONDS)) {
try {
if (paymentLock.tryLock(1, TimeUnit.SECONDS)) {
try {
// 业务处理
} finally {
paymentLock.unlock();
}
}
} finally {
orderLock.unlock();
}
}
}
订单退款是典型的需要事务管理的场景。我们来看一个真实案例:
java复制@Service
public class OrderServiceImpl implements OrderService {
@Transactional(propagation = Propagation.REQUIRED)
public void refund(Long orderId) {
// 1. 更新订单状态
orderRepository.updateStatus(orderId, REFUNDING);
// 2. 调用支付服务退款
paymentService.refund(orderId);
// 3. 恢复库存
inventoryService.restore(orderId);
}
}
踩坑提醒:支付服务如果通过Feign调用,需要特别注意事务边界。建议将远程调用放在事务的最后一步,避免长事务问题。
订单查询的N+1问题是面试高频考点。除了基本的Repository定义,我们更应关注性能优化:
java复制public interface OrderRepository extends JpaRepository<Order, Long> {
@EntityGraph(attributePaths = {"items", "payment"})
@Query("SELECT o FROM Order o WHERE o.userId = :userId")
List<Order> findByUserIdWithDetails(@Param("userId") Long userId);
@Query(value = "SELECT * FROM orders WHERE user_id = ?1 ORDER BY create_time DESC LIMIT ?2 OFFSET ?3",
nativeQuery = true)
List<Order> findUserOrdersPage(Long userId, int limit, int offset);
}
实战建议:
订单服务的可用性直接影响公司营收。我们的架构方案包括:
yaml复制hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 2000
circuitBreaker:
requestVolumeThreshold: 20
sleepWindowInMilliseconds: 5000
订单创建涉及多个服务调用,我们最终采用本地消息表+最大努力通知方案:
java复制public void createOrder(OrderDTO dto) {
// 1. 本地事务
Order order = saveOrder(dto);
// 2. 保存消息到本地表
saveMessage(order);
// 3. 异步发送消息
asyncSendMessage(order);
}
// 定时任务补偿
@Scheduled(fixedRate = 60000)
public void compensateMessages() {
List<Message> failed = messageRepository.findFailed();
failed.forEach(this::retrySend);
}
Spring Cloud Gateway的配置需要结合实际流量特点优化:
yaml复制spring:
cloud:
gateway:
routes:
- id: order-service
uri: lb://order-service
predicates:
- Path=/api/orders/**
filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 100
redis-rate-limiter.burstCapacity: 200
- name: Retry
args:
retries: 3
statuses: BAD_GATEWAY
关键配置说明:
面试官常会追问订单号设计,我们的方案是:
code复制时间戳(6位) + 业务类型(2位) + 用户ID后4位 + 随机数(4位)
示例代码:
java复制public String generateOrderNo(Long userId) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyMMdd");
String time = LocalDate.now().format(formatter);
String random = String.format("%04d", ThreadLocalRandom.current().nextInt(10000));
return time + "01" + String.format("%04d", userId % 10000) + random;
}
订单超时未支付自动取消是典型场景,我们使用Redis分布式锁:
java复制public boolean cancelOrder(Long orderId) {
String lockKey = "order:cancel:" + orderId;
String requestId = UUID.randomUUID().toString();
try {
// 获取锁
boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, requestId, 30, TimeUnit.SECONDS);
if (!locked) {
return false;
}
// 执行业务逻辑
return doCancel(orderId);
} finally {
// 释放锁
if (requestId.equals(redisTemplate.opsForValue().get(lockKey))) {
redisTemplate.delete(lockKey);
}
}
}
某次大促前,我们通过以下优化将订单查询性能提升5倍:
(user_id, status, create_time)java复制@Bean
public CaffeineCacheManager cacheManager() {
Caffeine<Object, Object> caffeine = Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(5, TimeUnit.MINUTES);
return new CaffeineCacheManager("orders", caffeine);
}
在面试中展示这些实战细节,能让面试官看到你真正的项目经验。记住,大厂面试不只要听正确答案,更要看解决问题的思路和深度。建议准备2-3个自己遇到的典型问题及解决方案,这会成为你的加分项。