php限时抢购实现思路
限时抢购功能设计
限时抢购的核心在于处理高并发请求、保证数据一致性以及防止超卖。以下是PHP实现的关键技术点:
数据库设计
创建商品表时需包含抢购相关字段:
CREATE TABLE `flash_sale` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`goods_id` int(11) NOT NULL COMMENT '商品ID',
`stock` int(11) NOT NULL COMMENT '库存',
`start_time` datetime NOT NULL COMMENT '开始时间',
`end_time` datetime NOT NULL COMMENT '结束时间',
PRIMARY KEY (`id`),
KEY `idx_time` (`start_time`,`end_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
原子性操作实现
使用MySQL乐观锁保证库存扣减的原子性:
$pdo->beginTransaction();
$stmt = $pdo->prepare("SELECT stock FROM flash_sale WHERE goods_id = ? FOR UPDATE");
$stmt->execute([$goods_id]);
$stock = $stmt->fetchColumn();
if ($stock > 0) {
$update = $pdo->prepare("UPDATE flash_sale SET stock = stock - 1 WHERE goods_id = ? AND stock > 0");
$update->execute([$goods_id]);
if ($update->rowCount() > 0) {
$pdo->commit();
// 生成订单
} else {
$pdo->rollBack();
}
}
缓存预热与库存同步
采用Redis预减库存方案:
// 活动开始前预热库存
$redis->set("fs_stock_{$goods_id}", $stock);
// 抢购时原子操作
$remaining = $redis->decr("fs_stock_{$goods_id}");
if ($remaining >= 0) {
// 进入下单流程
} else {
// 已售罄
}
接口限流措施
使用Redis实现令牌桶限流:
$key = "fs_rate_limit_{$user_id}";
$now = microtime(true);
$redis->zRemRangeByScore($key, 0, $now - 60); // 清除60秒前的记录
$requestCount = $redis->zCard($key);
if ($requestCount < 30) { // 每分钟30次
$redis->zAdd($key, $now, uniqid());
// 处理请求
} else {
http_response_code(429);
exit;
}
异步订单处理
采用消息队列解耦:
// 抢购成功后发送消息
$queue->push([
'user_id' => $userId,
'goods_id' => $goodsId,
'time' => time()
]);
// 消费者处理订单
while ($job = $queue->pop()) {
try {
$orderService->createOrder($job->data);
$job->delete();
} catch (Exception $e) {
$job->release();
}
}
防刷机制实现
实现用户级别限制:
$lockKey = "fs_user_lock_{$user_id}_{$goods_id}";
if ($redis->set($lockKey, 1, ['nx', 'ex' => 3600])) {
// 允许购买
} else {
// 重复请求
}
服务降级方案
当系统压力过大时返回静态页:
if ($loadAverage > 80) {
header('HTTP/1.1 503 Service Unavailable');
readfile('static/fallback.html');
exit;
}
数据一致性保障
通过定时任务同步缓存与数据库:
// 每小时执行一次
$stocks = $redis->keys("fs_stock_*");
foreach ($stocks as $key) {
$goodsId = str_replace('fs_stock_', '', $key);
$dbStock = $db->query("SELECT stock FROM flash_sale WHERE goods_id = $goodsId")->fetchColumn();
$redisStock = $redis->get($key);
if ($dbStock != $redisStock) {
$redis->set($key, min($dbStock, $redisStock));
}
}
以上方案需要根据实际业务场景调整参数,建议配合压力测试验证系统承载能力。分布式环境下还需考虑使用分布式锁替代文件锁,确保集群环境下的数据一致性。







