php 实现秒杀功能
PHP实现秒杀功能的核心要点
秒杀场景的特点是高并发、低库存,需解决超卖、性能瓶颈、恶意请求等问题。以下是关键实现方案:
数据库设计优化
使用InnoDB引擎,利用事务和行锁保证数据一致性。商品表需包含stock字段(无符号整型)并设置乐观锁版本号:
CREATE TABLE `seckill_goods` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`stock` int(11) unsigned NOT NULL COMMENT '剩余库存',
`version` int(11) unsigned DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
悲观锁方案(MySQL行锁)
通过SELECT ... FOR UPDATE锁定记录,适合并发量中等场景:
$pdo->beginTransaction();
try {
$stmt = $pdo->prepare("SELECT stock FROM seckill_goods WHERE id = ? FOR UPDATE");
$stmt->execute([$goods_id]);
$stock = $stmt->fetchColumn();
if ($stock > 0) {
$pdo->prepare("UPDATE seckill_goods SET stock = stock - 1 WHERE id = ?")->execute([$goods_id]);
$pdo->commit();
echo "秒杀成功";
} else {
$pdo->rollBack();
echo "库存不足";
}
} catch (Exception $e) {
$pdo->rollBack();
echo "系统异常";
}
乐观锁方案
通过版本号避免阻塞,适合高并发场景:
$pdo->beginTransaction();
try {
// 先查询当前版本号
$stmt = $pdo->prepare("SELECT stock, version FROM seckill_goods WHERE id = ?");
$stmt->execute([$goods_id]);
$data = $stmt->fetch(PDO::FETCH_ASSOC);
if ($data['stock'] > 0) {
// 带版本号更新
$affected = $pdo->prepare("UPDATE seckill_goods SET stock = stock - 1, version = version + 1
WHERE id = ? AND version = ?")->execute([$goods_id, $data['version']]);
if ($affected) {
$pdo->commit();
echo "秒杀成功";
} else {
$pdo->rollBack();
echo "请重试";
}
} else {
$pdo->rollBack();
echo "库存不足";
}
} catch (Exception $e) {
$pdo->rollBack();
echo "系统异常";
}
Redis原子操作
利用Redis的DECR和WATCH实现原子减库存:
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$redis_key = "seckill:goods:$goods_id";
// 监控库存键
$redis->watch($redis_key);
$stock = $redis->get($redis_key);
if ($stock > 0) {
$redis->multi();
$redis->decr($redis_key);
$result = $redis->exec();
if ($result) {
echo "秒杀成功";
// 异步更新数据库
} else {
echo "请重试";
}
} else {
$redis->unwatch();
echo "库存不足";
}
流量削峰策略
- 前端限流:按钮倒计时+随机延迟提交
- 令牌桶算法:使用Redis实现请求速率限制
- 消息队列:将请求写入RabbitMQ/Kafka异步处理
// Redis令牌桶示例
$rate_limit_key = "user_limit:$user_id";
$limit = 10; // 每秒10次
if ($redis->lLen($rate_limit_key) < $limit) {
$redis->lPush($rate_limit_key, time());
$redis->expire($rate_limit_key, 1);
// 处理请求
} else {
echo "操作过于频繁";
}
防刷机制
- 隐藏秒杀接口:动态生成URL
- 验证码验证:复杂图形验证码
- 用户限购:Redis记录用户购买记录
- IP限制:Nginx层限制单IP访问频率
分层缓存架构
- Nginx缓存:静态页面缓存
- Redis集群:库存预热+热点数据缓存
- 数据库分库分表:降低单表压力
注意事项
- 压测工具:使用JMeter模拟10万级并发测试
- 库存预热:活动开始前将库存加载到Redis
- 超时处理:设置合理的PHP脚本执行时间
- 降级方案:库存售罄后直接返回静态页
实际部署时需根据业务规模选择组合方案,分布式环境需考虑Redis集群和分布式锁的实现。







