# 企业级电商下单流程面试指南
> 🎯 面向面试准备 | 结合企业级项目经验
> 涵盖:下单流程、数据一致性、异常回滚、MQ应用、Redis应用
---
## 📚 目录
1. [下单流程全景图](#一下单流程全景图)
2. [核心步骤详解](#二核心步骤详解)
3. [数据一致性保证](#三数据一致性保证)
4. [异常处理与回滚](#四异常处理与回滚)
5. [MQ的作用与应用](#五mq的作用与应用)
6. [Redis的作用与应用](#六redis的作用与应用)
7. [面试高频问题](#七面试高频问题)
8. [面试话术模板](#八面试话术模板)
---
## 一、下单流程全景图
### 1.1 完整流程图
```
用户点击下单
│
▼
┌────────────────────────────────────────────────────────────┐
│ 前置校验阶段 │
│ ├─ 1. 用户登录态校验 │
│ ├─ 2. 参数合法性校验(商品ID、数量、地址等) │
│ ├─ 3. 风控校验(防刷单、黑名单) │
│ └─ 4. 幂等性校验(防重复提交) │
└────────────────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────┐
│ 库存预占阶段 │
│ ├─ 1. Redis预扣库存(快速失败) │
│ ├─ 2. 数据库扣减库存(乐观锁) │
│ └─ 3. 库存不足 → 快速返回失败 │
└────────────────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────┐
│ 核心下单阶段 │
│ ├─ 1. 计算订单金额(商品价格 + 运费 - 优惠) │
│ ├─ 2. 锁定/使用优惠券 │
│ ├─ 3. 生成订单(主订单 + 子订单) │
│ ├─ 4. 生成支付单 │
│ └─ 5. 发送MQ消息(延迟关单、库存确认等) │
└────────────────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────┐
│ 后置异步阶段(MQ消费) │
│ ├─ 1. 发送下单成功通知(短信/推送) │
│ ├─ 2. 更新用户统计数据 │
│ ├─ 3. 延迟队列:30分钟未支付自动取消 │
│ └─ 4. 同步到ES(订单搜索) │
└────────────────────────────────────────────────────────────┘
│
▼
返回订单信息,跳转支付
```
### 1.2 核心组件协作
```
┌─────────────┐
│ Gateway │
└──────┬──────┘
│
┌──────▼──────┐
│ Order服务 │◄──── 主服务
└──────┬──────┘
┌───────────────┼───────────────┐
│ │ │
┌──────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐
│ Product服务 │ │ Inventory服务│ │ Coupon服务 │
│ 商品信息 │ │ 库存管理 │ │ 优惠券 │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
└───────────────┼───────────────┘
│
┌─────────────────┼─────────────────┐
│ │ │
┌────▼────┐ ┌─────▼─────┐ ┌────▼────┐
│ MySQL │ │ Redis │ │ MQ │
│ 持久化 │ │ 缓存/锁 │ │ 异步 │
└─────────┘ └───────────┘ └─────────┘
```
---
## 二、核心步骤详解
### 2.1 前置校验
```java
@Service
@RequiredArgsConstructor
@Slf4j
public class OrderValidateService {
private final RedisTemplate<String, String> redisTemplate;
private final RiskControlService riskControlService;
/**
* 前置校验
*/
public void validate(CreateOrderRequest request) {
// 1. 参数校验
if (request.getProductId() == null || request.getQuantity() <= 0) {
throw new BusinessException("参数错误");
}
// 2. 幂等性校验(防重复提交)
String idempotentKey = "order:idempotent:" + request.getUserId() + ":" + request.getRequestId();
Boolean success = redisTemplate.opsForValue()
.setIfAbsent(idempotentKey, "1", 5, TimeUnit.MINUTES);
if (!success) {
throw new BusinessException("请勿重复提交");
}
// 3. 风控校验
RiskResult riskResult = riskControlService.check(request.getUserId());
if (riskResult.isBlocked()) {
throw new BusinessException("账户存在风险,暂时无法下单");
}
// 4. 限流校验(用户级别)
String rateLimitKey = "order:rate:" + request.getUserId();
Long count = redisTemplate.opsForValue().increment(rateLimitKey);
if (count == 1) {
redisTemplate.expire(rateLimitKey, 1, TimeUnit.MINUTES);
}
if (count > 10) { // 每分钟最多10次
throw new BusinessException("操作太频繁,请稍后再试");
}
}
}
```
### 2.2 库存扣减(Redis预扣 + 数据库扣减)
```java
@Service
@RequiredArgsConstructor
@Slf4j
public class InventoryService {
private final RedisTemplate<String, String> redisTemplate;
private final StockMapper stockMapper;
// Redis库存key
private static final String STOCK_KEY = "stock:product:";
/**
* 扣减库存(Redis预扣 + 数据库扣减)
*
* 为什么要两步?
* 1. Redis预扣:快速失败,减少数据库压力
* 2. 数据库扣减:最终一致性保证
*/
@Transactional(rollbackFor = Exception.class)
public boolean deductStock(Long productId, Integer quantity) {
// ========== 第一步:Redis预扣库存(快速失败)==========
String stockKey = STOCK_KEY + productId;
// 使用Lua脚本保证原子性
String luaScript =
"local stock = redis.call('get', KEYS[1]) " +
"if stock and tonumber(stock) >= tonumber(ARGV[1]) then " +
" redis.call('decrby', KEYS[1], ARGV[1]) " +
" return 1 " +
"else " +
" return 0 " +
"end";
Long result = redisTemplate.execute(
new DefaultRedisScript<>(luaScript, Long.class),
Collections.singletonList(stockKey),
String.valueOf(quantity)
);
if (result == null || result == 0) {
log.warn("Redis库存不足,productId: {}", productId);
return false; // 快速失败
}
// ========== 第二步:数据库扣减库存(乐观锁)==========
try {
int rows = stockMapper.deductWithOptimisticLock(productId, quantity);
if (rows == 0) {
// 数据库扣减失败,回滚Redis
redisTemplate.opsForValue().increment(stockKey, quantity);
log.warn("数据库库存扣减失败,productId: {}", productId);
return false;
}
return true;
} catch (Exception e) {
// 异常时回滚Redis
redisTemplate.opsForValue().increment(stockKey, quantity);
throw e;
}
}
/**
* 回滚库存(订单取消/支付超时)
*/
public void rollbackStock(Long productId, Integer quantity) {
// 1. Redis回滚
String stockKey = STOCK_KEY + productId;
redisTemplate.opsForValue().increment(stockKey, quantity);
// 2. 数据库回滚
stockMapper.increaseStock(productId, quantity);
log.info("库存回滚成功,productId: {}, quantity: {}", productId, quantity);
}
}
```
**数据库乐观锁SQL:**
```sql
<!-- 乐观锁扣减库存 -->
<update id="deductWithOptimisticLock">
UPDATE product_stock
SET quantity = quantity - #{quantity},
version = version + 1,
update_time = NOW()
WHERE product_id = #{productId}
AND quantity >= #{quantity}
AND version = #{version}
</update>
```
### 2.3 核心下单逻辑
```java
@Service
@RequiredArgsConstructor
@Slf4j
public class OrderService {
private final OrderMapper orderMapper;
private final InventoryService inventoryService;
private final CouponService couponService;
private final PaymentService paymentService;
private final RocketMQTemplate rocketMQTemplate;
private final RedissonClient redissonClient;
/**
* 创建订单
*/
@Transactional(rollbackFor = Exception.class)
public OrderResult createOrder(CreateOrderRequest request) {
Long userId = request.getUserId();
Long productId = request.getProductId();
Integer quantity = request.getQuantity();
// ========== 1. 获取分布式锁(防并发)==========
String lockKey = "order:lock:" + userId;
RLock lock = redissonClient.getLock(lockKey);
try {
// 尝试获取锁,等待3秒,持有10秒
boolean locked = lock.tryLock(3, 10, TimeUnit.SECONDS);
if (!locked) {
throw new BusinessException("系统繁忙,请稍后再试");
}
// ========== 2. 扣减库存 ==========
boolean stockResult = inventoryService.deductStock(productId, quantity);
if (!stockResult) {
throw new BusinessException("库存不足");
}
// ========== 3. 使用优惠券 ==========
BigDecimal discount = BigDecimal.ZERO;
if (request.getCouponId() != null) {
discount = couponService.useCoupon(userId, request.getCouponId());
}
// ========== 4. 计算订单金额 ==========
Product product = productService.getById(productId);
BigDecimal totalAmount = product.getPrice()
.multiply(new BigDecimal(quantity))
.subtract(discount);
// ========== 5. 创建订单 ==========
Order order = new Order();
order.setOrderNo(generateOrderNo());
order.setUserId(userId);
order.setProductId(productId);
order.setQuantity(quantity);
order.setTotalAmount(totalAmount);
order.setStatus(OrderStatus.UNPAID.getCode());
order.setCreateTime(new Date());
orderMapper.insert(order);
// ========== 6. 创建支付单 ==========
Payment payment = paymentService.createPayment(order);
// ========== 7. 发送延迟消息(30分钟后检查是否支付)==========
sendDelayCloseOrderMessage(order.getOrderNo());
// ========== 8. 发送订单创建消息(异步处理)==========
sendOrderCreatedMessage(order);
log.info("订单创建成功,orderNo: {}", order.getOrderNo());
return OrderResult.success(order, payment);
} catch (BusinessException e) {
throw e;
} catch (Exception e) {
log.error("创建订单异常", e);
throw new SystemException("系统繁忙,请稍后再试");
} finally {
// 释放锁
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
/**
* 发送延迟关单消息
*/
private void sendDelayCloseOrderMessage(String orderNo) {
// RocketMQ延迟消息,30分钟后触发
Message<String> message = MessageBuilder
.withPayload(orderNo)
.build();
// 延迟级别:16 = 30分钟
rocketMQTemplate.syncSend("ORDER_DELAY_CLOSE_TOPIC", message, 3000, 16);
log.info("发送延迟关单消息,orderNo: {}, delay: 30min", orderNo);
}
/**
* 发送订单创建消息
*/
private void sendOrderCreatedMessage(Order order) {
OrderMessage message = new OrderMessage();
message.setOrderNo(order.getOrderNo());
message.setUserId(order.getUserId());
message.setAction("CREATE");
rocketMQTemplate.asyncSend("ORDER_CREATED_TOPIC", message, new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
log.info("订单消息发送成功,orderNo: {}", order.getOrderNo());
}
@Override
public void onException(Throwable e) {
log.error("订单消息发送失败,orderNo: {}", order.getOrderNo(), e);
// 可以存入本地消息表,定时重试
}
});
}
}
```
---
## 三、数据一致性保证
### 3.1 一致性问题场景
| 场景 | 问题 | 解决方案 |
|------|------|---------|
| 库存与订单不一致 | 库存扣了,订单没创建成功 | 本地事务 + 补偿机制 |
| 订单与支付不一致 | 订单创建了,支付单没创建 | 本地事务 |
| 库存超卖 | 高并发下库存变负数 | Redis预扣 + 乐观锁 |
| 重复下单 | 用户点击多次 | 幂等性校验 |
| 消息丢失 | MQ消息没发出去 | 本地消息表 + 定时重试 |
### 3.2 本地事务保证(强一致性)
```java
/**
* 核心操作在同一个事务中
*
* 包含:库存扣减、优惠券使用、订单创建、支付单创建
* 任何一步失败,全部回滚
*/
@Transactional(rollbackFor = Exception.class)
public OrderResult createOrder(CreateOrderRequest request) {
// 1. 扣库存(数据库)
inventoryService.deductStock(productId, quantity);
// 2. 用优惠券
couponService.useCoupon(userId, couponId);
// 3. 创建订单
orderMapper.insert(order);
// 4. 创建支付单
paymentMapper.insert(payment);
// 以上任何一步失败,全部回滚
}
```
### 3.3 最终一致性保证(MQ + 补偿)
```java
/**
* 本地消息表 + 定时任务
* 解决:MQ消息可靠性问题
*/
@Service
public class OrderMessageService {
/**
* 发送消息(先写本地表,再发MQ)
*/
@Transactional(rollbackFor = Exception.class)
public void sendMessageReliably(Order order) {
// 1. 写入本地消息表(和订单在同一个事务)
LocalMessage message = new LocalMessage();
message.setMessageId(UUID.randomUUID().toString());
message.setOrderNo(order.getOrderNo());
message.setContent(JSON.toJSONString(order));
message.setStatus(0); // 待发送
message.setRetryCount(0);
localMessageMapper.insert(message);
// 2. 尝试发送MQ
try {
rocketMQTemplate.syncSend("ORDER_TOPIC", order);
// 发送成功,更新状态
message.setStatus(1); // 已发送
localMessageMapper.updateById(message);
} catch (Exception e) {
// 发送失败,定时任务会重试
log.error("MQ发送失败,等待重试", e);
}
}
/**
* 定时任务:重试发送失败的消息
*/
@Scheduled(fixedRate = 60000) // 每分钟执行
public void retryFailedMessages() {
// 查询待发送的消息
List<LocalMessage> messages = localMessageMapper.selectPending();
for (LocalMessage message : messages) {
if (message.getRetryCount() >= 5) {
// 重试超过5次,人工处理
message.setStatus(2); // 失败
localMessageMapper.updateById(message);
alertService.sendAlert("消息发送失败: " + message.getMessageId());
continue;
}
try {
rocketMQTemplate.syncSend("ORDER_TOPIC", message.getContent());
message.setStatus(1); // 成功
} catch (Exception e) {
message.setRetryCount(message.getRetryCount() + 1);
}
localMessageMapper.updateById(message);
}
}
}
```
### 3.4 分布式事务方案对比
| 方案 | 一致性 | 性能 | 复杂度 | 适用场景 |
|------|--------|------|--------|---------|
| **本地事务** | 强一致 | 高 | 低 | 单服务内 |
| **2PC/XA** | 强一致 | 低 | 高 | 金融级 |
| **TCC** | 强一致 | 中 | 高 | 资金相关 |
| **本地消息表** | 最终一致 | 高 | 中 | 跨服务 |
| **RocketMQ事务消息** | 最终一致 | 高 | 中 | 跨服务 |
| **Seata** | 可配置 | 中 | 中 | 通用 |
---
## 四、异常处理与回滚
### 4.1 异常分类与处理策略
```java
/**
* 异常处理策略
*/
@Service
public class OrderExceptionHandler {
/**
* 业务异常:明确告知用户
*/
public void handleBusinessException(BusinessException e, Order order) {
// 回滚已扣的库存
if (order.getStockDeducted()) {
inventoryService.rollbackStock(order.getProductId(), order.getQuantity());
}
// 回滚已使用的优惠券
if (order.getCouponUsed()) {
couponService.rollbackCoupon(order.getUserId(), order.getCouponId());
}
// 返回明确的错误信息
throw e;
}
/**
* 系统异常:通用错误 + 告警
*/
public void handleSystemException(Exception e, Order order) {
// 同上回滚
rollbackAll(order);
// 发送告警
alertService.sendAlert("订单创建异常: " + e.getMessage());
// 返回通用错误
throw new SystemException("系统繁忙,请稍后再试");
}
}
```
### 4.2 订单取消与回滚
```java
@Service
@RequiredArgsConstructor
@Slf4j
public class OrderCancelService {
/**
* 取消订单
*
* 触发场景:
* 1. 用户主动取消
* 2. 支付超时(30分钟)
* 3. 支付失败
*/
@Transactional(rollbackFor = Exception.class)
public void cancelOrder(String orderNo, String reason) {
// 1. 查询订单
Order order = orderMapper.selectByOrderNo(orderNo);
if (order == null) {
throw new BusinessException("订单不存在");
}
// 2. 校验订单状态(只有未支付的订单可以取消)
if (order.getStatus() != OrderStatus.UNPAID.getCode()) {
throw new BusinessException("当前订单状态不可取消");
}
// 3. 使用分布式锁(防止并发取消)
String lockKey = "order:cancel:" + orderNo;
RLock lock = redissonClient.getLock(lockKey);
try {
if (!lock.tryLock(3, 10, TimeUnit.SECONDS)) {
throw new BusinessException("操作进行中,请稍后");
}
// 4. 回滚库存
inventoryService.rollbackStock(order.getProductId(), order.getQuantity());
// 5. 回滚优惠券
if (order.getCouponId() != null) {
couponService.rollbackCoupon(order.getUserId(), order.getCouponId());
}
// 6. 更新订单状态
order.setStatus(OrderStatus.CANCELLED.getCode());
order.setCancelReason(reason);
order.setCancelTime(new Date());
orderMapper.updateById(order);
// 7. 关闭支付单
paymentService.closePayment(order.getOrderNo());
// 8. 发送取消消息(异步处理)
sendOrderCancelledMessage(order);
log.info("订单取消成功,orderNo: {}, reason: {}", orderNo, reason);
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}
```
### 4.3 支付超时自动取消(延迟队列)
```java
/**
* MQ消费者:处理延迟关单
*/
@Component
@RocketMQMessageListener(
topic = "ORDER_DELAY_CLOSE_TOPIC",
consumerGroup = "order-delay-close-group"
)
@Slf4j
public class OrderDelayCloseConsumer implements RocketMQListener<String> {
@Autowired
private OrderMapper orderMapper;
@Autowired
private OrderCancelService orderCancelService;
@Override
public void onMessage(String orderNo) {
log.info("收到延迟关单消息,orderNo: {}", orderNo);
try {
// 1. 查询订单状态
Order order = orderMapper.selectByOrderNo(orderNo);
if (order == null) {
log.warn("订单不存在,orderNo: {}", orderNo);
return;
}
// 2. 只处理未支付的订单
if (order.getStatus() == OrderStatus.UNPAID.getCode()) {
log.info("订单超时未支付,自动取消,orderNo: {}", orderNo);
orderCancelService.cancelOrder(orderNo, "支付超时自动取消");
} else {
log.info("订单状态已变更,无需取消,orderNo: {}, status: {}",
orderNo, order.getStatus());
}
} catch (Exception e) {
log.error("处理延迟关单失败,orderNo: {}", orderNo, e);
// 可以重新发送消息重试
}
}
}
```
---
## 五、MQ的作用与应用
### 5.1 MQ在订单系统中的作用
```
┌─────────────────────────────────────────────────────────┐
│ MQ 的四大作用 │
├─────────────┬───────────────────────────────────────────┤
│ 异步解耦 │ 下单后异步发通知、更新统计,不阻塞主流程 │
├─────────────┼───────────────────────────────────────────┤
│ 流量削峰 │ 秒杀场景,MQ缓冲请求,保护后端服务 │
├─────────────┼───────────────────────────────────────────┤
│ 延迟处理 │ 延迟队列实现:30分钟未支付自动取消 │
├─────────────┼───────────────────────────────────────────┤
│ 最终一致 │ 跨服务数据同步,保证最终一致性 │
└─────────────┴───────────────────────────────────────────┘
```
### 5.2 MQ应用场景详解
```java
/**
* 场景1:异步解耦 - 订单创建后的异步处理
*/
@Component
@RocketMQMessageListener(topic = "ORDER_CREATED_TOPIC", consumerGroup = "order-created-group")
public class OrderCreatedConsumer implements RocketMQListener<OrderMessage> {
@Override
public void onMessage(OrderMessage message) {
// 异步处理,不影响下单主流程
// 1. 发送下单成功短信
smsService.sendOrderSuccessSms(message.getUserId(), message.getOrderNo());
// 2. 发送微信推送
wechatService.sendOrderNotification(message);
// 3. 更新用户订单统计
userStatService.incrementOrderCount(message.getUserId());
// 4. 同步到ES(订单搜索)
orderEsService.syncToEs(message.getOrderNo());
// 5. 记录用户行为(大数据分析)
behaviorService.recordOrderBehavior(message);
}
}
/**
* 场景2:延迟队列 - 30分钟未支付自动取消
*/
// 发送延迟消息
rocketMQTemplate.syncSend("ORDER_DELAY_CLOSE_TOPIC", message, 3000, 16); // 16级 = 30分钟
/**
* 场景3:流量削峰 - 秒杀场景
*/
@Service
public class SeckillService {
public String seckill(Long userId, Long productId) {
// 1. 快速校验(Redis)
if (!checkStock(productId)) {
return "已售罄";
}
// 2. 发送MQ,异步创建订单
SeckillMessage message = new SeckillMessage(userId, productId);
rocketMQTemplate.asyncSend("SECKILL_ORDER_TOPIC", message, ...);
// 3. 立即返回(排队中)
return "下单请求已提交,请稍后查看";
}
}
/**
* 场景4:事务消息 - 保证数据一致性
*/
@Service
public class TransactionalMessageService {
/**
* RocketMQ事务消息
*/
public void sendTransactionalMessage(Order order) {
Message<Order> message = MessageBuilder.withPayload(order).build();
// 发送事务消息
rocketMQTemplate.sendMessageInTransaction(
"ORDER_TOPIC",
message,
order // 传递参数给本地事务
);
}
/**
* 本地事务执行器
*/
@RocketMQTransactionListener
public class OrderTransactionListener implements RocketMQLocalTransactionListener {
@Override
public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) {
Order order = (Order) arg;
try {
// 执行本地事务
orderService.createOrderInTransaction(order);
return RocketMQLocalTransactionState.COMMIT;
} catch (Exception e) {
return RocketMQLocalTransactionState.ROLLBACK;
}
}
@Override
public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
// 回查本地事务状态
String orderNo = msg.getHeaders().get("orderNo", String.class);
Order order = orderMapper.selectByOrderNo(orderNo);
if (order != null) {
return RocketMQLocalTransactionState.COMMIT;
}
return RocketMQLocalTransactionState.ROLLBACK;
}
}
}
```
### 5.3 RocketMQ延迟级别
```
延迟级别对照表:
Level 1 = 1s Level 2 = 5s Level 3 = 10s
Level 4 = 30s Level 5 = 1m Level 6 = 2m
Level 7 = 3m Level 8 = 4m Level 9 = 5m
Level 10 = 6m Level 11 = 7m Level 12 = 8m
Level 13 = 9m Level 14 = 10m Level 15 = 20m
Level 16 = 30m ← 订单超时取消常用
Level 17 = 1h Level 18 = 2h
```
---
## 六、Redis的作用与应用
### 6.1 Redis在订单系统中的作用
```
┌─────────────────────────────────────────────────────────┐
│ Redis 的六大作用 │
├─────────────┬───────────────────────────────────────────┤
│ 库存预扣 │ 快速扣减库存,减少数据库压力 │
├─────────────┼───────────────────────────────────────────┤
│ 分布式锁 │ 防止并发问题:重复下单、重复支付 │
├─────────────┼───────────────────────────────────────────┤
│ 幂等控制 │ 防重复提交,保证接口幂等性 │
├─────────────┼───────────────────────────────────────────┤
│ 限流控制 │ 用户级/接口级限流,防刷单 │
├─────────────┼───────────────────────────────────────────┤
│ 数据缓存 │ 缓存热点商品、用户信息等 │
├─────────────┼───────────────────────────────────────────┤
│ 会话存储 │ 存储登录态、购物车等 │
└─────────────┴───────────────────────────────────────────┘
```
### 6.2 Redis应用场景详解
```java
/**
* 场景1:库存预扣(Lua脚本保证原子性)
*/
@Service
public class StockRedisService {
private static final String STOCK_KEY = "stock:product:";
// Lua脚本:原子性扣减库存
private static final String DEDUCT_STOCK_LUA =
"local stock = redis.call('get', KEYS[1]) " +
"if stock and tonumber(stock) >= tonumber(ARGV[1]) then " +
" redis.call('decrby', KEYS[1], ARGV[1]) " +
" return 1 " +
"else " +
" return 0 " +
"end";
/**
* 扣减库存
*/
public boolean deductStock(Long productId, Integer quantity) {
String key = STOCK_KEY + productId;
Long result = redisTemplate.execute(
new DefaultRedisScript<>(DEDUCT_STOCK_LUA, Long.class),
Collections.singletonList(key),
String.valueOf(quantity)
);
return result != null && result == 1;
}
/**
* 初始化库存(商品上架时)
*/
public void initStock(Long productId, Integer quantity) {
String key = STOCK_KEY + productId;
redisTemplate.opsForValue().set(key, String.valueOf(quantity));
}
/**
* 回滚库存
*/
public void rollbackStock(Long productId, Integer quantity) {
String key = STOCK_KEY + productId;
redisTemplate.opsForValue().increment(key, quantity);
}
}
/**
* 场景2:分布式锁(Redisson实现)
*/
@Service
public class DistributedLockService {
@Autowired
private RedissonClient redissonClient;
/**
* 下单加锁(防止同一用户并发下单)
*/
public OrderResult createOrderWithLock(CreateOrderRequest request) {
String lockKey = "order:lock:user:" + request.getUserId();
RLock lock = redissonClient.getLock(lockKey);
try {
// 等待3秒,持有锁10秒
boolean locked = lock.tryLock(3, 10, TimeUnit.SECONDS);
if (!locked) {
throw new BusinessException("操作太频繁,请稍后再试");
}
// 执行下单逻辑
return doCreateOrder(request);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new SystemException("系统繁忙");
} finally {
// 释放锁
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}
/**
* 场景3:幂等控制
*/
@Service
public class IdempotentService {
/**
* 幂等校验
*
* @param requestId 请求唯一ID(前端生成)
* @param userId 用户ID
* @return true=首次请求,false=重复请求
*/
public boolean checkIdempotent(String requestId, Long userId) {
String key = "order:idempotent:" + userId + ":" + requestId;
// setIfAbsent = SETNX,只有key不存在时才设置成功
Boolean success = redisTemplate.opsForValue()
.setIfAbsent(key, "1", 5, TimeUnit.MINUTES);
return Boolean.TRUE.equals(success);
}
}
/**
* 场景4:限流控制
*/
@Service
public class RateLimitService {
/**
* 用户级限流:每分钟最多下10单
*/
public boolean checkUserRateLimit(Long userId) {
String key = "order:rate:user:" + userId;
Long count = redisTemplate.opsForValue().increment(key);
if (count == 1) {
// 首次访问,设置过期时间
redisTemplate.expire(key, 1, TimeUnit.MINUTES);
}
return count <= 10;
}
/**
* 滑动窗口限流(更精确)
*/
public boolean checkSlidingWindowLimit(Long userId, int maxCount, int windowSeconds) {
String key = "order:sliding:" + userId;
long now = System.currentTimeMillis();
long windowStart = now - windowSeconds * 1000;
// 使用ZSet实现滑动窗口
redisTemplate.opsForZSet().removeRangeByScore(key, 0, windowStart);
Long count = redisTemplate.opsForZSet().zCard(key);
if (count != null && count >= maxCount) {
return false;
}
redisTemplate.opsForZSet().add(key, String.valueOf(now), now);
redisTemplate.expire(key, windowSeconds, TimeUnit.SECONDS);
return true;
}
}
/**
* 场景5:购物车
*/
@Service
public class CartRedisService {
private static final String CART_KEY = "cart:user:";
/**
* 添加购物车
*/
public void addToCart(Long userId, Long productId, Integer quantity) {
String key = CART_KEY + userId;
// 使用Hash结构:field=商品ID,value=数量
redisTemplate.opsForHash().increment(key, String.valueOf(productId), quantity);
// 设置过期时间(7天)
redisTemplate.expire(key, 7, TimeUnit.DAYS);
}
/**
* 获取购物车
*/
public Map<Long, Integer> getCart(Long userId) {
String key = CART_KEY + userId;
Map<Object, Object> entries = redisTemplate.opsForHash().entries(key);
Map<Long, Integer> cart = new HashMap<>();
entries.forEach((k, v) -> {
cart.put(Long.parseLong(k.toString()), Integer.parseInt(v.toString()));
});
return cart;
}
}
/**
* 场景6:缓存热点商品
*/
@Service
public class ProductCacheService {
private static final String PRODUCT_KEY = "product:";
/**
* 获取商品(缓存穿透防护)
*/
public Product getProduct(Long productId) {
String key = PRODUCT_KEY + productId;
// 1. 查缓存
String json = redisTemplate.opsForValue().get(key);
if (json != null) {
if ("NULL".equals(json)) {
return null; // 空值缓存,防止缓存穿透
}
return JSON.parseObject(json, Product.class);
}
// 2. 查数据库
Product product = productMapper.selectById(productId);
// 3. 写缓存
if (product != null) {
redisTemplate.opsForValue().set(key, JSON.toJSONString(product),
1, TimeUnit.HOURS);
} else {
// 空值缓存,防止缓存穿透
redisTemplate.opsForValue().set(key, "NULL", 5, TimeUnit.MINUTES);
}
return product;
}
}
```
### 6.3 Redis数据结构选择
| 场景 | 数据结构 | Key示例 | Value示例 |
|------|---------|---------|-----------|
| 库存 | String | stock:product:1001 | 100 |
| 购物车 | Hash | cart:user:123 | {productId: quantity} |
| 限流计数 | String | rate:user:123 | 5 |
| 分布式锁 | String | lock:order:123 | uuid |
| 排行榜 | ZSet | rank:sales | {productId: score} |
| 最近浏览 | List | recent:user:123 | [productId1, productId2] |
---
## 七、面试高频问题
### Q1:下单过程中如何保证数据一致性?
**答**:
我们采用**本地事务 + 最终一致性**的方案:
1. **核心操作使用本地事务**:库存扣减、优惠券使用、订单创建在同一个事务中,任何一步失败全部回滚。
2. **非核心操作使用MQ异步处理**:发送通知、更新统计等通过MQ异步执行,不影响主流程。
3. **MQ消息可靠性保证**:使用本地消息表+定时重试,确保消息不丢失。
4. **补偿机制**:订单取消时,回滚库存、优惠券等操作。
---
### Q2:如何防止库存超卖?
**答**:
我们采用**Redis预扣 + 数据库乐观锁**的双重保障:
1. **Redis预扣**:使用Lua脚本原子性扣减,快速失败。
```lua
if stock >= quantity then
decrby(key, quantity)
return 1
else
return 0
end
```
2. **数据库乐观锁**:
```sql
UPDATE stock SET quantity = quantity - #{num}
WHERE product_id = #{id} AND quantity >= #{num}
```
3. **分布式锁**:同一用户并发下单时,使用Redisson加锁。
---
### Q3:订单超时未支付如何处理?
**答**:
使用**RocketMQ延迟消息**实现:
1. 订单创建时,发送30分钟延迟消息
2. 30分钟后,消费者检查订单状态
3. 如果仍未支付,自动取消订单并回滚库存
也可以用**定时任务扫描**:每分钟扫描超时订单,但延迟消息更实时。
---
### Q4:MQ消息丢失怎么办?
**答**:
使用**本地消息表**保证消息可靠性:
1. 订单创建时,先写本地消息表(同一事务)
2. 尝试发送MQ
3. 发送成功,更新消息状态为已发送
4. 发送失败,定时任务重试
5. 重试超过5次,人工处理
---
### Q5:Redis和数据库库存不一致怎么办?
**答**:
1. **正常情况**:使用Lua脚本保证Redis扣减成功后再扣数据库,数据库失败则回滚Redis。
2. **异常情况**:定时任务同步,每隔一段时间以数据库为准重置Redis库存。
3. **兜底方案**:下单时先查数据库库存校验一次。
---
### Q6:如何保证接口幂等性?
**答**:
1. **前端**:生成唯一requestId,防重复点击
2. **后端**:使用Redis的SETNX实现幂等校验
3. **数据库**:唯一索引防止重复数据
```java
String key = "idempotent:" + userId + ":" + requestId;
Boolean success = redis.setIfAbsent(key, "1", 5, TimeUnit.MINUTES);
if (!success) {
throw new BusinessException("请勿重复提交");
}
```
---
### Q7:高并发下如何保证系统稳定?
**答**:
1. **限流**:用户级/接口级限流,防止恶意请求
2. **削峰**:MQ异步处理,缓冲突发流量
3. **缓存**:热点数据缓存,减少数据库压力
4. **降级**:非核心功能降级,保证核心链路
5. **熔断**:服务不可用时快速失败
---
## 八、面试话术模板
### 话术模板1:描述下单流程
> 面试官:说说你们的下单流程?
>
> 我:好的,我们的下单流程分为几个阶段:
>
> **第一阶段是前置校验**:包括参数校验、幂等性校验(防重复提交)、风控校验、限流校验。
>
> **第二阶段是库存处理**:我们采用Redis预扣+数据库乐观锁的方式。先用Lua脚本在Redis中原子性扣减,快速失败;成功后再扣数据库,用乐观锁防止超卖。
>
> **第三阶段是核心下单**:在一个本地事务中完成库存扣减确认、优惠券使用、订单创建、支付单创建。使用分布式锁防止并发问题。
>
> **第四阶段是异步处理**:通过MQ发送消息,异步处理通知、统计更新等。同时发送延迟消息,30分钟后检查未支付的订单自动取消。
### 话术模板2:描述一致性保证
> 面试官:你们如何保证数据一致性?
>
> 我:我们采用本地事务+最终一致性的方案:
>
> **强一致性部分**:核心操作(库存、优惠券、订单)在同一个本地事务中,任何失败全部回滚。
>
> **最终一致性部分**:非核心操作通过MQ异步处理。为了保证消息可靠性,我们用本地消息表,先写消息记录再发MQ,失败了有定时任务重试。
>
> **补偿机制**:订单取消、支付超时等场景,会回滚库存和优惠券。
>
> **兜底方案**:定时任务检查异常数据,发现不一致发告警人工处理。
### 话术模板3:描述技术选型
> 面试官:为什么用Redis?MQ的作用是什么?
>
> 我:**Redis主要用于**:
> 1. 库存预扣,减少数据库压力
> 2. 分布式锁,防止并发问题
> 3. 幂等控制和限流
> 4. 缓存热点数据
>
> **MQ主要用于**:
> 1. 异步解耦,非核心操作不阻塞主流程
> 2. 流量削峰,秒杀场景缓冲请求
> 3. 延迟队列,实现订单超时取消
> 4. 保证最终一致性,跨服务数据同步
---
## 📝 总结速记卡
```
下单流程 = 校验 → 库存 → 下单 → 异步
数据一致性 = 本地事务(强一致)+ MQ(最终一致)
防超卖 = Redis预扣(Lua)+ 数据库乐观锁
防重复 = 幂等Key(Redis SETNX)
超时取消 = MQ延迟消息
消息可靠 = 本地消息表 + 定时重试
高并发 = 限流 + 削峰 + 缓存 + 降级 + 熔断
```
---
**文档版本**:v1.0
**创建时间**:2026-01-19
**适用场景**:Java后端面试、电商系统设计