一、秒杀场景的技术挑战
典型特征
- 瞬时流量峰值:平时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│
└───────┘ └───────┘ └───────┘
关键优化:
- 连接复用:应用10万连接 → 网关只需1000个后端连接
- 请求合并:相同查询合并为一次
- 智能路由:读写分离、分库分表自动路由
三、完整的秒杀架构
阿里双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);
}
}
}
五、答题总结
面试回答框架:
-
架构层面:
“阿里使用PolarDB和OceanBase替代传统MySQL。PolarDB采用存储计算分离,1个主节点写入,15个只读节点分担查询,总QPS可达160万;OceanBase通过分区表将热点数据打散到256个分区,理论QPS达256万” -
核心技术:
“PolarDB的物理复制实现毫秒级主从延迟,支持强一致性读写分离;OceanBase的LSM-Tree存储引擎将随机写优化为顺序写,配合Paxos协议保证分布式强一致性” -
完整方案:
“秒杀流程不是单靠数据库,而是多层优化:前端CDN静态化,Nginx限流,Redis预扣库存,MQ削峰,最后才是数据库持久化。数据库只需处理MQ消费的平滑流量,避免直接承受瞬时峰值” -
关键点:
“Redis扣减库存用DECR原子操作保证准确性,订单创建异步化提升响应速度,分布式限流和降级保证系统稳定性,多级缓存减少数据库压力”
关键点:
- 理解PolarDB/OceanBase的架构优势
- 掌握存储计算分离、分区表等核心技术
- 强调多级缓存、异步化、限流降级的重要性
- 知道完整的秒杀技术栈,而非单纯依赖数据库