商品秒杀实现php
商品秒杀系统的核心设计
高并发场景下,商品秒杀系统需要解决超卖、性能瓶颈和公平性问题。PHP实现需结合数据库优化、缓存和队列技术。
数据库设计
创建秒杀商品表和订单表,关键字段需添加索引:
CREATE TABLE `seckill_goods` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL,
`stock` int(11) NOT NULL,
`start_time` datetime NOT NULL,
`end_time` datetime NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_time` (`start_time`,`end_time`)
);
CREATE TABLE `seckill_orders` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`goods_id` int(11) NOT NULL,
`create_time` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_user_goods` (`user_id`,`goods_id`)
);
库存预加载
活动开始前将库存加载到Redis:
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$goodsId = 1;
$stock = 1000; // 从数据库获取实际库存
$redis->set("seckill:goods:$goodsId:stock", $stock);
原子性扣减库存
使用Redis Lua脚本保证原子操作:
$lua = <<<LUA
local stock = tonumber(redis.call('GET', KEYS[1]))
if stock <= 0 then
return 0
end
redis.call('DECR', KEYS[1])
return 1
LUA;
$goodsKey = "seckill:goods:$goodsId:stock";
$result = $redis->eval($lua, [$goodsKey], 1);
消息队列处理订单
库存扣减成功后投递订单到RabbitMQ:
$connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest');
$channel = $connection->channel();
$channel->queue_declare('order_queue', false, true, false, false);
$orderData = json_encode([
'user_id' => $userId,
'goods_id' => $goodsId
]);
$msg = new AMQPMessage($orderData, ['delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT]);
$channel->basic_publish($msg, '', 'order_queue');
防重复购买
使用Redis SET记录已购买用户:
$userKey = "seckill:goods:$goodsId:users";
if ($redis->sAdd($userKey, $userId)) {
// 允许购买
} else {
// 已购买过
}
接口限流
使用Redis实现令牌桶限流:
$rateLimitKey = "seckill:rate_limit:$goodsId";
$capacity = 1000; // 最大请求数
$rate = 100; // 每秒恢复数量
$now = microtime(true);
$lastTime = $redis->get("$rateLimitKey:last_time") ?: $now;
$tokens = $redis->get("$rateLimitKey:tokens") ?: $capacity;
$elapsed = $now - $lastTime;
$tokens = min($capacity, $tokens + $elapsed * $rate);
if ($tokens < 1) {
http_response_code(429);
exit;
}
$tokens--;
$redis->set("$rateLimitKey:last_time", $now);
$redis->set("$rateLimitKey:tokens", $tokens);
数据库最终一致性
消费队列处理实际订单:
$callback = function ($msg) {
$orderData = json_decode($msg->body, true);
try {
$pdo = new PDO('mysql:host=localhost;dbname=seckill', 'root', '');
$pdo->beginTransaction();
$stmt = $pdo->prepare("INSERT INTO seckill_orders VALUES (NULL, ?, ?, NOW())");
$stmt->execute([$orderData['user_id'], $orderData['goods_id']]);
$stmt = $pdo->prepare("UPDATE seckill_goods SET stock = stock - 1 WHERE id = ?");
$stmt->execute([$orderData['goods_id']]);
$pdo->commit();
$msg->delivery_info['channel']->basic_ack($msg->delivery_info['delivery_tag']);
} catch (Exception $e) {
$pdo->rollBack();
// 记录失败或重试
}
};
$channel->basic_consume('order_queue', '', false, false, false, false, $callback);
系统优化建议
采用Nginx负载均衡分流请求,PHP建议使用Swoole扩展提升并发处理能力。静态资源部署到CDN,活动页面做静态化处理。数据库主从分离,读写操作分配到不同实例。







