HiHuo
首页
博客
手册
工具
关于
首页
博客
手册
工具
关于
  • 系统设计实战

    • 系统设计面试教程
    • 系统设计方法论
    • 01-短链系统设计
    • 02 - 秒杀系统设计
    • 03 - IM 即时通讯系统设计
    • 04 - Feed 流系统设计
    • 05 - 分布式 ID 生成器设计
    • 06 - 限流系统设计
    • 第7章:搜索引擎设计
    • 08 - 推荐系统设计
    • 09 - 支付系统设计
    • 10 - 电商系统设计
    • 11 - 直播系统设计
    • 第12章:缓存系统设计
    • 第13章:消息队列设计
    • 第14章:分布式事务
    • 15 - 监控系统设计

10 - 电商系统设计

> 面试频率: 需求类型指标
用户规模5000 万注册用户,500 万日活
商品规模1000 万 SKU
订单量100 万单/天,大促 1000 万单/天
QPS正常 10 万,大促 50 万
响应时间P99 < 500ms
可用性99.99%
一致性强一致性(库存、订单)

1.3 面试官可能的追问

Q1: 电商系统和其他系统有什么不同?

A1:

  • 复杂度高:涉及多个子系统(商品、订单、库存、支付)
  • 一致性要求:库存不能超卖,订单不能丢失
  • 高并发:大促场景 QPS 极高
  • 业务规则复杂:促销、优惠券、分销等

Q2: 如何防止超卖?

A2:

  • 数据库行锁:SELECT ... FOR UPDATE
  • 乐观锁:version 版本号
  • Redis 原子操作:DECR
  • 预扣库存:下单时预扣,支付后确认

2. 容量估算

2.1 场景假设

  • 日活用户(DAU):500 万
  • 日订单量:100 万
  • 平均客单价:200 元
  • 日 GMV:2 亿元
  • 大促(双十一):10 倍流量

2.2 QPS 估算

正常流量

商品浏览:500 万 × 20 次/天 = 1 亿次
浏览 QPS = 1 亿 / 86400 ≈ 1,157 QPS

下单:100 万 / 86400 ≈ 11.6 QPS
峰值 = 11.6 × 5 ≈ 58 QPS

大促流量

商品浏览 QPS = 1,157 × 10 = 11,570 QPS
下单 QPS = 58 × 100 = 5,800 QPS(前 1 小时)

2.3 存储估算

商品表

SKU 数量 = 1000 万
单条记录 = 2 KB
总存储 = 1000 万 × 2 KB = 20 GB

订单表

日订单 = 100 万
单条记录 = 1 KB
日存储 = 100 万 × 1 KB = 1 GB
年存储 = 1 GB × 365 = 365 GB

保留 3 年 = 365 GB × 3 ≈ 1.1 TB

订单商品表(订单详情)

平均每单 3 个商品
日订单商品 = 100 万 × 3 = 300 万
单条记录 = 500 字节
日存储 = 300 万 × 500 字节 = 1.5 GB
年存储 = 1.5 GB × 365 ≈ 548 GB
3 年 ≈ 1.6 TB

总存储需求:约 3 TB(3 年数据)

2.4 数据库估算

分库分表

订单表按用户 ID 分片:
- 分 256 个表
- 每表 = 100 万 × 365 × 3 / 256 ≈ 427 万条

分 32 个数据库:
- 每库 8 个表

3. API 设计

3.1 核心 API

3.1.1 商品列表

GET /api/v1/products

Request:
{
  "category_id": 101,
  "keyword": "手机",
  "page": 1,
  "page_size": 20,
  "sort": "sales",                  // sales | price_asc | price_desc
  "filters": {
    "price_min": 1000,
    "price_max": 5000,
    "brand_ids": [1, 2, 3]
  }
}

Response:
{
  "code": 0,
  "message": "success",
  "data": {
    "total": 1523,
    "items": [
      {
        "product_id": 10001,
        "spu_id": 1001,
        "sku_id": 100101,
        "title": "iPhone 15 Pro 256GB 黑色",
        "price": 7999.00,
        "original_price": 8999.00,
        "stock": 1000,
        "sales": 5234,
        "image_url": "https://cdn.example.com/product.jpg",
        "tags": ["热销", "新品"]
      }
    ]
  }
}

3.1.2 添加购物车

POST /api/v1/cart/items

Request:
{
  "sku_id": 100101,
  "quantity": 1
}

Response:
{
  "code": 0,
  "message": "success",
  "data": {
    "cart_item_id": 123456,
    "total_count": 3,
    "total_amount": 15998.00
  }
}

3.1.3 创建订单

POST /api/v1/orders

Request:
{
  "address_id": 5001,
  "cart_item_ids": [123456, 123457],
  "coupon_id": 2001,
  "remark": "尽快发货",
  "payment_method": "wechat"
}

Response:
{
  "code": 0,
  "message": "success",
  "data": {
    "order_id": "ORD20231113001",
    "order_sn": "202311130001234567",
    "total_amount": 7999.00,
    "discount_amount": 100.00,
    "final_amount": 7899.00,
    "status": "unpaid",
    "payment_url": "weixin://wxpay/...",
    "expires_at": "2023-11-13T15:00:00Z"
  }
}

3.1.4 支付订单

POST /api/v1/orders/{order_id}/pay

Request:
{
  "payment_method": "wechat",
  "payment_password": "******"
}

Response:
{
  "code": 0,
  "message": "success",
  "data": {
    "payment_order_id": "PAY20231113001",
    "status": "paying",
    "qr_code": "weixin://wxpay/..."
  }
}

3.1.5 查询订单

GET /api/v1/orders/{order_id}

Response:
{
  "code": 0,
  "message": "success",
  "data": {
    "order_id": "ORD20231113001",
    "order_sn": "202311130001234567",
    "status": "paid",
    "user_id": 12345,
    "total_amount": 7999.00,
    "final_amount": 7899.00,
    "created_at": "2023-11-13T14:30:00Z",
    "paid_at": "2023-11-13T14:35:00Z",
    "items": [
      {
        "sku_id": 100101,
        "title": "iPhone 15 Pro",
        "price": 7999.00,
        "quantity": 1,
        "image_url": "https://cdn.example.com/product.jpg"
      }
    ],
    "address": {
      "receiver": "张三",
      "phone": "13800138000",
      "address": "北京市朝阳区..."
    },
    "logistics": {
      "company": "顺丰速运",
      "tracking_number": "SF1234567890",
      "status": "运输中"
    }
  }
}

4. 数据模型设计

4.1 商品表(SPU/SKU)

-- SPU(Standard Product Unit):标准化产品单元
CREATE TABLE products_spu (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    spu_id BIGINT NOT NULL UNIQUE,
    
    title VARCHAR(256) NOT NULL,
    category_id INT NOT NULL,
    brand_id INT,
    
    description TEXT,
    detail_html TEXT,
    
    status VARCHAR(20) DEFAULT 'active' COMMENT 'active|inactive',
    
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    
    INDEX idx_category (category_id),
    INDEX idx_brand (brand_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品SPU表';

-- SKU(Stock Keeping Unit):库存单位
CREATE TABLE products_sku (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    sku_id BIGINT NOT NULL UNIQUE,
    spu_id BIGINT NOT NULL,
    
    title VARCHAR(256) NOT NULL,
    
    -- 规格(JSON)
    specs JSON COMMENT '{"颜色": "黑色", "内存": "256GB"}',
    
    -- 价格
    price DECIMAL(10, 2) NOT NULL,
    original_price DECIMAL(10, 2),
    
    -- 库存
    stock INT DEFAULT 0,
    
    -- 销量
    sales INT DEFAULT 0,
    
    -- 图片
    image_url VARCHAR(512),
    
    status VARCHAR(20) DEFAULT 'active',
    
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    
    INDEX idx_spu (spu_id),
    INDEX idx_price (price),
    INDEX idx_sales (sales)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品SKU表';

4.2 购物车表

CREATE TABLE shopping_cart (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT NOT NULL,
    sku_id BIGINT NOT NULL,
    
    quantity INT NOT NULL DEFAULT 1,
    
    -- 是否选中
    selected BOOLEAN DEFAULT TRUE,
    
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    
    UNIQUE KEY uk_user_sku (user_id, sku_id),
    INDEX idx_user (user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='购物车表';

4.3 订单表

CREATE TABLE orders (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    order_id VARCHAR(64) NOT NULL UNIQUE,
    order_sn VARCHAR(64) NOT NULL UNIQUE COMMENT '订单号(对外显示)',
    
    user_id BIGINT NOT NULL,
    
    -- 订单状态
    status VARCHAR(20) NOT NULL COMMENT 'unpaid|paid|shipped|received|completed|cancelled',
    
    -- 金额
    total_amount DECIMAL(10, 2) NOT NULL COMMENT '商品总金额',
    discount_amount DECIMAL(10, 2) DEFAULT 0 COMMENT '优惠金额',
    shipping_amount DECIMAL(10, 2) DEFAULT 0 COMMENT '运费',
    final_amount DECIMAL(10, 2) NOT NULL COMMENT '实付金额',
    
    -- 收货地址
    address_id BIGINT,
    receiver_name VARCHAR(50),
    receiver_phone VARCHAR(20),
    receiver_address VARCHAR(512),
    
    -- 优惠券
    coupon_id BIGINT,
    
    -- 备注
    remark TEXT,
    
    -- 支付信息
    payment_method VARCHAR(20) COMMENT 'wechat|alipay|balance',
    payment_order_id VARCHAR(64) COMMENT '支付订单号',
    paid_at DATETIME,
    
    -- 物流信息
    logistics_company VARCHAR(50),
    logistics_number VARCHAR(100),
    shipped_at DATETIME,
    
    -- 完成时间
    received_at DATETIME,
    completed_at DATETIME,
    
    -- 取消
    cancelled_at DATETIME,
    cancel_reason TEXT,
    
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    
    INDEX idx_user_status (user_id, status),
    INDEX idx_status_created (status, created_at),
    INDEX idx_order_sn (order_sn)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表';

4.4 订单商品表

CREATE TABLE order_items (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    order_id VARCHAR(64) NOT NULL,
    
    sku_id BIGINT NOT NULL,
    spu_id BIGINT NOT NULL,
    
    title VARCHAR(256) NOT NULL,
    image_url VARCHAR(512),
    specs JSON,
    
    price DECIMAL(10, 2) NOT NULL COMMENT '下单时价格',
    quantity INT NOT NULL,
    total_amount DECIMAL(10, 2) NOT NULL,
    
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    
    INDEX idx_order (order_id),
    INDEX idx_sku (sku_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单商品表';

4.5 库存表

CREATE TABLE inventory (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    sku_id BIGINT NOT NULL UNIQUE,
    
    -- 可用库存
    available_stock INT NOT NULL DEFAULT 0,
    
    -- 预占库存(已下单未支付)
    locked_stock INT NOT NULL DEFAULT 0,
    
    -- 总库存
    total_stock INT NOT NULL DEFAULT 0,
    
    -- 版本号(乐观锁)
    version INT NOT NULL DEFAULT 0,
    
    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    
    INDEX idx_sku (sku_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='库存表';

4.6 库存流水表

CREATE TABLE inventory_logs (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    log_id VARCHAR(64) NOT NULL UNIQUE,
    
    sku_id BIGINT NOT NULL,
    
    -- 操作类型
    operation_type VARCHAR(20) NOT NULL COMMENT 'lock|unlock|deduct|return',
    
    -- 数量
    quantity INT NOT NULL,
    
    -- 前后库存
    before_stock INT NOT NULL,
    after_stock INT NOT NULL,
    
    -- 业务信息
    business_type VARCHAR(50) COMMENT 'order|refund',
    business_id VARCHAR(64),
    
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    
    INDEX idx_sku (sku_id),
    INDEX idx_business (business_type, business_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='库存流水表';

5. 架构设计

5.1 整体架构

┌─────────────────────────────────────────────────────────────┐
│                          用户端                              │
│                    (Web / App / 小程序)                      │
└─────────────────────┬───────────────────────────────────────┘
                      │
                      
┌─────────────────────────────────────────────────────────────┐
│                      API Gateway                            │
│              (限流、鉴权、路由、熔断)                          │
└─────────────┬───────────────────────────────────────────────┘
              │
              
┌─────────────────────────────────────────────────────────────┐
│                    业务服务层                                │
│                                                             │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐  │
│  │ 商品服务 │  │ 购物车   │  │ 订单服务 │  │ 库存服务 │  │
│  └──────────┘  └──────────┘  └──────────┘  └──────────┘  │
│                                                             │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐                │
│  │ 促销服务 │  │ 支付服务 │  │ 物流服务 │                │
│  └──────────┘  └──────────┘  └──────────┘                │
└─────────────────────────────────────────────────────────────┘
              │
              
┌─────────────────────────────────────────────────────────────┐
│                    数据层                                    │
│                                                             │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐       │
│  │   MySQL     │  │    Redis    │  │  Kafka/MQ   │       │
│  │ (分库分表)   │  │  (缓存)     │  │ (消息队列)   │       │
│  └─────────────┘  └─────────────┘  └─────────────┘       │
└─────────────────────────────────────────────────────────────┘

5.2 V1: 单体架构(MVP)

package main

import (
    "context"
    "database/sql"
    "encoding/json"
    "fmt"
    "net/http"
    "time"
    
    "github.com/google/uuid"
    "github.com/gorilla/mux"
)

// ==================== 数据结构 ====================

// Product 商品
type Product struct {
    SkuID         int64   `json:"sku_id"`
    Title         string  `json:"title"`
    Price         float64 `json:"price"`
    Stock         int     `json:"stock"`
    Sales         int     `json:"sales"`
    ImageURL      string  `json:"image_url"`
}

// CartItem 购物车项
type CartItem struct {
    ID       int64   `json:"id"`
    UserID   int64   `json:"user_id"`
    SkuID    int64   `json:"sku_id"`
    Quantity int     `json:"quantity"`
    Selected bool    `json:"selected"`
}

// Order 订单
type Order struct {
    OrderID        string    `json:"order_id"`
    OrderSn        string    `json:"order_sn"`
    UserID         int64     `json:"user_id"`
    Status         string    `json:"status"`
    TotalAmount    float64   `json:"total_amount"`
    DiscountAmount float64   `json:"discount_amount"`
    FinalAmount    float64   `json:"final_amount"`
    CreatedAt      time.Time `json:"created_at"`
}

// OrderItem 订单商品
type OrderItem struct {
    OrderID     string  `json:"order_id"`
    SkuID       int64   `json:"sku_id"`
    Title       string  `json:"title"`
    Price       float64 `json:"price"`
    Quantity    int     `json:"quantity"`
    TotalAmount float64 `json:"total_amount"`
}

// ==================== 商品服务 ====================

type ProductService struct {
    db *sql.DB
}

// GetProducts 获取商品列表
func (s *ProductService) GetProducts(categoryID int, page, pageSize int) ([]*Product, error) {
    offset := (page - 1) * pageSize
    
    rows, err := s.db.Query(`
        SELECT sku_id, title, price, stock, sales, image_url
        FROM products_sku
        WHERE status = 'active'
        ORDER BY sales DESC
        LIMIT ? OFFSET ?
    `, pageSize, offset)
    
    if err != nil {
        return nil, err
    }
    defer rows.Close()
    
    products := make([]*Product, 0)
    for rows.Next() {
        var p Product
        rows.Scan(&p.SkuID, &p.Title, &p.Price, &p.Stock, &p.Sales, &p.ImageURL)
        products = append(products, &p)
    }
    
    return products, nil
}

// GetProduct 获取商品详情
func (s *ProductService) GetProduct(skuID int64) (*Product, error) {
    var p Product
    err := s.db.QueryRow(`
        SELECT sku_id, title, price, stock, sales, image_url
        FROM products_sku
        WHERE sku_id = ? AND status = 'active'
    `, skuID).Scan(&p.SkuID, &p.Title, &p.Price, &p.Stock, &p.Sales, &p.ImageURL)
    
    return &p, err
}

// ==================== 购物车服务 ====================

type CartService struct {
    db *sql.DB
}

// AddToCart 添加到购物车
func (s *CartService) AddToCart(userID, skuID int64, quantity int) error {
    // 检查商品是否存在
    var stock int
    err := s.db.QueryRow(`SELECT stock FROM products_sku WHERE sku_id = ?`, skuID).Scan(&stock)
    if err != nil {
        return fmt.Errorf("商品不存在")
    }
    
    if stock < quantity {
        return fmt.Errorf("库存不足")
    }
    
    // 插入或更新购物车
    _, err = s.db.Exec(`
        INSERT INTO shopping_cart (user_id, sku_id, quantity)
        VALUES (?, ?, ?)
        ON DUPLICATE KEY UPDATE quantity = quantity + ?
    `, userID, skuID, quantity, quantity)
    
    return err
}

// GetCartItems 获取购物车列表
func (s *CartService) GetCartItems(userID int64) ([]*CartItem, error) {
    rows, err := s.db.Query(`
        SELECT id, user_id, sku_id, quantity, selected
        FROM shopping_cart
        WHERE user_id = ?
    `, userID)
    
    if err != nil {
        return nil, err
    }
    defer rows.Close()
    
    items := make([]*CartItem, 0)
    for rows.Next() {
        var item CartItem
        rows.Scan(&item.ID, &item.UserID, &item.SkuID, &item.Quantity, &item.Selected)
        items = append(items, &item)
    }
    
    return items, nil
}

// ==================== 订单服务 ====================

type OrderService struct {
    db             *sql.DB
    inventoryService *InventoryService
}

// CreateOrder 创建订单
func (s *OrderService) CreateOrder(userID int64, cartItemIDs []int64, addressID int64) (*Order, error) {
    // 1. 开启事务
    tx, err := s.db.Begin()
    if err != nil {
        return nil, err
    }
    defer tx.Rollback()
    
    // 2. 查询购物车商品
    items, err := s.getCartItemsByIDs(tx, cartItemIDs)
    if err != nil {
        return nil, err
    }
    
    // 3. 检查并锁定库存
    for _, item := range items {
        if err := s.inventoryService.LockStock(tx, item.SkuID, item.Quantity); err != nil {
            return nil, fmt.Errorf("库存不足: %w", err)
        }
    }
    
    // 4. 计算金额
    totalAmount := 0.0
    for _, item := range items {
        var price float64
        tx.QueryRow(`SELECT price FROM products_sku WHERE sku_id = ?`, item.SkuID).Scan(&price)
        totalAmount += price * float64(item.Quantity)
    }
    
    // 5. 创建订单
    orderID := generateOrderID()
    orderSn := generateOrderSn()
    
    _, err = tx.Exec(`
        INSERT INTO orders (
            order_id, order_sn, user_id, status, 
            total_amount, final_amount, address_id
        ) VALUES (?, ?, ?, 'unpaid', ?, ?, ?)
    `, orderID, orderSn, userID, totalAmount, totalAmount, addressID)
    
    if err != nil {
        return nil, err
    }
    
    // 6. 创建订单商品
    for _, item := range items {
        var title string
        var price float64
        tx.QueryRow(`SELECT title, price FROM products_sku WHERE sku_id = ?`, item.SkuID).
            Scan(&title, &price)
        
        _, err = tx.Exec(`
            INSERT INTO order_items (
                order_id, sku_id, title, price, quantity, total_amount
            ) VALUES (?, ?, ?, ?, ?, ?)
        `, orderID, item.SkuID, title, price, item.Quantity, price*float64(item.Quantity))
        
        if err != nil {
            return nil, err
        }
    }
    
    // 7. 删除购物车
    _, err = tx.Exec(`DELETE FROM shopping_cart WHERE id IN (?)`, cartItemIDs)
    if err != nil {
        return nil, err
    }
    
    // 8. 提交事务
    if err = tx.Commit(); err != nil {
        return nil, err
    }
    
    // 9. 设置订单超时(15分钟未支付自动取消)
    go s.scheduleOrderTimeout(orderID, 15*time.Minute)
    
    order := &Order{
        OrderID:     orderID,
        OrderSn:     orderSn,
        UserID:      userID,
        Status:      "unpaid",
        TotalAmount: totalAmount,
        FinalAmount: totalAmount,
        CreatedAt:   time.Now(),
    }
    
    return order, nil
}

// PayOrder 支付订单
func (s *OrderService) PayOrder(orderID string, paymentMethod string) error {
    // 1. 开启事务
    tx, err := s.db.Begin()
    if err != nil {
        return err
    }
    defer tx.Rollback()
    
    // 2. 查询订单
    var status string
    err = tx.QueryRow(`SELECT status FROM orders WHERE order_id = ? FOR UPDATE`, orderID).Scan(&status)
    if err != nil {
        return fmt.Errorf("订单不存在")
    }
    
    if status != "unpaid" {
        return fmt.Errorf("订单状态异常: %s", status)
    }
    
    // 3. 扣减库存(从预占转为实际扣减)
    items, err := s.getOrderItems(tx, orderID)
    if err != nil {
        return err
    }
    
    for _, item := range items {
        if err := s.inventoryService.DeductStock(tx, item.SkuID, item.Quantity); err != nil {
            return err
        }
    }
    
    // 4. 更新订单状态
    _, err = tx.Exec(`
        UPDATE orders 
        SET status = 'paid', paid_at = NOW()
        WHERE order_id = ?
    `, orderID)
    
    if err != nil {
        return err
    }
    
    // 5. 提交事务
    return tx.Commit()
}

// CancelOrder 取消订单
func (s *OrderService) CancelOrder(orderID string, reason string) error {
    tx, err := s.db.Begin()
    if err != nil {
        return err
    }
    defer tx.Rollback()
    
    // 1. 查询订单
    var status string
    tx.QueryRow(`SELECT status FROM orders WHERE order_id = ?`, orderID).Scan(&status)
    
    if status == "paid" || status == "shipped" {
        return fmt.Errorf("订单不能取消")
    }
    
    // 2. 释放库存
    items, _ := s.getOrderItems(tx, orderID)
    for _, item := range items {
        s.inventoryService.UnlockStock(tx, item.SkuID, item.Quantity)
    }
    
    // 3. 更新订单状态
    _, err = tx.Exec(`
        UPDATE orders 
        SET status = 'cancelled', cancelled_at = NOW(), cancel_reason = ?
        WHERE order_id = ?
    `, reason, orderID)
    
    if err != nil {
        return err
    }
    
    return tx.Commit()
}

// scheduleOrderTimeout 订单超时处理
func (s *OrderService) scheduleOrderTimeout(orderID string, timeout time.Duration) {
    time.Sleep(timeout)
    
    // 查询订单状态
    var status string
    s.db.QueryRow(`SELECT status FROM orders WHERE order_id = ?`, orderID).Scan(&status)
    
    if status == "unpaid" {
        s.CancelOrder(orderID, "超时未支付")
    }
}

func (s *OrderService) getCartItemsByIDs(tx *sql.Tx, ids []int64) ([]*CartItem, error) {
    // 简化实现
    return nil, nil
}

func (s *OrderService) getOrderItems(tx *sql.Tx, orderID string) ([]*OrderItem, error) {
    rows, err := tx.Query(`
        SELECT order_id, sku_id, title, price, quantity, total_amount
        FROM order_items
        WHERE order_id = ?
    `, orderID)
    
    if err != nil {
        return nil, err
    }
    defer rows.Close()
    
    items := make([]*OrderItem, 0)
    for rows.Next() {
        var item OrderItem
        rows.Scan(&item.OrderID, &item.SkuID, &item.Title, &item.Price, &item.Quantity, &item.TotalAmount)
        items = append(items, &item)
    }
    
    return items, nil
}

// ==================== 库存服务 ====================

type InventoryService struct {
    db *sql.DB
}

// LockStock 锁定库存(预占)
func (s *InventoryService) LockStock(tx *sql.Tx, skuID int64, quantity int) error {
    // 使用乐观锁
    result, err := tx.Exec(`
        UPDATE inventory 
        SET available_stock = available_stock - ?,
            locked_stock = locked_stock + ?,
            version = version + 1
        WHERE sku_id = ? 
          AND available_stock >= ?
          AND version = (SELECT version FROM (SELECT version FROM inventory WHERE sku_id = ?) AS tmp)
    `, quantity, quantity, skuID, quantity, skuID)
    
    if err != nil {
        return err
    }
    
    rowsAffected, _ := result.RowsAffected()
    if rowsAffected == 0 {
        return fmt.Errorf("库存不足或版本冲突")
    }
    
    // 记录流水
    s.logInventory(tx, skuID, "lock", quantity)
    
    return nil
}

// DeductStock 扣减库存(支付后)
func (s *InventoryService) DeductStock(tx *sql.Tx, skuID int64, quantity int) error {
    _, err := tx.Exec(`
        UPDATE inventory 
        SET locked_stock = locked_stock - ?,
            total_stock = total_stock - ?
        WHERE sku_id = ?
    `, quantity, quantity, skuID)
    
    if err != nil {
        return err
    }
    
    // 记录流水
    s.logInventory(tx, skuID, "deduct", quantity)
    
    return nil
}

// UnlockStock 解锁库存(取消订单)
func (s *InventoryService) UnlockStock(tx *sql.Tx, skuID int64, quantity int) error {
    _, err := tx.Exec(`
        UPDATE inventory 
        SET available_stock = available_stock + ?,
            locked_stock = locked_stock - ?
        WHERE sku_id = ?
    `, quantity, quantity, skuID)
    
    if err != nil {
        return err
    }
    
    // 记录流水
    s.logInventory(tx, skuID, "unlock", quantity)
    
    return nil
}

func (s *InventoryService) logInventory(tx *sql.Tx, skuID int64, opType string, quantity int) {
    logID := uuid.New().String()
    tx.Exec(`
        INSERT INTO inventory_logs (log_id, sku_id, operation_type, quantity, created_at)
        VALUES (?, ?, ?, ?, NOW())
    `, logID, skuID, opType, quantity)
}

// ==================== 辅助函数 ====================

func generateOrderID() string {
    return "ORD" + time.Now().Format("20060102") + uuid.New().String()[:8]
}

func generateOrderSn() string {
    return time.Now().Format("20060102150405") + fmt.Sprintf("%06d", time.Now().Nanosecond()%1000000)
}

// ==================== HTTP Handlers ====================

type EcommerceHandler struct {
    productService   *ProductService
    cartService      *CartService
    orderService     *OrderService
    inventoryService *InventoryService
}

func (h *EcommerceHandler) GetProductsHandler(w http.ResponseWriter, r *http.Request) {
    products, err := h.productService.GetProducts(0, 1, 20)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    
    json.NewEncoder(w).Encode(map[string]interface{}{
        "code":    0,
        "message": "success",
        "data":    products,
    })
}

func (h *EcommerceHandler) AddToCartHandler(w http.ResponseWriter, r *http.Request) {
    var req struct {
        SkuID    int64 `json:"sku_id"`
        Quantity int   `json:"quantity"`
    }
    
    json.NewDecoder(r.Body).Decode(&req)
    
    userID := int64(12345) // 从 token 获取
    
    err := h.cartService.AddToCart(userID, req.SkuID, req.Quantity)
    if err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    
    json.NewEncoder(w).Encode(map[string]interface{}{
        "code":    0,
        "message": "success",
    })
}

func (h *EcommerceHandler) CreateOrderHandler(w http.ResponseWriter, r *http.Request) {
    var req struct {
        CartItemIDs []int64 `json:"cart_item_ids"`
        AddressID   int64   `json:"address_id"`
    }
    
    json.NewDecoder(r.Body).Decode(&req)
    
    userID := int64(12345) // 从 token 获取
    
    order, err := h.orderService.CreateOrder(userID, req.CartItemIDs, req.AddressID)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    
    json.NewEncoder(w).Encode(map[string]interface{}{
        "code":    0,
        "message": "success",
        "data":    order,
    })
}

func main() {
    db, _ := sql.Open("mysql", "root:password@tcp(localhost:3306)/ecommerce")
    defer db.Close()
    
    productService := &ProductService{db: db}
    cartService := &CartService{db: db}
    inventoryService := &InventoryService{db: db}
    orderService := &OrderService{
        db:               db,
        inventoryService: inventoryService,
    }
    
    handler := &EcommerceHandler{
        productService:   productService,
        cartService:      cartService,
        orderService:     orderService,
        inventoryService: inventoryService,
    }
    
    r := mux.NewRouter()
    r.HandleFunc("/api/v1/products", handler.GetProductsHandler).Methods("GET")
    r.HandleFunc("/api/v1/cart/items", handler.AddToCartHandler).Methods("POST")
    r.HandleFunc("/api/v1/orders", handler.CreateOrderHandler).Methods("POST")
    
    fmt.Println("电商系统启动在 :8080")
    http.ListenAndServe(":8080", r)
}

V1 特点:

  • 简单易实现
  • 数据库事务保证一致性
  • 乐观锁防止超卖
  • 单体应用,无法水平扩展
  • 库存高并发性能差

5.3 V2: 微服务 + 分布式事务

优化点:

  1. 服务拆分(商品、订单、库存独立)
  2. TCC 分布式事务
  3. Redis 库存缓存
  4. 消息队列异步处理
// ==================== Redis 库存缓存 ====================

package cache

import (
    "context"
    "fmt"
    "strconv"
    
    "github.com/go-redis/redis/v8"
)

// InventoryCacheService 库存缓存服务
type InventoryCacheService struct {
    redis *redis.Client
}

// DecrStock 扣减库存(原子操作)
func (s *InventoryCacheService) DecrStock(skuID int64, quantity int) error {
    ctx := context.Background()
    key := fmt.Sprintf("inventory:sku:%d:stock", skuID)
    
    // Lua 脚本保证原子性
    script := redis.NewScript(`
        local stock = redis.call('GET', KEYS[1])
        if not stock then
            return -1
        end
        
        stock = tonumber(stock)
        local quantity = tonumber(ARGV[1])
        
        if stock < quantity then
            return -2
        end
        
        redis.call('DECRBY', KEYS[1], quantity)
        return stock - quantity
    `)
    
    result, err := script.Run(ctx, s.redis, []string{key}, quantity).Result()
    if err != nil {
        return err
    }
    
    remaining := result.(int64)
    if remaining == -1 {
        return fmt.Errorf("库存不存在")
    } else if remaining == -2 {
        return fmt.Errorf("库存不足")
    }
    
    return nil
}

// SyncToRedis 同步库存到 Redis
func (s *InventoryCacheService) SyncToRedis(skuID int64, stock int) error {
    ctx := context.Background()
    key := fmt.Sprintf("inventory:sku:%d:stock", skuID)
    
    return s.redis.Set(ctx, key, stock, 0).Err()
}

// ==================== 分布式锁 ====================

type DistributedLock struct {
    redis *redis.Client
}

// Lock 获取锁
func (l *DistributedLock) Lock(key string, expiration time.Duration) bool {
    ctx := context.Background()
    result, err := l.redis.SetNX(ctx, key, "1", expiration).Result()
    return err == nil && result
}

// Unlock 释放锁
func (l *DistributedLock) Unlock(key string) {
    ctx := context.Background()
    l.redis.Del(ctx, key)
}

// ==================== 订单创建流程(TCC)====================

type OrderTCCService struct {
    orderDB          *sql.DB
    inventoryService *InventoryCacheService
    paymentService   *PaymentService
}

// CreateOrderWithTCC 使用 TCC 创建订单
func (s *OrderTCCService) CreateOrderWithTCC(
    userID int64,
    items []*OrderItem,
    addressID int64,
) (*Order, error) {
    txID := generateTxID()
    
    // Try 阶段
    if err := s.tryCreateOrder(txID, userID, items, addressID); err != nil {
        s.cancelCreateOrder(txID)
        return nil, err
    }
    
    // Confirm 阶段
    if err := s.confirmCreateOrder(txID); err != nil {
        s.cancelCreateOrder(txID)
        return nil, err
    }
    
    return nil, nil
}

// tryCreateOrder Try 阶段:预占资源
func (s *OrderTCCService) tryCreateOrder(
    txID string,
    userID int64,
    items []*OrderItem,
    addressID int64,
) error {
    // 1. 检查库存并预占
    for _, item := range items {
        if err := s.inventoryService.DecrStock(item.SkuID, item.Quantity); err != nil {
            return err
        }
    }
    
    // 2. 创建预订单
    orderID := generateOrderID()
    _, err := s.orderDB.Exec(`
        INSERT INTO orders_temp (order_id, tx_id, user_id, status)
        VALUES (?, ?, ?, 'trying')
    `, orderID, txID, userID)
    
    return err
}

// confirmCreateOrder Confirm 阶段:确认提交
func (s *OrderTCCService) confirmCreateOrder(txID string) error {
    // 1. 将临时订单转为正式订单
    _, err := s.orderDB.Exec(`
        INSERT INTO orders (order_id, user_id, status)
        SELECT order_id, user_id, 'unpaid'
        FROM orders_temp
        WHERE tx_id = ? AND status = 'trying'
    `, txID)
    
    if err != nil {
        return err
    }
    
    // 2. 删除临时订单
    s.orderDB.Exec(`DELETE FROM orders_temp WHERE tx_id = ?`, txID)
    
    return nil
}

// cancelCreateOrder Cancel 阶段:回滚
func (s *OrderTCCService) cancelCreateOrder(txID string) error {
    // 1. 查询临时订单
    rows, err := s.orderDB.Query(`
        SELECT sku_id, quantity FROM order_items_temp WHERE tx_id = ?
    `, txID)
    
    if err != nil {
        return err
    }
    defer rows.Close()
    
    // 2. 释放库存
    for rows.Next() {
        var skuID int64
        var quantity int
        rows.Scan(&skuID, &quantity)
        
        // 库存回滚
        s.inventoryService.redis.IncrBy(
            context.Background(),
            fmt.Sprintf("inventory:sku:%d:stock", skuID),
            int64(quantity),
        )
    }
    
    // 3. 删除临时订单
    s.orderDB.Exec(`DELETE FROM orders_temp WHERE tx_id = ?`, txID)
    s.orderDB.Exec(`DELETE FROM order_items_temp WHERE tx_id = ?`, txID)
    
    return nil
}

func generateTxID() string {
    return "TCC_" + uuid.New().String()
}

type PaymentService struct{}

V2 特点:

  • 微服务架构,服务独立部署
  • Redis 缓存库存,性能提升 10 倍
  • TCC 分布式事务保证一致性
  • 支持水平扩展
  • ⚠️ TCC 实现复杂
  • ⚠️ 需要补偿机制

5.4 V3: 最终一致性 + 消息队列

优化点:

  1. 本地消息表 + 定时任务
  2. Kafka 异步处理
  3. 数据库分库分表
  4. Elasticsearch 商品搜索
// ==================== 本地消息表方案 ====================

package eventual

import (
    "encoding/json"
)

// OrderEventPublisher 订单事件发布器
type OrderEventPublisher struct {
    db    *sql.DB
    kafka *KafkaProducer
}

// PublishOrderCreatedEvent 发布订单创建事件
func (p *OrderEventPublisher) PublishOrderCreatedEvent(order *Order) error {
    // 1. 在事务中插入订单和本地消息
    tx, err := p.db.Begin()
    if err != nil {
        return err
    }
    defer tx.Rollback()
    
    // 插入订单
    _, err = tx.Exec(`INSERT INTO orders ...`, order)
    if err != nil {
        return err
    }
    
    // 插入本地消息
    messageID := uuid.New().String()
    messageBody, _ := json.Marshal(map[string]interface{}{
        "event_type": "order_created",
        "order_id":   order.OrderID,
        "user_id":    order.UserID,
        "amount":     order.FinalAmount,
    })
    
    _, err = tx.Exec(`
        INSERT INTO local_messages (message_id, topic, body, status)
        VALUES (?, 'order-events', ?, 'pending')
    `, messageID, string(messageBody))
    
    if err != nil {
        return err
    }
    
    // 2. 提交事务
    if err = tx.Commit(); err != nil {
        return err
    }
    
    // 3. 异步发送消息到 Kafka(定时任务处理)
    
    return nil
}

// MessageRelayJob 消息中继任务
func (p *OrderEventPublisher) MessageRelayJob() {
    ticker := time.NewTicker(1 * time.Second)
    defer ticker.Stop()
    
    for range ticker.C {
        p.sendPendingMessages()
    }
}

func (p *OrderEventPublisher) sendPendingMessages() {
    rows, _ := p.db.Query(`
        SELECT message_id, topic, body
        FROM local_messages
        WHERE status = 'pending'
        LIMIT 100
        FOR UPDATE SKIP LOCKED
    `)
    defer rows.Close()
    
    for rows.Next() {
        var messageID, topic, body string
        rows.Scan(&messageID, &topic, &body)
        
        // 发送到 Kafka
        if err := p.kafka.Send(topic, body); err != nil {
            // 发送失败,更新重试次数
            continue
        }
        
        // 标记为已发送
        p.db.Exec(`
            UPDATE local_messages SET status = 'sent' WHERE message_id = ?
        `, messageID)
    }
}

type KafkaProducer struct{}

func (k *KafkaProducer) Send(topic, body string) error {
    return nil
}

// ==================== 商品搜索(Elasticsearch)====================

package search

import (
    "context"
    "encoding/json"
    
    "github.com/elastic/go-elasticsearch/v8"
)

type ProductSearchService struct {
    es *elasticsearch.Client
}

// SearchProducts 搜索商品
func (s *ProductSearchService) SearchProducts(keyword string, page, pageSize int) ([]*Product, error) {
    ctx := context.Background()
    
    // 构建搜索查询
    query := map[string]interface{}{
        "query": map[string]interface{}{
            "multi_match": map[string]interface{}{
                "query":  keyword,
                "fields": []string{"title^3", "description"},
            },
        },
        "from": (page - 1) * pageSize,
        "size": pageSize,
        "sort": []map[string]interface{}{
            {"sales": "desc"},
            {"_score": "desc"},
        },
    }
    
    queryJSON, _ := json.Marshal(query)
    
    // 执行搜索
    res, err := s.es.Search(
        s.es.Search.WithContext(ctx),
        s.es.Search.WithIndex("products"),
        s.es.Search.WithBody(strings.NewReader(string(queryJSON))),
    )
    
    if err != nil {
        return nil, err
    }
    defer res.Body.Close()
    
    // 解析结果
    var result map[string]interface{}
    json.NewDecoder(res.Body).Decode(&result)
    
    products := make([]*Product, 0)
    // 解析 hits ...
    
    return products, nil
}

// SyncProductToES 同步商品到 ES
func (s *ProductSearchService) SyncProductToES(product *Product) error {
    ctx := context.Background()
    
    body, _ := json.Marshal(product)
    
    _, err := s.es.Index(
        "products",
        strings.NewReader(string(body)),
        s.es.Index.WithContext(ctx),
        s.es.Index.WithDocumentID(fmt.Sprintf("%d", product.SkuID)),
    )
    
    return err
}

V3 特点:

  • 最终一致性,性能更高
  • 消息队列解耦服务
  • Elasticsearch 搜索性能好
  • 分库分表支持海量数据
  • ⚠️ 存在短暂不一致
  • 适合大规模场景

6. 核心问题与解决方案

6.1 防止超卖

方案 1: 数据库行锁

func DecrStockWithLock(db *sql.DB, skuID int64, quantity int) error {
    tx, _ := db.Begin()
    defer tx.Rollback()
    
    // 行锁
    var stock int
    tx.QueryRow(`SELECT stock FROM inventory WHERE sku_id = ? FOR UPDATE`, skuID).Scan(&stock)
    
    if stock < quantity {
        return fmt.Errorf("库存不足")
    }
    
    tx.Exec(`UPDATE inventory SET stock = stock - ? WHERE sku_id = ?`, quantity, skuID)
    
    return tx.Commit()
}

方案 2: Redis 原子操作

-- Lua 脚本
local stock = redis.call('GET', KEYS[1])
if tonumber(stock) < tonumber(ARGV[1]) then
    return -1
end
redis.call('DECRBY', KEYS[1], ARGV[1])
return 0

方案 3: 乐观锁

func DecrStockOptimistic(db *sql.DB, skuID int64, quantity int) error {
    for i := 0; i < 3; i++ {
        var stock, version int
        db.QueryRow(`SELECT stock, version FROM inventory WHERE sku_id = ?`, skuID).
            Scan(&stock, &version)
        
        if stock < quantity {
            return fmt.Errorf("库存不足")
        }
        
        result, _ := db.Exec(`
            UPDATE inventory 
            SET stock = stock - ?, version = version + 1
            WHERE sku_id = ? AND version = ?
        `, quantity, skuID, version)
        
        rowsAffected, _ := result.RowsAffected()
        if rowsAffected > 0 {
            return nil
        }
        
        // 版本冲突,重试
        time.Sleep(time.Millisecond * 10)
    }
    
    return fmt.Errorf("扣减库存失败")
}

6.2 订单超时处理

方案 1: 定时任务扫描

func ScanTimeoutOrders() {
    ticker := time.NewTicker(1 * time.Minute)
    defer ticker.Stop()
    
    for range ticker.C {
        // 查询超时未支付订单
        rows, _ := db.Query(`
            SELECT order_id 
            FROM orders 
            WHERE status = 'unpaid' 
              AND created_at < DATE_SUB(NOW(), INTERVAL 15 MINUTE)
            LIMIT 1000
        `)
        
        for rows.Next() {
            var orderID string
            rows.Scan(&orderID)
            
            // 取消订单
            CancelOrder(orderID, "超时未支付")
        }
    }
}

方案 2: 延迟队列

// RabbitMQ 延迟队列
func PublishOrderTimeout(orderID string, delay time.Duration) {
    message := map[string]interface{}{
        "order_id": orderID,
        "action":   "check_timeout",
    }
    
    body, _ := json.Marshal(message)
    
    // 发送到延迟队列
    channel.Publish(
        "",
        "order.timeout.queue",
        false,
        false,
        amqp.Publishing{
            ContentType: "application/json",
            Body:        body,
            Expiration:  fmt.Sprintf("%d", delay.Milliseconds()),
        },
    )
}

6.3 分库分表

// 订单分片规则:按 user_id 取模
func GetOrderTableName(userID int64) string {
    tableIndex := userID % 256
    return fmt.Sprintf("orders_%03d", tableIndex)
}

func GetOrderDBIndex(userID int64) int {
    return int(userID / 256 % 32)
}

// 路由查询
func QueryOrder(userID int64, orderID string) (*Order, error) {
    dbIndex := GetOrderDBIndex(userID)
    tableName := GetOrderTableName(userID)
    
    db := dbCluster[dbIndex]
    
    var order Order
    db.QueryRow(fmt.Sprintf(`
        SELECT * FROM %s WHERE order_id = ?
    `, tableName), orderID).Scan(&order)
    
    return &order, nil
}

7. 监控与告警

var (
    orderTotal = prometheus.NewCounterVec(
        prometheus.CounterOpts{
            Name: "order_total",
            Help: "Total orders",
        },
        []string{"status"},
    )
    
    orderLatency = prometheus.NewHistogramVec(
        prometheus.HistogramOpts{
            Name:    "order_latency_seconds",
            Help:    "Order creation latency",
            Buckets: []float64{0.1, 0.5, 1, 2, 5},
        },
        []string{"step"},
    )
    
    inventoryStock = prometheus.NewGaugeVec(
        prometheus.GaugeOpts{
            Name: "inventory_stock",
            Help: "Current inventory stock",
        },
        []string{"sku_id"},
    )
    
    oversellCount = prometheus.NewCounter(
        prometheus.CounterOpts{
            Name: "inventory_oversell_total",
            Help: "Total oversell count",
        },
    )
)

8. 面试问答(10个高频题)

如何防止库存超卖?

答:

方案对比:

方案优点缺点适用场景
数据库行锁简单可靠性能差,并发低小规模
Redis原子操作性能高需要同步到DB高并发
乐观锁无锁,并发高冲突重试中等并发

最佳实践:

Redis 预扣库存(快速响应)
    ↓
订单支付成功
    ↓
DB 扣减库存(持久化)
    ↓
定时对账(确保一致)

订单状态机如何设计?

答:

unpaid(待支付)→ paid(已支付)→ shipped(已发货)→ received(已收货)→ completed(已完成)
    ↓                  ↓
cancelled(已取消)  refunding(退款中)→ refunded(已退款)

状态流转规则:

  • unpaid → paid: 支付成功
  • paid → cancelled: 支付后可取消(退款)
  • paid → shipped: 商家发货
  • shipped → received: 用户确认收货
  • received → completed: 超过售后期

代码实现:

func (o *Order) CanTransitionTo(newStatus string) bool {
    transitions := map[string][]string{
        "unpaid":    {"paid", "cancelled"},
        "paid":      {"shipped", "refunding"},
        "shipped":   {"received"},
        "received":  {"completed", "refunding"},
    }
    
    allowed := transitions[o.Status]
    for _, s := range allowed {
        if s == newStatus {
            return true
        }
    }
    return false
}

如何处理订单超时?

答:

方案1:定时任务扫描

// 每分钟扫描一次
SELECT order_id FROM orders 
WHERE status = 'unpaid' 
  AND created_at < DATE_SUB(NOW(), INTERVAL 15 MINUTE)

优点:简单 缺点:延迟高,DB 压力大

方案2:延迟队列

  • RabbitMQ 死信队列
  • Redis ZSET(按时间戳排序)
  • 时间轮算法

优点:实时性好 缺点:实现复杂

方案3:本地定时器

time.AfterFunc(15*time.Minute, func() {
    CancelOrder(orderID)
})

优点:实时 缺点:服务重启丢失


购物车如何设计?

答:

存储方案:

方案优点缺点
MySQL持久化,支持复杂查询性能一般
Redis性能高,天然过期持久化弱
Cookie/LocalStorage无需登录容量小,不同步

推荐方案:

  • 未登录:LocalStorage
  • 已登录:Redis + MySQL

Redis 结构:

HSET cart:user:12345 sku:100101 quantity:1
HSET cart:user:12345 sku:100102 quantity:2
EXPIRE cart:user:12345 2592000  # 30天

优惠券如何设计?

答:

类型:

  1. 满减券:满 100 减 10
  2. 折扣券:9 折
  3. 免邮券:免运费
  4. 品类券:指定品类可用

核心表:

CREATE TABLE coupons (
    coupon_id BIGINT PRIMARY KEY,
    type VARCHAR(20),      -- full_discount | percentage | free_shipping
    rule JSON,             -- {"min_amount": 100, "discount": 10}
    total_count INT,       -- 总发行量
    used_count INT,        -- 已使用
    valid_from DATETIME,
    valid_until DATETIME
);

CREATE TABLE user_coupons (
    id BIGINT PRIMARY KEY,
    user_id BIGINT,
    coupon_id BIGINT,
    status VARCHAR(20),    -- unused | used | expired
    used_order_id VARCHAR(64),
    used_at DATETIME
);

使用流程:

  1. 创建订单时选择优惠券
  2. 检查优惠券是否可用
  3. 计算优惠金额
  4. 锁定优惠券(状态=using)
  5. 支付成功→used,失败→unused

如何处理并发下单?

答:

问题:

  • 多个用户同时购买最后一件商品
  • 同一用户重复提交订单

解决方案:

1. 分布式锁:

lockKey := fmt.Sprintf("order:create:user:%d", userID)
if !redis.SetNX(lockKey, "1", 10*time.Second) {
    return errors.New("请勿重复提交")
}
defer redis.Del(lockKey)

2. 幂等性:

// 基于请求ID去重
requestID := r.Header.Get("X-Request-ID")
if cache.Exists(requestID) {
    return cache.Get(requestID) // 返回已有结果
}

3. 库存预扣:

// Redis 原子扣减
remaining := redis.DecrBy("stock:sku:100101", quantity)
if remaining < 0 {
    redis.IncrBy("stock:sku:100101", quantity) // 回滚
    return errors.New("库存不足")
}

分库分表如何查询?

答:

路由规则:

// 按 user_id 分片
dbIndex = user_id / 256 % 32      // 32个数据库
tableIndex = user_id % 256        // 256个表

查询场景:

1. 单个用户的订单(有 user_id):

db := dbCluster[GetDBIndex(userID)]
table := GetTableName(userID)
db.Query(fmt.Sprintf("SELECT * FROM %s WHERE user_id = ?", table), userID)

2. 全局查询(无 user_id,如按订单号查询):

  • 方案1:在订单号中编码 user_id
订单号 = user_id + 时间戳 + 随机数
  • 方案2:建立全局路由表(order_id → user_id)
  • 方案3:Elasticsearch 索引

促销活动如何设计?

答:

活动类型:

  1. 限时折扣:指定时间段打折
  2. 满减:满 200 减 30
  3. N 件 M 折:第二件半价
  4. 秒杀:限量抢购
  5. 拼团:3人成团

核心设计:

CREATE TABLE promotions (
    promo_id BIGINT PRIMARY KEY,
    type VARCHAR(20),       -- discount | full_reduction | seckill
    rule JSON,
    priority INT,           -- 优先级(多个活动)
    start_time DATETIME,
    end_time DATETIME,
    target_type VARCHAR(20), -- product | category | user_level
    target_ids JSON
);

价格计算:

func CalculatePrice(skuID int64, quantity int, userID int64) float64 {
    // 1. 获取商品原价
    originalPrice := GetPrice(skuID)
    
    // 2. 查询适用的促销活动(按优先级)
    promotions := GetActivePromotions(skuID, userID)
    
    // 3. 计算最优价格
    bestPrice := originalPrice
    for _, promo := range promotions {
        price := promo.Calculate(originalPrice, quantity)
        if price < bestPrice {
            bestPrice = price
        }
    }
    
    return bestPrice
}

如何保证订单与库存一致性?

答:

问题:

  • 创建订单成功,扣库存失败
  • 扣库存成功,创建订单失败

解决方案:

方案1:数据库事务(单体)

tx.Begin()
tx.Exec("INSERT INTO orders ...")
tx.Exec("UPDATE inventory SET stock = stock - ?")
tx.Commit()

方案2:TCC 分布式事务(微服务)

  • Try: 预扣库存、创建临时订单
  • Confirm: 确认扣库存、订单转正
  • Cancel: 释放库存、删除订单

方案3:本地消息表 + 最终一致性

1. 创建订单 + 插入本地消息(事务)
2. 定时任务发送消息到 Kafka
3. 库存服务消费消息,扣减库存
4. 定时对账,发现不一致进行补偿

推荐:

  • 小规模:数据库事务
  • 大规模:最终一致性 + 对账

电商系统的性能优化?

答:

1. 缓存优化:

商品详情 → Redis(热点数据)
购物车 → Redis Hash
用户会话 → Redis

2. 数据库优化:

分库分表:订单按 user_id 分片
读写分离:主库写,从库读
索引优化:覆盖索引、组合索引

3. 异步化:

订单创建成功 → Kafka → 
  - 发送短信
  - 更新用户画像
  - 同步到数据仓库

4. 限流降级:

限流:单用户 QPS < 10
降级:关闭推荐、评论等非核心功能
熔断:第三方服务故障时快速失败

5. 搜索优化:

MySQL → Elasticsearch
全文搜索、聚合分析

6. 静态资源:

商品图片 → CDN
页面静态化

9. 总结

核心要点

  1. 防超卖

    • Redis 原子操作
    • 数据库行锁
    • 乐观锁
    • 预扣库存
  2. 订单流程

    • 状态机管理
    • 超时处理
    • 幂等性保证
    • 分布式事务
  3. 高性能

    • Redis 缓存
    • 分库分表
    • 异步消息
    • 搜索引擎
  4. 一致性

    • 数据库事务(单体)
    • TCC(微服务)
    • 最终一致性 + 对账

架构演进

V1: 单体 + 数据库事务
    ↓
V2: 微服务 + TCC + Redis
    ↓
V3: 最终一致性 + 消息队列 + 分库分表

本章完,祝面试顺利!

Prev
09 - 支付系统设计
Next
11 - 直播系统设计