php 实现抢购功能
数据库设计
使用事务和锁机制确保数据一致性。创建商品表时需包含库存字段(stock)并设置乐观锁(version字段)或悲观锁。
CREATE TABLE `seckill_goods` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL,
`stock` int(11) NOT NULL COMMENT '库存',
`version` int(11) DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
乐观锁实现
通过版本号控制并发,更新时校验版本号是否变化。适合冲突较少的场景。
$pdo->beginTransaction();
try {
$stmt = $pdo->prepare("SELECT stock, version FROM seckill_goods WHERE id = ? FOR UPDATE");
$stmt->execute([$goodsId]);
$goods = $stmt->fetch(PDO::FETCH_ASSOC);
if ($goods['stock'] > 0) {
$update = $pdo->prepare("UPDATE seckill_goods SET stock = stock - 1, version = version + 1
WHERE id = ? AND version = ?");
$affected = $update->execute([$goodsId, $goods['version']]);
if ($affected) {
// 生成订单逻辑
$pdo->commit();
return true;
}
}
$pdo->rollBack();
return false;
} catch (Exception $e) {
$pdo->rollBack();
throw $e;
}
悲观锁实现
使用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) {
$update = $pdo->prepare("UPDATE seckill_goods SET stock = stock - 1 WHERE id = ?");
$update->execute([$goodsId]);
// 生成订单逻辑
$pdo->commit();
return true;
}
$pdo->rollBack();
return false;
} catch (Exception $e) {
$pdo->rollBack();
throw $e;
}
Redis队列方案
使用Redis的原子操作减轻数据库压力,通过LIST结构实现排队。
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
// 预加载库存到Redis
$redis->set("goods_stock_{$goodsId}", 100);
// 抢购逻辑
$remaining = $redis->decr("goods_stock_{$goodsId}");
if ($remaining >= 0) {
// 订单入队列
$redis->lPush("order_queue", json_encode([
'user_id' => $userId,
'goods_id' => $goodsId
]));
return true;
} else {
$redis->incr("goods_stock_{$goodsId}"); // 恢复库存
return false;
}
限流措施
采用令牌桶算法控制请求速率,防止系统过载。
$rateLimiter = new RedisRateLimiter($redis);
$key = "user_limit_{$userId}";
if (!$rateLimiter->consume($key, 10, 60)) { // 60秒内最多10次
header('HTTP/1.1 429 Too Many Requests');
exit;
}
前端优化
使用倒计时同步和按钮禁用防止重复提交。活动开始前禁用提交按钮,通过服务端时间校准倒计时。
function syncCountdown() {
fetch('/api/server_time').then(res => res.json()).then(data => {
const remainMs = new Date(data.start_time) - Date.now();
if (remainMs > 0) {
disableSubmitButton();
setTimeout(enableSubmitButton, remainMs);
}
});
}
库存预热
活动前将库存加载到内存数据库,采用分段锁提升并发性能。将商品库存拆分为多个子库存单元,减少锁竞争。
// 初始化10个子库存
for ($i=0; $i<10; $i++) {
$redis->set("sub_stock_{$goodsId}_{$i}", 10);
}
// 随机选择子库存扣减
$slot = mt_rand(0, 9);
$subKey = "sub_stock_{$goodsId}_{$slot}";
if ($redis->decr($subKey) >= 0) {
// 扣减成功
}
异步处理
使用消息队列解耦抢购与订单创建流程,提升系统响应速度。
$message = [
'user_id' => $userId,
'goods_id' => $goodsId,
'timestamp' => time()
];
$redis->lPush('seckill_orders', json_encode($message));
// 后台Worker处理订单
while (true) {
$order = $redis->rPop('seckill_orders');
if ($order) {
processOrder(json_decode($order, true));
}
usleep(100000); // 100ms间隔
}





