php实现商城秒杀
PHP实现商城秒杀的核心逻辑
商城秒杀的核心在于高并发场景下的库存控制和数据一致性。PHP可通过以下方案实现:
数据库设计优化
创建独立的秒杀商品表,包含字段如id、product_id、stock、start_time、end_time。使用InnoDB引擎确保事务支持:
CREATE TABLE `flash_sale` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`product_id` int(11) NOT NULL,
`stock` int(11) NOT NULL COMMENT '剩余库存',
`start_time` datetime NOT NULL,
`end_time` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_product` (`product_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
乐观锁实现并发控制
在更新库存时添加版本号校验,防止超卖:
// 使用事务+乐观锁
$pdo->beginTransaction();
try {
$stmt = $pdo->prepare("SELECT stock, version FROM flash_sale WHERE product_id = ? FOR UPDATE");
$stmt->execute([$product_id]);
$product = $stmt->fetch(PDO::FETCH_ASSOC);
if ($product['stock'] > 0) {
$update = $pdo->prepare("UPDATE flash_sale SET stock = stock - 1, version = version + 1
WHERE product_id = ? AND version = ?");
$affected = $update->execute([$product_id, $product['version']]);
if ($affected) {
$pdo->commit();
// 生成订单逻辑
}
}
} catch (Exception $e) {
$pdo->rollBack();
}
Redis预减库存
在秒杀开始前将库存加载到Redis,通过原子操作减少数据库压力:
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
// 预减库存
$remain = $redis->decr('flash_sale:'.$product_id);
if ($remain >= 0) {
// 进入下单队列
} else {
// 已售罄
}
消息队列异步处理
使用RabbitMQ或Kafka将订单请求异步化:
// 生产者示例
$connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest');
$channel = $connection->channel();
$channel->queue_declare('order_queue', false, true, false, false);
$msg = new AMQPMessage(json_encode(['user_id' => 123, 'product_id' => 456]));
$channel->basic_publish($msg, '', 'order_queue');
限流措施
通过Nginx或PHP代码限制请求频率:
// Redis实现令牌桶限流
$rateLimit = 100; // 每秒100个请求
$key = 'rate_limit:'.$_SERVER['REMOTE_ADDR'];
$redis->multi();
$redis->incr($key);
$redis->expire($key, 1);
$result = $redis->exec();
if ($result[0] > $rateLimit) {
header('HTTP/1.1 429 Too Many Requests');
exit;
}
静态化页面
将商品详情页生成静态HTML,减少动态请求:
ob_start();
// 渲染页面逻辑
$content = ob_get_clean();
file_put_contents("/cache/product_{$id}.html", $content);
分布式锁
跨服务器场景使用Redis实现分布式锁:
$lockKey = 'lock:product_'.$product_id;
$randomValue = uniqid();
$locked = $redis->set($lockKey, $randomValue, ['NX', 'EX' => 10]);
if ($locked) {
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);
}
}
数据预热
提前将热点数据加载到缓存:
// 秒杀开始前执行
$products = $db->query("SELECT * FROM flash_sale WHERE start_time > NOW()");
foreach ($products as $product) {
$redis->hMSet("product:{$product['id']}", $product);
$redis->set("stock:{$product['id']}", $product['stock']);
}
监控与降级
实施监控系统并在压力过大时启用降级策略:
// 监控Redis内存使用
$used_memory = $redis->info('memory')['used_memory'];
if ($used_memory > 80%_OF_LIMIT) {
// 触发降级策略,如关闭非核心功能
}
以上方案需要根据实际业务场景组合使用,建议通过压力测试验证系统承载能力。







