php 秒杀实现过程
秒杀系统的基本原理
秒杀系统的核心在于应对高并发请求,确保在极短时间内处理大量用户对有限商品的抢购。关键在于系统架构设计、缓存优化、限流和库存管理。
数据库设计
商品表和秒杀活动表需要特别设计。商品表包含库存字段,秒杀活动表记录活动时间、商品ID、秒杀价格等信息。
CREATE TABLE `seckill_goods` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`goods_id` int(11) NOT NULL,
`seckill_price` decimal(10,2) NOT NULL,
`stock_count` int(11) NOT NULL,
`start_time` datetime NOT NULL,
`end_time` datetime NOT NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE `seckill_order` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`order_id` int(11) NOT NULL,
`goods_id` int(11) NOT NULL,
PRIMARY KEY (`id`)
);
缓存预热
活动开始前将秒杀商品信息加载到Redis缓存中,避免直接查询数据库。使用Redis的原子操作保证库存扣减的准确性。
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$redis->set('seckill_goods_1_stock', 100);
限流措施
使用令牌桶或漏桶算法控制请求流量,防止系统过载。Nginx层可以通过配置限制并发连接数。
// 简单计数器限流
$rateLimit = 1000; // 每秒允许的请求数
if ($redis->incr('seckill_rate_limit') > $rateLimit) {
header('HTTP/1.1 429 Too Many Requests');
exit;
}
库存扣减
采用Redis的原子操作确保库存扣减的原子性,避免超卖。使用Lua脚本保证操作的原子性。
$lua = <<<LUA
local stock = tonumber(redis.call('GET', KEYS[1]))
if stock > 0 then
redis.call('DECR', KEYS[1])
return 1
end
return 0
LUA;
$result = $redis->eval($lua, ['seckill_goods_1_stock'], 1);
if ($result) {
// 扣减成功,创建订单
} else {
// 库存不足
}
异步下单
将下单请求放入消息队列,后端消费者异步处理订单创建,减轻数据库压力。
// 使用RabbitMQ等消息队列
$connection = new AMQPConnection();
$channel = $connection->channel();
$channel->queue_declare('seckill_order', false, true, false, false);
$msg = new AMQPMessage(json_encode([
'user_id' => $userId,
'goods_id' => $goodsId
]));
$channel->basic_publish($msg, '', 'seckill_order');
防止重复购买
使用Redis集合记录已购买用户,防止同一用户多次抢购。
if ($redis->sAdd('seckill_goods_1_users', $userId)) {
// 首次购买
} else {
// 已购买过
}
系统降级
当系统压力过大时,自动降级非核心功能,确保核心流程可用。可以关闭部分页面渲染、日志记录等非关键功能。
if ($systemLoad > 80) {
$isDegradeMode = true;
// 关闭非核心功能
}
监控和报警
实时监控系统关键指标,如QPS、响应时间、错误率等,设置报警阈值。
// 使用Prometheus等监控工具记录指标
$metrics->increment('seckill.requests');
$metrics->gauge('seckill.stock', $currentStock);
数据一致性
采用最终一致性方案,通过定时任务核对Redis和数据库的库存数据,修复不一致情况。
// 每小时执行一次核对任务
$redisStock = $redis->get('seckill_goods_1_stock');
$dbStock = $db->query('SELECT stock_count FROM seckill_goods WHERE id=1')->fetchColumn();
if ($redisStock != $dbStock) {
// 修复不一致
}






