在 Java 后端开发中,下单接口是一个高频且高风险的场景。
很多项目在功能层面可以快速上线,但在流量上涨或异常重试时容易暴露系统性问题。常见评审意见也集中在同一点:
功能实现完整,但系统设计考虑不足。
本文用一个简化的 POST /orders 场景,对比“功能可用”与“工程可用”的实现差异。
【场景:下单接口的典型风险】
促销活动开始后,POST /orders 可能出现三类问题:
-
1. 用户重复点击,出现重复订单。
-
2. 瞬时流量冲高,服务被打挂。
-
3. 热点商品被频繁查询,数据库飙高。
如果只关注业务主流程,问题通常会在高峰流量或异常链路下出现。
更稳妥的做法是先定义系统边界:请求量级、重试行为、依赖稳定性和数据一致性要求。
【一个“可运行但风险高”的实现】
@PostMapping("/orders")
public Long create(@RequestBody CreateOrderRequest req) {
Order order = new Order(req.getUserId(), req.getProductId(), req.getQuantity());
return orderRepository.save(order).getId();
}
这段代码在测试环境通常可以通过,但线上容易暴露以下问题:
-
• 没有幂等:重复请求会下多单
-
• 没有限流:峰值流量直接压垮服务
-
• 没有缓存:热点查询都打到数据库
【系统化改造:3个基础动作】
1)限流:先保护系统,再谈体验
if (!rateLimiter.tryAcquire()) {
return ApiResponse.fail("系统繁忙,请稍后再试");
}
作用:将故障影响从系统级扩散控制为可接受的局部拒绝。
2)幂等:保证一次业务只生效一次
if (!idempotencyService.tryStart(request.getIdempotencyKey())) {
return ApiResponse.fail("重复请求,请勿重复提交");
}
作用:避免连点、超时重试、回放请求导致重复下单。
3)缓存:把热点从数据库前移
@Cacheable(cacheNames = "productStock", key = "#productId")
public Integer queryStock(Long productId) {
return stockRepository.queryStock(productId);
}
作用:对读多写少的热点数据进行前置缓存,降低数据库读取压力。
【把3个动作串起来:一段伪代码就够了】
下面的伪代码给出一个可复用的下单主流程模板:
public ApiResponse createOrder(CreateOrderRequest req) {
// 1. 限流保护
if (!rateLimiter.allow("create-order", req.getUserId())) {
return ApiResponse.fail("系统繁忙,请稍后再试");
}
// 2. 幂等控制(前端传 Idempotency-Key)
String key = req.getIdempotencyKey();
if (!idempotency.tryLock(key, 30)) {
return ApiResponse.fail("重复请求,请勿重复提交");
}
try {
// 3. 读缓存,减少数据库压力
int stock = cache.getOrLoad("stock:" + req.getProductId(), () -> db.queryStock(req.getProductId()));
if (stock < req.getQuantity()) {
return ApiResponse.fail("库存不足");
}
// 4. 扣减库存 + 创建订单(建议同事务)
transaction.execute(() -> {
db.decreaseStock(req.getProductId(), req.getQuantity());
db.createOrder(req.getUserId(), req.getProductId(), req.getQuantity());
});
return ApiResponse.ok("下单成功");
} finally {
idempotency.release(key);
}
}
这段流程不依赖特定框架,重点是把“流量保护、幂等控制、数据一致性”放在同一条链路中统一处理。
【系统设计的检查清单】
每次设计接口,建议先回答以下 4 个问题:
-
1. 如果用户重复提交,会发生什么?
-
2. 如果流量突然翻 10 倍,会发生什么?
-
3. 如果依赖服务抖动,会发生什么?
-
4. 如果数据库压力过高,有没有前置缓冲?
如果这 4 个问题能形成固定的评审习惯,接口稳定性和可维护性会显著提升。
后续会继续写:
-
• Java API 设计 7 个坑
-
• MySQL 慢查询排查路线
-
• Redis 三大问题工程解法
-
• Kafka vs RabbitMQ 选型模板
-
• 分布式事务落地策略