Administrator
发布于 2026-03-16 / 1 阅读
0
0

企业级电商下单流程面试指南

# 企业级电商下单流程面试指南

> 🎯 面向面试准备 | 结合企业级项目经验  
> 涵盖:下单流程、数据一致性、异常回滚、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后端面试、电商系统设计


评论