一、秒杀场景的技术挑战

典型特征

  • 瞬时流量峰值:平时1000 QPS,秒杀瞬间100万 QPS
  • 热点数据集中:大量请求访问同一商品库存
  • 强一致性要求:不能超卖,库存扣减必须准确
  • 响应时间要求:用户体验要求毫秒级响应

传统MySQL的瓶颈

-- 秒杀时的库存扣减
UPDATE goods SET stock = stock - 1 WHERE id = 1001 AND stock > 0;

问题

  • ❌ 行锁竞争:所有请求串行等待
  • ❌ 磁盘IO瓶颈:单机IOPS有限
  • ❌ CPU瓶颈:加锁/解锁开销大
  • ❌ 连接数限制:MySQL默认最大连接数有限

二、阿里云数据库核心技术

1. PolarDB架构(云原生数据库)

核心架构:存储计算分离

传统MySQL架构:
┌─────────────────┐
│  MySQL Server   │
│  (计算+存储)     │
│  - Buffer Pool  │
│  - Redo Log     │
│  - 数据文件      │
└─────────────────┘

PolarDB架构:
┌──────────────┐  ┌──────────────┐  ┌──────────────┐
│  只读节点1    │  │  只读节点2    │  │  只读节点N    │
│  (计算层)     │  │  (计算层)     │  │  (计算层)     │
└──────┬───────┘  └──────┬───────┘  └──────┬───────┘
       │                  │                  │
       └──────────────────┼──────────────────┘
                          │
                 ┌────────▼─────────┐
                 │   主节点(写)      │
                 │   (计算层)        │
                 └────────┬─────────┘
                          │
                 ┌────────▼─────────┐
                 │   PolarFS         │
                 │   (共享存储)      │
                 │   - Redo Log      │
                 │   - 数据文件       │
                 │   - RDMA高速网络  │
                 └──────────────────┘

关键技术1:物理复制(极致性能)

传统MySQL主从复制

主库:执行SQL → 生成Binlog → 传输Binlog
从库:接收Binlog → 解析SQL → 重放SQL → 写入数据

延迟:10-100ms

PolarDB物理复制

主库:执行SQL → 写入共享存储的Redo Log
从库:直接从共享存储读取Redo Log → 回放Page

延迟:< 1ms(读写一致性)

关键技术2:并行查询

// 秒杀场景:大量读请求
@GetMapping("/goods/{id}")
public Goods getGoods(@PathVariable Long id) {
    // PolarDB自动将查询分发到多个只读节点
    return goodsService.getById(id);
}

优势

  • 1个主节点负责写入(库存扣减)
  • 15个只读节点负责查询(商品详情)
  • 总QPS:主节点10万 + 只读节点150万 = 160万

关键技术3:智能热点识别

// PolarDB内置热点检测
// 当检测到某个数据页被频繁访问时:

1. 自动缓存热点页到所有节点的Buffer Pool
2. 读请求优先从本地内存读取
3. 避免访问共享存储减少网络开销

实现原理

// PolarDB热点检测伪代码
class HotPageDetector {
    // 统计每个Page的访问频率
    HashMap<PageId, AccessCount> pageAccessMap;
    
    void recordAccess(PageId pageId) {
        pageAccessMap[pageId]++;
        
        // 访问超过阈值,标记为热点
        if (pageAccessMap[pageId] > HOT_THRESHOLD) {
            markAsHotPage(pageId);
            replicateToAllNodes(pageId);  // 复制到所有只读节点
        }
    }
}

2. OceanBase架构(分布式数据库)

核心架构:分布式强一致

OceanBase集群架构:
┌─────────────────────────────────────────────┐
│            OBServer 集群                     │
│  ┌────────┐  ┌────────┐  ┌────────┐         │
│  │ Zone 1 │  │ Zone 2 │  │ Zone 3 │  多副本 │
│  │(机房1) │  │(机房2) │  │(机房3) │  保证   │
│  └────────┘  └────────┘  └────────┘  高可用 │
└─────────────────────────────────────────────┘
           ↓
    Paxos协议保证一致性

关键技术1:分区表(热点打散)

-- 传统MySQL(单表热点)
CREATE TABLE orders (
    id BIGINT PRIMARY KEY,
    user_id BIGINT,
    goods_id BIGINT,  -- 秒杀商品,所有请求集中在同一行
    amount DECIMAL(10,2)
);

-- OceanBase分区表(打散热点)
CREATE TABLE orders (
    id BIGINT,
    user_id BIGINT,
    goods_id BIGINT,
    amount DECIMAL(10,2),
    create_time DATETIME,
    PRIMARY KEY (goods_id, id)  -- 复合主键
) PARTITION BY HASH(goods_id) PARTITIONS 256;  -- 256个分区

-- 结果:秒杀请求分散到256个分区,每个分区独立加锁

效果

  • 单机行锁QPS:1万
  • 256分区后理论QPS:256万

关键技术2:LSM-Tree存储引擎

传统B+Tree(MySQL InnoDB)

写入流程:
1. 查找插入位置(磁盘随机读)
2. 插入数据
3. 可能触发页分裂(磁盘随机写)

性能:随机写入QPS约1万

LSM-Tree(OceanBase)

写入流程:
1. 写入MemTable(内存)
2. 定期刷入SSTable(磁盘顺序写)
3. 后台异步Compaction

性能:顺序写入QPS可达10万+
// OceanBase写入流程简化
class LSMTreeEngine {
    MemTable activeMemTable;    // 活跃内存表
    List<SSTable> ssTables;      // 磁盘SSTable列表
    
    void write(Key key, Value value) {
        // 1. 直接写入内存
        activeMemTable.put(key, value);
        
        // 2. 内存表满时,刷盘
        if (activeMemTable.isFull()) {
            flushToDisk(activeMemTable);
            activeMemTable = new MemTable();
        }
    }
    
    Value read(Key key) {
        // 1. 先查内存
        Value value = activeMemTable.get(key);
        if (value != null) return value;
        
        // 2. 再查磁盘SSTable(多个文件)
        for (SSTable sst : ssTables) {
            value = sst.get(key);
            if (value != null) return value;
        }
        return null;
    }
}

关键技术3:多副本并行读

// OceanBase读写分离
@Service
public class OrderService {
    
    // 写请求:发送到Leader副本
    @Transactional
    public void createOrder(Order order) {
        // 自动路由到Leader分区
        orderMapper.insert(order);
    }
    
    // 读请求:从Follower副本读取
    @Transactional(readOnly = true)
    public Order getOrder(Long id) {
        // OceanBase自动从Follower读取,减轻Leader压力
        return orderMapper.selectById(id);
    }
}

Paxos保证一致性

写请求流程:
1. Leader接收写请求
2. 通过Paxos协议同步到多数派(如3副本中的2个)
3. 确认多数派写入成功后返回
4. 后台异步同步到剩余副本

读请求流程:
- 强一致性读:从Leader读
- 弱一致性读:从Follower读(可能有延迟)

3. 阿里云数据库网关(Database Gateway)

连接池优化

传统架构问题

应用服务器:1000台
每台连接数:100个
总连接数:10万(MySQL无法承受)

数据库网关方案

┌──────────┐  ┌──────────┐  ┌──────────┐
│ 应用服务器1│  │ 应用服务器2│  │ 应用服务器N│
│ (1000台) │  │           │  │           │
└─────┬────┘  └─────┬────┘  └─────┬────┘
      │             │             │
      └─────────────┼─────────────┘
                    │
           ┌────────▼─────────┐
           │  数据库网关       │
           │  - 连接复用       │
           │  - 请求合并       │
           │  - 智能路由       │
           └────────┬─────────┘
                    │
        ┌───────────┼───────────┐
        ▼           ▼           ▼
    ┌───────┐  ┌───────┐  ┌───────┐
    │ MySQL1│  │ MySQL2│  │ MySQL3│
    └───────┘  └───────┘  └───────┘

关键优化

  1. 连接复用:应用10万连接 → 网关只需1000个后端连接
  2. 请求合并:相同查询合并为一次
  3. 智能路由:读写分离、分库分表自动路由

三、完整的秒杀架构

阿里双11秒杀技术栈

┌─────────────────────────────────────────────────┐
│            前端/CDN层                            │
│  - 静态资源CDN缓存                               │
│  - 商品详情页预渲染                              │
└─────────────────┬───────────────────────────────┘
                  │
┌─────────────────▼───────────────────────────────┐
│            Nginx/OpenResty                       │
│  - 限流:单IP每秒10次                            │
│  - Lua脚本直接返回库存(Redis查询)              │
└─────────────────┬───────────────────────────────┘
                  │
┌─────────────────▼───────────────────────────────┐
│            应用服务层                            │
│  ┌─────────────────────────────────┐            │
│  │  Redis缓存层(热点数据)         │            │
│  │  - 商品信息                      │            │
│  │  - 库存预扣(原子操作)          │            │
│  │  - 用户购买记录(防重复)        │            │
│  └─────────────┬───────────────────┘            │
│                │                                 │
│  ┌─────────────▼───────────────────┐            │
│  │  秒杀服务(Java/Go)             │            │
│  │  - 本地缓存(Caffeine)          │            │
│  │  - 限流(Sentinel/Hystrix)      │            │
│  │  - 降级(库存不足直接返回)      │            │
│  └─────────────┬───────────────────┘            │
└────────────────┼───────────────────────────────┘
                 │
┌────────────────▼───────────────────────────────┐
│            消息队列(RocketMQ)                  │
│  - 订单创建消息(异步处理)                      │
│  - 削峰:100万请求 → 队列 → 1万TPS消费          │
└────────────────┬───────────────────────────────┘
                 │
┌────────────────▼───────────────────────────────┐
│            数据库层                              │
│  ┌─────────────────────────────────┐            │
│  │  PolarDB集群(订单库)           │            │
│  │  - 1主 + 15只读                  │            │
│  │  - 读写QPS:160万                │            │
│  └─────────────────────────────────┘            │
│                                                  │
│  ┌─────────────────────────────────┐            │
│  │  OceanBase集群(库存库)         │            │
│  │  - 256分区                       │            │
│  │  - 理论QPS:256万                │            │
│  └─────────────────────────────────┘            │
└──────────────────────────────────────────────────┘

完整流程

@Service
public class SeckillService {
    
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
    @Autowired
    private RocketMQTemplate rocketMQTemplate;
    
    /**
     * 秒杀入口
     */
    public SeckillResult seckill(Long userId, Long goodsId) {
        String userKey = "seckill:user:" + goodsId + ":" + userId;
        String stockKey = "seckill:stock:" + goodsId;
        
        // 1. 防重复购买(Redis SETNX)
        Boolean firstRequest = redisTemplate.opsForValue()
            .setIfAbsent(userKey, "1", Duration.ofMinutes(10));
        
        if (!firstRequest) {
            return SeckillResult.fail("您已参与过该商品的秒杀");
        }
        
        // 2. 扣减Redis库存(原子操作)
        Long remain = redisTemplate.opsForValue().decrement(stockKey);
        
        if (remain == null || remain < 0) {
            // 库存不足,回滚Redis
            redisTemplate.opsForValue().increment(stockKey);
            return SeckillResult.fail("商品已售罄");
        }
        
        // 3. 创建订单消息,发送到MQ(异步处理)
        SeckillMessage message = new SeckillMessage();
        message.setUserId(userId);
        message.setGoodsId(goodsId);
        message.setTimestamp(System.currentTimeMillis());
        
        rocketMQTemplate.convertAndSend("seckill-order-topic", message);
        
        // 4. 立即返回成功(实际订单异步创建)
        return SeckillResult.success("秒杀成功,订单生成中...");
    }
}

/**
 * 异步订单处理(消费者)
 */
@Service
@RocketMQMessageListener(
    topic = "seckill-order-topic",
    consumerGroup = "seckill-order-consumer"
)
public class SeckillOrderConsumer implements RocketMQListener<SeckillMessage> {
    
    @Autowired
    private OrderService orderService;
    
    @Override
    public void onMessage(SeckillMessage message) {
        try {
            // 1. 创建订单(写入PolarDB)
            Order order = new Order();
            order.setUserId(message.getUserId());
            order.setGoodsId(message.getGoodsId());
            order.setStatus("PENDING");
            orderService.createOrder(order);
            
            // 2. 扣减数据库库存(写入OceanBase)
            goodsService.deductStock(message.getGoodsId(), 1);
            
            // 3. 通知用户
            notifyService.sendSMS(message.getUserId(), "订单创建成功");
            
        } catch (Exception e) {
            log.error("订单创建失败", e);
            // 补偿:回滚Redis库存
            redisTemplate.opsForValue().increment("seckill:stock:" + message.getGoodsId());
        }
    }
}

四、关键技术总结

1. 多级缓存

L1: Nginx本地缓存(商品基本信息,命中率99%)
L2: Redis集群(库存/用户状态,QPS 100万)
L3: 应用本地缓存(热点数据,避免Redis网络开销)
L4: PolarDB Buffer Pool(数据库缓存)

2. 异步化

// 同步流程(响应时间:500ms)
用户请求  校验库存  创建订单  扣减库存  发送通知  返回结果

// 异步流程(响应时间:50ms)
用户请求  扣减Redis库存  发送MQ消息  立即返回成功
           
         后台异步处理创建订单扣减DB库存发送通知

3. 限流降级

@Service
public class SeckillServiceWithSentinel {
    
    @SentinelResource(
        value = "seckill",
        blockHandler = "blockHandler",
        fallback = "fallbackHandler"
    )
    public SeckillResult seckill(Long userId, Long goodsId) {
        // 秒杀逻辑
    }
    
    // 限流降级处理
    public SeckillResult blockHandler(Long userId, Long goodsId, BlockException e) {
        return SeckillResult.fail("系统繁忙,请稍后重试");
    }
    
    // 异常降级处理
    public SeckillResult fallbackHandler(Long userId, Long goodsId, Throwable e) {
        log.error("秒杀异常", e);
        return SeckillResult.fail("服务异常,请稍后重试");
    }
}

4. 热点数据预热

@Component
public class SeckillDataPreloader {
    
    @Scheduled(cron = "0 55 19 * * ?")  // 秒杀前5分钟
    public void preloadSeckillData() {
        List<SeckillGoods> goods = getSeckillGoodsList();
        
        for (SeckillGoods item : goods) {
            // 1. 预热Redis库存
            redisTemplate.opsForValue().set(
                "seckill:stock:" + item.getId(),
                String.valueOf(item.getStock())
            );
            
            // 2. 预热PolarDB(读取数据到Buffer Pool)
            goodsMapper.selectById(item.getId());
            
            // 3. 预热应用本地缓存
            localCache.put(item.getId(), item);
        }
    }
}

五、答题总结

面试回答框架

  1. 架构层面
    “阿里使用PolarDB和OceanBase替代传统MySQL。PolarDB采用存储计算分离,1个主节点写入,15个只读节点分担查询,总QPS可达160万;OceanBase通过分区表将热点数据打散到256个分区,理论QPS达256万”

  2. 核心技术
    “PolarDB的物理复制实现毫秒级主从延迟,支持强一致性读写分离;OceanBase的LSM-Tree存储引擎将随机写优化为顺序写,配合Paxos协议保证分布式强一致性”

  3. 完整方案
    “秒杀流程不是单靠数据库,而是多层优化:前端CDN静态化,Nginx限流,Redis预扣库存,MQ削峰,最后才是数据库持久化。数据库只需处理MQ消费的平滑流量,避免直接承受瞬时峰值”

  4. 关键点
    “Redis扣减库存用DECR原子操作保证准确性,订单创建异步化提升响应速度,分布式限流和降级保证系统稳定性,多级缓存减少数据库压力”

关键点

  • 理解PolarDB/OceanBase的架构优势
  • 掌握存储计算分离、分区表等核心技术
  • 强调多级缓存、异步化、限流降级的重要性
  • 知道完整的秒杀技术栈,而非单纯依赖数据库