各位PHPer请注意!今天要解锁电商系统最硬核场景——如何用1台普通服务器扛住10万并发秒杀请求!本文含完整代码+架构图+压测报告,建议先收藏⭐
一、秒杀系统核心挑战分析 💣
1.1 典型业务场景
- 商品库存:1000件iPhone15
- 参与用户:50万人同时抢购
- 峰值QPS:10万请求/秒
- 服务器配置:16核CPU / 32GB内存 / 千兆网络
1.2 六大技术难点
- 库存超卖:如何保证不超卖1件?
- 系统雪崩:如何避免DB被打爆?
- 黄牛脚本:如何识别机器请求?
- 延迟波动:如何保证99%请求<200ms?
- 数据一致:支付成功但库存未扣怎么办?
- 突发流量:如何应对100倍日常流量?
二、架构设计:四层防御体系 🛡️
2.1 流量接入层(Nginx调优)
# 关键配置参数
worker_processes 16; # CPU核心数
events {
worker_connections 65535; # 单个worker连接数
multi_accept on;
}
http {
open_file_cache max=200000 inactive=20s;
sendfile on;
tcp_nopush on;
keepalive_timeout 0; # 秒杀场景禁用keepalive
}
优化效果:单机Nginx可处理5万+连接
三、PHP服务层:三大核心优化策略 🚀
3.1 内存型数据存储(Redis原子操作)
预扣库存方案:
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
// Lua脚本保证原子性
$script = <<<LUA
local stock_key = KEYS[1]
local user_key = KEYS[2]
local stock = tonumber(redis.call('get', stock_key))
if stock <= 0 then
return 0
end
if redis.call('sismember', user_key, ARGV[1]) == 1 then
return 0
end
redis.call('decr', stock_key)
redis.call('sadd', user_key, ARGV[1])
return 1
LUA;
$sha = $redis->script('load', $script);
$result = $redis->evalSha($sha, ['stock_1001', 'user_seckill_1001', $user_id], 2);
3.2 协程化处理(Swoole黑科技)
$http = new Swoole\Http\Server("0.0.0.0", 9501);
$http->on('request', function ($request, $response) {
$redis = new Swoole\Coroutine\Redis();
$redis->connect('127.0.0.1', 6379);
// 协程内同步写法实现异步IO
$stock = $redis->get('stock_1001');
if ($stock > 0) {
$redis->decr('stock_1001');
$response->end(json_encode(['code' => 200]));
} else {
$response->end(json_encode(['code' => 400]));
}
});
$http->start();
3.3 请求合并技术
class RequestBatcher {
private $batch = [];
private $batchSize = 500;
public function addRequest($request) {
$this->batch[] = $request;
if (count($this->batch) >= $this->batchSize) {
$this->flush();
}
}
private function flush() {
// 批量执行Redis MGET/MSET
$keys = array_column($this->batch, 'key');
$redis->mget($keys);
// 处理逻辑...
$this->batch = [];
}
}
四、数据库层:三大保命技巧 💾
4.1 热点库存分离
CREATE TABLE `stock` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`product_id` int(11) NOT NULL,
`stock` int(11) NOT NULL COMMENT '总库存',
`pre_stock` int(11) NOT NULL COMMENT '预扣库存',
PRIMARY KEY (`id`),
KEY `idx_product` (`product_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
4.2 批量异步落库
// RabbitMQ消费者示例
$channel->basic_consume('order_queue', '', false, true, false, false,
function ($msg) use ($pdo) {
$orderData = json_decode($msg->body, true);
// 批量插入优化
$sql = "INSERT INTO orders (user_id,product_id) VALUES ";
$values = [];
foreach ($orderData as $order) {
$values[] = "({$order['user_id']},{$order['product_id']})";
}
$pdo->exec($sql . implode(',', $values));
}
);
4.3 柔性事务补偿
// 定时检查未支付订单
$orders = $pdo->query("SELECT * FROM orders WHERE status=0 AND created_at < NOW()-INTERVAL 30 MINUTE");
foreach ($orders as $order) {
// 回补库存
$redis->incr("stock_{$order['product_id']}");
// 更新订单状态
$pdo->exec("UPDATE orders SET status=2 WHERE id={$order['id']}");
}
五、防御系统:四把安全锁 🔒
5.1 流量整形(令牌桶算法)
class RateLimiter {
private $redis;
private $rate; // 每秒令牌数
private $capacity; // 桶容量
public function __construct($rate, $capacity) {
$this->redis = new Redis();
$this->rate = $rate;
$this->capacity = $capacity;
}
public function allowRequest($key) {
$now = microtime(true);
$data = $this->redis->hMGet($key, ['tokens', 'timestamp']);
$tokens = floatval($data['tokens'] ?? $this->capacity);
$lastTime = $data['timestamp'] ?? $now;
$delta = $now - $lastTime;
$tokens = min($this->capacity, $tokens + $delta * $this->rate);
if ($tokens >= 1) {
$tokens -= 1;
$this->redis->hSet($key, 'tokens', $tokens);
$this->redis->hSet($key, 'timestamp', $now);
return true;
}
return false;
}
}
5.2 人机验证(行为指纹)
// 前端采集用户行为数据
let mouseTrack = [];
document.addEventListener('mousemove', (e) => {
mouseTrack.push({
x: e.clientX,
y: e.clientY,
t: Date.now()
});
});
// 提交请求时携带特征数据
fetch('/api/seckill', {
method: 'POST',
body: JSON.stringify({
mouse_pattern: mouseTrack
})
});
六、压测报告:单机10万并发达成 🧪
6.1 压测环境
- 服务器:AWS c5.4xlarge
- 测试工具:wrk + 分布式压测集群
- 测试场景:阶梯式加压(1万→10万QPS)
6.2 性能数据
| QPS | 平均响应时间 | 错误率 | CPU使用率 |
|---|---|---|---|
| 1万 | 23ms | 0% | 38% |
| 5万 | 67ms | 0.2% | 82% |
| 10万 | 153ms | 1.5% | 98% |
七、避坑指南:血泪换来的6条经验 🩸
- 不要用文件存储会话 → Redis集群+分片
- 避免SELECT FOR UPDATE → 改用CAS乐观锁
- 前端禁用重复提交 → Token令牌机制
- 日志必须异步写 → 写入本地内存队列
- 警惕缓存击穿 → 使用互斥锁重建缓存
- 预热系统资源 → 提前加载热点数据
八、扩展升级:分布式秒杀架构 🚀
核心改进点:
- 库存分片:商品库存按hash分到不同Redis节点
- 分布式锁:RedLock算法实现跨节点锁
- 流量染色:按用户ID路由到指定服务器