php抢购实现
高并发抢购系统的实现要点
PHP实现抢购功能需要解决高并发下的数据一致性和系统性能问题。核心在于库存控制、请求限流和分布式锁的应用。
数据库设计优化
使用InnoDB引擎并建立合适索引,商品表需包含库存字段并设置无符号整型防止超卖。单独设计订单表关联商品ID和用户ID。
CREATE TABLE `goods` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`stock` int(11) unsigned NOT NULL COMMENT '库存',
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
CREATE TABLE `orders` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`goods_id` int(11) NOT NULL,
`user_id` int(11) NOT NULL,
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
乐观锁实现方案
通过版本号或条件更新实现乐观锁控制。在更新库存时添加库存数量条件,确保不会出现负库存。
$pdo->beginTransaction();
$stmt = $pdo->prepare("UPDATE goods SET stock = stock - 1 WHERE id = ? AND stock > 0");
$stmt->execute([$goods_id]);
if ($stmt->rowCount() > 0) {
// 生成订单
$order_stmt = $pdo->prepare("INSERT INTO orders(goods_id, user_id) VALUES(?, ?)");
$order_stmt->execute([$goods_id, $user_id]);
$pdo->commit();
} else {
$pdo->rollBack();
throw new Exception('库存不足');
}
Redis分布式锁应用
使用SETNX命令实现分布式锁,防止重复请求。设置锁过期时间避免死锁,采用Lua脚本保证原子性。
$redis = new Redis();
$lockKey = 'goods_' . $goods_id;
$randomValue = uniqid();
// 获取锁
if ($redis->set($lockKey, $randomValue, ['NX', 'EX' => 10])) {
try {
// 处理抢购逻辑
} finally {
// Lua脚本保证原子删除
$script = 'if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end';
$redis->eval($script, [$lockKey, $randomValue], 1);
}
}
请求限流策略
使用Redis实现计数器限流,针对用户ID或IP进行访问频率控制。滑动时间窗口算法能更精确控制单位时间请求量。
$redisKey = 'rate_limit:' . $user_id;
$now = time();
$windowSize = 60; // 60秒窗口
$redis->multi();
$redis->zAdd($redisKey, $now, uniqid());
$redis->zRemRangeByScore($redisKey, 0, $now - $windowSize);
$redis->expire($redisKey, $windowSize);
$count = $redis->zCard($redisKey);
$redis->exec();
if ($count > 100) { // 限制每分钟100次请求
header('HTTP/1.1 429 Too Many Requests');
exit;
}
异步处理方案
采用消息队列将下单请求异步化,前端显示排队中状态。RabbitMQ或Redis的List结构可实现简单队列。
// 生产者
$redis->rPush('order_queue', json_encode([
'user_id' => $user_id,
'goods_id' => $goods_id,
'time' => time()
]));
// 消费者
while (true) {
$data = $redis->blPop('order_queue', 30);
if ($data) {
$orderInfo = json_decode($data[1], true);
// 处理订单逻辑
}
}
缓存预热与库存分段
活动开始前预热Redis缓存,将库存分为多段减少竞争。使用Redis的DECR原子操作扣减分段库存。
// 预热10个库存段
for ($i = 0; $i < 10; $i++) {
$redis->set('goods_stock_segment_' . $goods_id . '_' . $i, 10);
}
// 随机选择分段扣减
$segment = mt_rand(0, 9);
$remaining = $redis->decr('goods_stock_segment_' . $goods_id . '_' . $segment);
if ($remaining >= 0) {
// 扣减成功
}
服务降级预案
准备降级策略当系统压力过大时,可关闭非核心功能。使用配置中心实时切换降级状态,前端展示友好提示。
if ($configCenter->get('degrade_mode')) {
header('HTTP/1.1 503 Service Unavailable');
echo json_encode(['code' => 503, 'msg' => '系统繁忙请稍后再试']);
exit;
}






