php秒杀 实现过程
数据库设计
创建商品表和订单表,商品表需包含库存字段(stock),订单表记录秒杀成功的用户信息。使用事务和锁机制确保数据一致性。
CREATE TABLE `seckill_goods` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL,
`stock` int(11) NOT NULL,
PRIMARY KEY (`id`)
);
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` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
);
悲观锁实现
在事务中使用SELECT...FOR UPDATE锁定商品记录,防止其他请求同时修改库存。检查库存充足后执行减库存和创建订单操作。
$pdo->beginTransaction();
try {
$stmt = $pdo->prepare("SELECT stock FROM seckill_goods WHERE id = ? FOR UPDATE");
$stmt->execute([$goodsId]);
$stock = $stmt->fetchColumn();
if ($stock > 0) {
$pdo->prepare("UPDATE seckill_goods SET stock = stock - 1 WHERE id = ?")->execute([$goodsId]);
$pdo->prepare("INSERT INTO seckill_orders (user_id, goods_id) VALUES (?, ?)")->execute([$userId, $goodsId]);
}
$pdo->commit();
} catch (Exception $e) {
$pdo->rollBack();
throw $e;
}
乐观锁实现
通过版本号或条件更新实现,在UPDATE语句中增加库存判断条件,利用affectedRows判断是否秒杀成功。
$pdo->beginTransaction();
try {
$affected = $pdo->prepare("UPDATE seckill_goods SET stock = stock - 1 WHERE id = ? AND stock > 0")
->execute([$goodsId]);
if ($affected > 0) {
$pdo->prepare("INSERT INTO seckill_orders (user_id, goods_id) VALUES (?, ?)")
->execute([$userId, $goodsId]);
}
$pdo->commit();
} catch (Exception $e) {
$pdo->rollBack();
throw $e;
}
缓存预热
将商品信息加载到Redis,使用原子操作DECR处理库存。避免频繁访问数据库,Lua脚本保证操作原子性。
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$redis->set("goods_stock_{$goodsId}", 100); // 初始化库存
$lua = "
local stock = tonumber(redis.call('get', KEYS[1]))
if stock > 0 then
redis.call('decr', KEYS[1])
return 1
end
return 0
";
$result = $redis->eval($lua, ["goods_stock_{$goodsId}"], 1);
if ($result) {
// 秒杀成功,异步处理数据库
}
请求限流
使用Redis计数器限制单位时间内的请求量,防止系统过载。结合IP和用户ID进行更细粒度的控制。
$key = 'seckill_rate_limit_' . date('YmdHi');
$redis->multi();
$redis->incr($key);
$redis->expire($key, 60);
$redis->exec();
if ($redis->get($key) > 1000) {
header('HTTP/1.1 429 Too Many Requests');
exit;
}
异步处理
将秒杀请求放入消息队列(如RabbitMQ),后台消费者顺序处理。实现流量削峰,提高系统吞吐量。
$connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest');
$channel = $connection->channel();
$channel->queue_declare('seckill_queue', false, true, false, false);
$msg = new AMQPMessage(json_encode([
'user_id' => $userId,
'goods_id' => $goodsId
]));
$channel->basic_publish($msg, '', 'seckill_queue');
$channel->close();
$connection->close();
防作弊措施
验证用户身份和请求合法性,限制单个用户购买数量。使用隐藏表单令牌防止CSRF攻击。
session_start();
$token = md5(uniqid());
$_SESSION['seckill_token'] = $token;
// 在表单中输出
<input type="hidden" name="token" value="<?php echo $token; ?>">
// 提交时验证
if (empty($_POST['token']) || $_POST['token'] !== $_SESSION['seckill_token']) {
die('非法请求');
}






