千万级并发下的秒杀系统架构设计与实现
一、秒杀系统技术选型
主流秒杀方案性能对比:
技术方案 | QPS | 开发成本 | 数据一致性 |
---|---|---|---|
纯数据库方案 | < 1000 | 低 | 高 |
Redis+队列 | 1万-5万 | 中 | 最终一致 |
Lua脚本+Redis | 5万-10万 | 高 | 强一致 |
分布式锁方案 | 3万-8万 | 高 | 强一致 |
二、系统架构设计
1. 分层削峰架构
用户层 → 接入层 → 服务层 → 队列层 → 数据层 ↑ ↑ ↑ ↑ ↑ 限流策略 Nginx分流 业务逻辑 RabbitMQ Redis集群
2. 数据流设计
请求进入 → 风险控制 → 库存预减 → 订单创建 → 支付通知
↑ ↑ ↑ ↑ ↑
恶意拦截 黑名单过滤 Redis原子操作 异步队列处理 结果回调
三、核心模块实现
1. 商品库存服务
<?php
class StockService
{
private $redis;
private $db;
public function __construct(Redis $redis, PDO $db) {
$this->redis = $redis;
$this->db = $db;
}
/**
* 初始化秒杀库存
*/
public function initStock($itemId, $total) {
$key = "seckill:stock:" . $itemId;
$this->redis->set($key, $total);
// 预热库存到数据库
$stmt = $this->db->prepare(
"INSERT INTO stock(item_id, total, available)
VALUES(?, ?, ?)
ON DUPLICATE KEY UPDATE total=VALUES(total), available=VALUES(available)"
);
$stmt->execute([$itemId, $total, $total]);
}
/**
* 原子扣减库存
*/
public function reduceStock($itemId, $userId) {
$stockKey = "seckill:stock:" . $itemId;
$boughtKey = "seckill:user:" . $itemId;
// 使用Lua脚本保证原子性
$lua = <<<LUA
local stock = tonumber(redis.call('GET', KEYS[1]))
if stock <= 0 then
return 0
end
local bought = redis.call('SISMEMBER', KEYS[2], ARGV[1])
if bought == 1 then
return -1
end
redis.call('DECR', KEYS[1])
redis.call('SADD', KEYS[2], ARGV[1])
return 1
LUA;
$result = $this->redis->eval($lua, [$stockKey, $boughtKey, $userId], 2);
if ($result === 1) {
// 异步更新数据库
$this->asyncUpdateDB($itemId);
return true;
}
return false;
}
private function asyncUpdateDB($itemId) {
$message = json_encode(['item_id' => $itemId, 'type' => 'stock_reduce']);
$this->redis->lPush('seckill:queue', $message);
}
}
2. 订单服务
<?php
class OrderService
{
private $db;
private $redis;
public function __construct(PDO $db, Redis $redis) {
$this->db = $db;
$this->redis = $redis;
}
/**
* 创建秒杀订单
*/
public function createOrder($itemId, $userId) {
// 检查是否已购买
if ($this->hasPurchased($itemId, $userId)) {
throw new Exception('您已经参与过本次秒杀');
}
// 生成订单号
$orderNo = $this->generateOrderNo();
try {
$this->db->beginTransaction();
// 创建订单
$stmt = $this->db->prepare(
"INSERT INTO orders(order_no, user_id, item_id, amount, status)
VALUES(?, ?, ?, 1, 'pending')"
);
$stmt->execute([$orderNo, $userId, $itemId]);
// 扣减数据库库存
$stmt = $this->db->prepare(
"UPDATE stock SET available = available - 1
WHERE item_id = ? AND available > 0"
);
$stmt->execute([$itemId]);
if ($stmt->rowCount() == 0) {
throw new Exception('库存不足');
}
$this->db->commit();
// 记录用户购买
$this->recordPurchase($itemId, $userId);
return $orderNo;
} catch (Exception $e) {
$this->db->rollBack();
throw $e;
}
}
private function generateOrderNo() {
return date('YmdHis') . str_pad(mt_rand(0, 9999), 4, '0', STR_PAD_LEFT);
}
private function hasPurchased($itemId, $userId) {
$key = "seckill:user:" . $itemId;
return $this->redis->sIsMember($key, $userId);
}
private function recordPurchase($itemId, $userId) {
$key = "seckill:user:" . $itemId;
$this->redis->sAdd($key, $userId);
}
}
四、高级功能实现
1. 防刷限流策略
<?php
class AntiSpamService
{
private $redis;
public function __construct(Redis $redis) {
$this->redis = $redis;
}
/**
* IP限流检查
*/
public function checkIpLimit($ip, $itemId, $limit = 10) {
$key = "seckill:ip_limit:" . $itemId . ":" . $ip;
$count = $this->redis->incr($key);
if ($count == 1) {
$this->redis->expire($key, 60);
}
return $count > $limit;
}
/**
* 用户行为分析
*/
public function analyzeUserBehavior($userId, $itemId) {
$key = "seckill:user_behavior:" . $userId;
$data = [
'ip' => $_SERVER['REMOTE_ADDR'],
'user_agent' => $_SERVER['HTTP_USER_AGENT'],
'item_id' => $itemId,
'time' => time()
];
$this->redis->lPush($key, json_encode($data));
$this->redis->lTrim($key, 0, 49);
// 分析最近50次行为
$behaviors = $this->redis->lRange($key, 0, -1);
$analysis = $this->doAnalysis($behaviors);
if ($analysis['risk_score'] > 80) {
$this->addToBlacklist($userId);
}
}
private function doAnalysis($behaviors) {
// 简化的风险分析逻辑
$itemIds = [];
$times = [];
foreach ($behaviors as $behavior) {
$data = json_decode($behavior, true);
$itemIds[] = $data['item_id'];
$times[] = $data['time'];
}
// 计算请求频率
$frequency = count($times) / (max($times) - min($times) + 1);
// 计算商品多样性
$diversity = count(array_unique($itemIds));
// 简单风险评分
$riskScore = min(100, $frequency * 30 + (1 - $diversity/count($itemIds)) * 70);
return [
'risk_score' => $riskScore,
'frequency' => $frequency,
'diversity' => $diversity
];
}
}
2. 异步任务处理
<?php
class AsyncTaskWorker
{
private $redis;
private $db;
private $running = false;
public function __construct(Redis $redis, PDO $db) {
$this->redis = $redis;
$this->db = $db;
}
public function start() {
$this->running = true;
while ($this->running) {
// 从队列获取任务
$message = $this->redis->brPop('seckill:queue', 30);
if ($message) {
try {
$task = json_decode($message[1], true);
$this->processTask($task);
} catch (Exception $e) {
error_log("Task failed: " . $e->getMessage());
// 失败任务重试
$this->redis->lPush('seckill:queue:retry', $message[1]);
}
}
}
}
private function processTask($task) {
switch ($task['type']) {
case 'stock_reduce':
$this->updateStock($task['item_id']);
break;
case 'order_complete':
$this->completeOrder($task['order_id']);
break;
default:
throw new Exception("Unknown task type");
}
}
private function updateStock($itemId) {
$stmt = $this->db->prepare(
"UPDATE stock SET available = available - 1
WHERE item_id = ? AND available > 0"
);
$stmt->execute([$itemId]);
if ($stmt->rowCount() == 0) {
throw new Exception("Stock update failed for item: " . $itemId);
}
}
private function completeOrder($orderId) {
$stmt = $this->db->prepare(
"UPDATE orders SET status = 'completed'
WHERE id = ? AND status = 'pending'"
);
$stmt->execute([$orderId]);
}
}
五、性能优化策略
1. OPcache加速
// php.ini 配置
[opcache]
opcache.enable=1
opcache.enable_cli=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=10000
opcache.revalidate_freq=60
opcache.fast_shutdown=1
// 预热脚本
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator('/path/to/app')
);
foreach ($files as $file) {
if ($file->isFile() && $file->getExtension() === 'php') {
opcache_compile_file($file->getRealPath());
}
}
2. Swoole协程优化
<?php
use SwooleHttpServer;
use SwooleHttpRequest;
use SwooleHttpResponse;
$server = new Server('0.0.0.0', 9501);
// 配置静态文件处理
$server->set([
'enable_static_handler' => true,
'document_root' => '/path/to/public',
'worker_num' => swoole_cpu_num() * 2,
'max_coroutine' => 100000
]);
// 秒杀接口
$server->on('request', function (Request $req, Response $res) {
if ($req->server['request_uri'] === '/seckill') {
$itemId = $req->get['item_id'] ?? 0;
$userId = $req->get['user_id'] ?? 0;
// 协程MySQL客户端
$mysql = new SwooleCoroutineMySQL();
$mysql->connect([
'host' => '127.0.0.1',
'user' => 'root',
'password' => 'password',
'database' => 'seckill'
]);
// 协程Redis客户端
$redis = new SwooleCoroutineRedis();
$redis->connect('127.0.0.1', 6379);
// 执行业务逻辑
$stockService = new StockService($redis, $mysql);
$result = $stockService->reduceStock($itemId, $userId);
$res->header('Content-Type', 'application/json');
$res->end(json_encode(['success' => $result]));
}
});
$server->start();
六、实战案例:电商秒杀系统
1. 秒杀API接口
<?php
// public/seckill.php
require __DIR__ . '/../vendor/autoload.php';
$config = require __DIR__ . '/../config.php';
// 初始化组件
$redis = new Redis();
$redis->connect($config['redis']['host'], $config['redis']['port']);
$db = new PDO(
"mysql:host={$config['db']['host']};dbname={$config['db']['database']}",
$config['db']['user'],
$config['db']['password']
);
// 创建服务实例
$antiSpam = new AntiSpamService($redis);
$stockService = new StockService($redis, $db);
$orderService = new OrderService($db, $redis);
// 获取参数
$itemId = $_GET['item_id'] ?? 0;
$userId = $_GET['user_id'] ?? 0;
$ip = $_SERVER['REMOTE_ADDR'];
header('Content-Type: application/json');
try {
// 1. 安全检查
if ($antiSpam->checkIpLimit($ip, $itemId)) {
throw new Exception('请求过于频繁,请稍后再试');
}
// 2. 分析用户行为
$antiSpam->analyzeUserBehavior($userId, $itemId);
// 3. 扣减库存
if (!$stockService->reduceStock($itemId, $userId)) {
throw new Exception('秒杀失败,库存不足');
}
// 4. 创建订单
$orderNo = $orderService->createOrder($itemId, $userId);
echo json_encode([
'success' => true,
'order_no' => $orderNo
]);
} catch (Exception $e) {
echo json_encode([
'success' => false,
'message' => $e->getMessage()
]);
}
2. 库存预热脚本
<?php
// scripts/init_stock.php
require __DIR__ . '/../vendor/autoload.php';
$config = require __DIR__ . '/../config.php';
$redis = new Redis();
$redis->connect($config['redis']['host'], $config['redis']['port']);
$db = new PDO(
"mysql:host={$config['db']['host']};dbname={$config['db']['database']}",
$config['db']['user'],
$config['db']['password']
);
$stockService = new StockService($redis, $db);
// 从数据库加载秒杀商品
$stmt = $db->query("SELECT id, stock FROM items WHERE is_seckill = 1");
$items = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($items as $item) {
echo "初始化商品 {$item['id']} 库存: {$item['stock']}n";
$stockService->initStock($item['id'], $item['stock']);
}
echo "库存预热完成n";