---
title: 高并发高性能电商商品商店系统架构设计与深度解析：从核心需求到库存防超卖、分布式事务及缓存优化
created: 2026-03-12
updated: 2026-03-12
type: summary
tags: ["architecture", "ecommerce", "notes", "scalability", "telegram"]
external: http://go/ui/posts/2157/
status: inbox
---

# 设计一个商品商店：系统架构与深度解析

## 一、核心需求分析

### 业务场景
1. **商品浏览**：分类、搜索、推荐
2. **购物流程**：加购、下单、支付
3. **库存管理**：实时库存扣减、防超卖
4. **订单履约**：状态流转、物流集成

### 非功能性需求
- **高并发**：秒杀场景峰值10万QPS
- **高可用**：99.99%可用性
- **数据一致性**：库存与订单最终一致
- **扩展性**：支持多仓库、多渠道

## 二、总体架构设计

### 架构选型：事件驱动 + 微服务

```
┌─────────────────────────────────────────────────────────┐
│                    客户端层                              │
├─────────────┬───────────────┬───────────────────────────┤
│  Web App    │  Mobile App   │   第三方接入              │
└─────────────┴───────────────┴───────────────────────────┘
                            │
                      API Gateway
                      (限流/鉴权/路由)
                            │
┌───────────────────────────────────────────────────────────┐
│                   微服务集群                               │
├──────────┬──────────┬──────────┬──────────┬───────────────┤
│商品服务  │库存服务  │订单服务  │支付服务  │用户服务       │
│          │          │          │          │               │
│- 商品CRUD│- 库存扣减│- 订单创建│- 支付对接│- 用户管理     │
│- 搜索    │- 预扣库存│- 状态机  │- 退款    │- 地址管理     │
│- 推荐    │- 预警    │- 分库分表│- 对账    │- 优惠券       │
└──────────┴──────────┴──────────┴──────────┴───────────────┘
                            │
                   消息队列 (Kafka/RocketMQ)
                   事件：库存扣减、支付成功、物流更新
                            │
┌───────────────────────────────────────────────────────────┐
│                    数据层                                  │
├──────────┬──────────┬──────────┬──────────┬───────────────┤
│ MySQL    │ Redis    │ Elastic  │ 对象存储 │ 时序数据库    │
│ (主数据) │ (缓存/   │ Search   │ (图片)   │ (监控/分析)   │
│          │  分布式锁)│ (商品搜索)│          │               │
└──────────┴──────────┴──────────┴──────────┴───────────────┘
```

## 三、核心数据实体设计

### 1. 商品实体 (product)
```sql
-- 主表
CREATE TABLE product (
    id BIGINT PRIMARY KEY,
    spu_id BIGINT,          -- 标准产品单元
    sku_id BIGINT,          -- 库存单位
    name VARCHAR(200),
    description TEXT,
    category_id INT,
    brand_id INT,
    price DECIMAL(10,2),
    original_price DECIMAL(10,2),
    status TINYINT,         -- 0-下架 1-上架
    created_at TIMESTAMP,
    updated_at TIMESTAMP
);

-- SKU扩展表
CREATE TABLE product_sku (
    id BIGINT PRIMARY KEY,
    product_id BIGINT,
    sku_code VARCHAR(50),
    attributes JSON,        -- {"color":"red","size":"XL"}
    weight DECIMAL(8,2),
    volume DECIMAL(8,2),
    is_default BOOLEAN
);

-- 商品图片
CREATE TABLE product_image (
    id BIGINT PRIMARY KEY,
    product_id BIGINT,
    url VARCHAR(500),
    sort_order INT,
    is_main BOOLEAN
);
```

### 2. 库存实体 (inventory)
```sql
-- 多仓库库存设计（关键！很多人只设计单仓库）
CREATE TABLE inventory (
    id BIGINT PRIMARY KEY,
    sku_id BIGINT,
    warehouse_id INT,       -- 仓库ID
    total_quantity INT,     -- 总库存
    available_quantity INT, -- 可售库存
    locked_quantity INT,    -- 锁定库存（下单未支付）
    version INT,            -- 乐观锁版本号
    updated_at TIMESTAMP,
    UNIQUE KEY (sku_id, warehouse_id)
);

-- 库存流水表（审计用）
CREATE TABLE inventory_log (
    id BIGINT PRIMARY KEY,
    sku_id BIGINT,
    warehouse_id INT,
    change_type TINYINT,    -- 1-入库 2-出库 3-锁定 4-释放
    change_quantity INT,
    before_quantity INT,
    after_quantity INT,
    order_id BIGINT,
    created_at TIMESTAMP
);
```

### 3. 订单实体 (order)
```sql
-- 订单主表
CREATE TABLE orders (
    id BIGINT PRIMARY KEY,
    order_no VARCHAR(32),   -- 业务单号
    user_id BIGINT,
    status TINYINT,         -- 10-待支付 20-已支付 30-发货中 40-已完成
    total_amount DECIMAL(10,2),
    pay_amount DECIMAL(10,2),
    freight_amount DECIMAL(10,2),
    pay_time TIMESTAMP,
    ship_time TIMESTAMP,
    complete_time TIMESTAMP,
    created_at TIMESTAMP,
    updated_at TIMESTAMP,
    INDEX idx_user (user_id),
    INDEX idx_status (status)
) PARTITION BY RANGE (YEAR(created_at));

-- 订单明细
CREATE TABLE order_item (
    id BIGINT PRIMARY KEY,
    order_id BIGINT,
    product_id BIGINT,
    sku_id BIGINT,
    product_name VARCHAR(200),
    sku_attributes JSON,
    price DECIMAL(10,2),
    quantity INT,
    total_price DECIMAL(10,2)
);
```

## 四、核心API设计

### 1. 商品服务API
```yaml
# 商品列表
GET /api/v1/products
Query Parameters:
  category_id: int
  brand_id: int
  price_min: decimal
  price_max: decimal
  keyword: string
  page: int (default:1)
  size: int (default:20)
  sort: string (price_asc, price_desc, sales)

# 库存查询
GET /api/v1/inventory/{sku_id}/warehouses
Response:
  [
    { "warehouse_id": 1, "available": 100 },
    { "warehouse_id": 2, "available": 50 }
  ]
```

### 2. 订单服务API
```yaml
# 创建订单（关键流程）
POST /api/v1/orders
Request Body:
{
  "user_id": 123,
  "items": [
    {
      "sku_id": 456,
      "quantity": 2,
      "warehouse_id": 1
    }
  ],
  "address_id": 789,
  "coupon_id": 101,
  "remark": "尽快发货"
}

# 库存预扣减（防超卖核心）
POST /api/v1/inventory/pre-deduct
Request Body:
{
  "deduct_list": [
    {
      "sku_id": 456,
      "warehouse_id": 1,
      "quantity": 2,
      "expire_seconds": 300  // 5分钟未支付释放
    }
  ]
}
```

## 五、深度深挖：常见错误与难点解析

### 1. 库存防超卖（90%的人设计不完整）

**常见错误方案**：
```sql
-- 错误1：简单扣减
UPDATE inventory SET quantity = quantity - 1 WHERE sku_id = 123;

-- 错误2：使用数据库乐观锁但未处理并发
UPDATE inventory SET quantity = quantity - 1, version = version + 1 
WHERE sku_id = 123 AND version = #{currentVersion};
```

**正确方案：多层防护体系**

```java
// 1. Redis预扣库存（解决瞬时峰值）
String stockKey = "stock:sku:" + skuId;
Long remaining = redisTemplate.opsForValue().decrement(stockKey);
if (remaining < 0) {
    redisTemplate.opsForValue().increment(stockKey); // 回滚
    throw new OutOfStockException();
}

// 2. 数据库最终扣减（保证数据一致）
@DistributedLock(key = "lock:sku:#{skuId}")
public void deductInventory(Long skuId, Integer quantity) {
    // 2.1 检查可用库存
    Inventory inv = inventoryMapper.selectAvailable(skuId);
    if (inv.getAvailableQuantity() < quantity) {
        // 2.2 库存不足，异步同步Redis库存
        asyncStockService.syncRedisStock(skuId);
        throw new OutOfStockException();
    }
    
    // 2.3 使用乐观锁更新
    int affected = inventoryMapper.deductWithVersion(
        skuId, quantity, inv.getVersion()
    );
    
    if (affected == 0) {
        // 并发冲突，重试或失败
        throw new ConcurrentConflictException();
    }
    
    // 2.4 发送库存变更事件
    eventPublisher.publish(new InventoryDeductedEvent(skuId, quantity));
}
```

**关键细节**：
- **二级缓存一致性**：Redis库存与DB库存不一致时的同步策略
- **库存预占**：下单时预占库存，支付后实际扣减，超时释放
- **库存分桶**：按用户ID哈希到不同库存桶，减少热点竞争

### 2. 分布式事务处理（订单与库存一致性）

**常见错误**：
- 只依赖数据库事务，无法跨服务
- 使用2PC导致性能瓶颈

**正确方案：最终一致性 + 补偿事务**

```java
@Service
public class OrderService {
    
    @Transactional
    public Order createOrder(OrderRequest request) {
        // 1. 创建订单（初始状态：待支付）
        Order order = orderRepository.save(new Order(request));
        
        // 2. 发送「库存预扣」事件（非阻塞）
        eventPublisher.publish(new InventoryPreDeductEvent(
            order.getId(), 
            request.getItems()
        ));
        
        return order;
    }
    
    // 监听库存预扣结果
    @EventListener
    public void handleInventoryDeducted(InventoryDeductedEvent event) {
        Order order = orderRepository.findById(event.getOrderId());
        
        if (event.isSuccess()) {
            // 库存扣减成功，订单可支付
            order.setStatus(OrderStatus.PAYABLE);
            orderRepository.save(order);
            
            // 启动支付超时倒计时（30分钟）
            scheduler.schedule(() -> {
                cancelOrderIfUnpaid(order.getId());
            }, 30, TimeUnit.MINUTES);
            
        } else {
            // 库存不足，自动取消订单
            cancelOrder(order.getId(), "库存不足");
        }
    }
    
    // 支付成功处理
    @Transactional
    public void handlePaymentSuccess(PaymentSuccessEvent event) {
        Order order = orderRepository.findById(event.getOrderId());
        order.setStatus(OrderStatus.PAID);
        order.setPayTime(LocalDateTime.now());
        orderRepository.save(order);
        
        // 发送「确认扣减库存」事件（正式扣减）
        eventPublisher.publish(new InventoryConfirmDeductEvent(
            order.getId(), 
            order.getItems()
        ));
    }
}
```

### 3. 高并发搜索与推荐（性能优化陷阱）

**常见问题**：
- 直接查数据库，性能差
- 搜索条件组合爆炸
- 推荐结果不实时

**正确方案：多级缓存 + 异步构建**

```java
// 搜索实现
public class ProductService {
    
    // 第一级：本地缓存（Caffeine）
    @Cacheable(value = "productList", key = "#condition.hash()")
    public Page<Product> searchProducts(SearchCondition condition) {
        // 第二级：Redis缓存（分布式）
        String cacheKey = buildCacheKey(condition);
        Page<Product> cached = redisTemplate.opsForValue().get(cacheKey);
        if (cached != null) {
            return cached;
        }
        
        // 第三级：ES搜索
        SearchRequest request = buildEsRequest(condition);
        SearchResponse response = esClient.search(request);
        Page<Product> result = parseEsResponse(response);
        
        // 异步缓存结果（避免缓存穿透）
        asyncCacheService.cacheSearchResult(cacheKey, result);
        
        return result;
    }
    
    // 库存实时性处理
    private Page<Product> enrichWithInventory(Page<Product> products) {
        List<Long> skuIds = products.stream()
            .map(Product::getSkuId)
            .collect(Collectors.toList());
        
        // 批量查询库存（Redis mget）
        Map<Long, Integer> inventoryMap = inventoryService.batchGet(skuIds);
        
        // 过滤无库存商品
        return products.stream()
            .filter(p -> inventoryMap.getOrDefault(p.getSkuId(), 0) > 0)
            .collect(Collectors.toList());
    }
}
```

### 4. 数据库分库分表策略（订单表设计）

**常见错误**：
- 只按时间分表，查询困难
- 未考虑跨表查询需求
- 分片键选择不当

**正确方案：用户ID分片 + 二级索引表**

```sql
-- 订单主表（按user_id取模分表）
CREATE TABLE orders_0 (
    id BIGINT PRIMARY KEY,
    order_no VARCHAR(32),
    user_id BIGINT,
    -- 其他字段
    INDEX idx_user_time (user_id, created_at)
);

CREATE TABLE orders_1 (...);
-- 共128张表

-- 二级索引表（解决跨用户查询）
CREATE TABLE order_index (
    order_no VARCHAR(32) PRIMARY KEY,
    user_id BIGINT,
    table_suffix INT,      -- orders_?的后缀
    created_at TIMESTAMP
);

-- 按状态查询的物化视图
CREATE MATERIALIZED VIEW order_by_status AS
SELECT 
    status,
    DATE(created_at) as date,
    COUNT(*) as count,
    SUM(total_amount) as amount
FROM orders
GROUP BY status, DATE(created_at);
```

### 5. 缓存设计陷阱（Redis使用不当）

**常见错误**：
- 缓存雪崩：大量key同时过期
- 缓存穿透：查询不存在的数据
- 缓存击穿：热点key过期瞬间

**正确方案：多层次防护**

```java
public class InventoryCacheService {
    
    // 1. 热点key永不过期 + 异步更新
    public Integer getInventory(Long skuId) {
        String key = "stock:" + skuId;
        
        // 1.1 查Redis
        String value = redisTemplate.opsForValue().get(key);
        if (value != null) {
            return Integer.parseInt(value);
        }
        
        // 1.2 缓存穿透防护：布隆过滤器
        if (!bloomFilter.mightContain(skuId)) {
            return 0;
        }
        
        // 1.3 获取分布式锁，防止缓存击穿
        String lockKey = "lock:stock:" + skuId;
        try {
            if (redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 3, TimeUnit.SECONDS)) {
                // 1.4 双重检查
                value = redisTemplate.opsForValue().get(key);
                if (value != null) {
                    return Integer.parseInt(value);
                }
                
                // 1.5 查数据库
                Integer inventory = inventoryMapper.selectAvailable(skuId);
                
                // 1.6 设置随机过期时间，防止雪崩
                int expire = 300 + ThreadLocalRandom.current().nextInt(60);
                redisTemplate.opsForValue().set(key, inventory.toString(), expire, TimeUnit.SECONDS);
                
                return inventory;
            } else {
                // 等待其他线程加载完成
                Thread.sleep(50);
                return getInventory(skuId); // 重试
            }
        } finally {
            redisTemplate.delete(lockKey);
        }
    }
    
    // 2. 库存预扣减的Redis实现
    public boolean preDeductStock(Long skuId, int quantity) {
        String stockKey = "stock:" + skuId;
        String lockKey = "lock:stock:" + skuId;
        
        // 使用Lua脚本保证原子性
        String script = 
            "if redis.call('exists', KEYS[1]) == 0 then " +
            "  return -1 " +
            "end " +
            "local stock = tonumber(redis.call('get', KEYS[1])) " +
            "if stock >= tonumber(ARGV[1]) then " +
            "  return redis.call('decrby', KEYS[1], ARGV[1]) " +
            "else " +
            "  return -2 " +
            "end";
            
        Long result = redisTemplate.execute(
            new DefaultRedisScript<>(script, Long.class),
            Collections.singletonList(stockKey),
            quantity
        );
        
        return result != null && result >= 0;
    }
}
```

## 六、关键设计决策与权衡

### 1. 一致性 vs 可用性
```java
// 选择最终一致性，保证系统可用性
// 通过事件溯源重建状态
public class InventoryEventSourcing {
    
    public Integer getInventory(Long skuId) {
        // 1. 查询当前快照
        InventorySnapshot snapshot = snapshotRepository.findLatest(skuId);
        
        // 2. 应用快照后的事件
        List<InventoryEvent> events = eventRepository
            .findBySkuIdAndVersionGreaterThan(skuId, snapshot.getVersion());
        
        Integer currentStock = snapshot.getQuantity();
        for (InventoryEvent event : events) {
            currentStock = applyEvent(currentStock, event);
        }
        
        return currentStock;
    }
}
```

### 2. 读写分离设计
```
写操作：主库 → Binlog → 消息队列 → 异步构建缓存
读操作：缓存(90%) → 从库(9%) → 主库(1%)  // 读比例分配
```

### 3. 降级与熔断策略
```yaml
# Hystrix配置
inventory-service:
  circuitBreaker:
    enabled: true
    requestVolumeThreshold: 20  # 20个请求后开始计算
    errorThresholdPercentage: 50 # 50%失败率触发熔断
    sleepWindowInMilliseconds: 5000 # 熔断5秒
  fallback:
    # 库存服务降级：返回预设库存
    enabled: true
    defaultStock: 100
```

## 七、面试要点总结

### 高频问题与回答要点

1. **如何设计秒杀系统？**
   - 前端：按钮防重、验证码、静态化
   - 网关：限流（令牌桶）、黑名单
   - 服务层：Redis预扣库存、异步创建订单
   - 数据层：库存分桶、热点隔离

2. **如何保证库存不超卖？**
   - 多层防护：Redis预扣 + 数据库乐观锁 + 分布式锁
   - 库存预占：下单预占，支付后确认，超时释放
   - 实时监控：库存预警、自动补充

3. **订单分库分表如何查询？**
   - 按用户ID分片（保证用户维度查询效率）
   - 建立二级索引表（解决跨用户查询）
   - 异步构建物化视图（统计类查询）

4. **如何处理支付与库存的一致性？**
   - 事件驱动：订单创建 → 库存预扣 → 支付成功 → 库存确认
   - 补偿机制：支付超时自动取消，释放库存
   - 对账系统：定期核对订单与库存状态

5. **缓存与数据库不一致怎么办？**
   - 延迟双删：更新DB → 删除缓存 → 延迟再次删除
   - 订阅Binlog：Canal监听数据库变更，异步更新缓存
   - 版本号控制：数据更新时带版本号，缓存失效

这个设计涵盖了商品商店的核心难点，特别在库存管理、分布式事务、高并发处理等容易出错的地方提供了详细的解决方案。在面试中，不仅要展示设计方案，更要说明设计背后的权衡和取舍。
