php抢单实现
PHP抢单实现方案
抢单功能常见于外卖、打车、配送等平台,核心在于高并发下数据的原子性操作和公平性保障。以下是具体实现方案:
数据库设计
订单表需包含状态字段(如status),标识是否可抢:
CREATE TABLE orders (
id INT PRIMARY KEY AUTO_INCREMENT,
order_data TEXT,
status ENUM('available', 'locked', 'completed') DEFAULT 'available',
locked_at TIMESTAMP NULL,
locked_by INT NULL
);
悲观锁实现
使用事务+行锁确保原子性:
$pdo->beginTransaction();
try {
$stmt = $pdo->prepare("SELECT * FROM orders WHERE id = ? AND status = 'available' FOR UPDATE");
$stmt->execute([$orderId]);
$order = $stmt->fetch();
if ($order) {
$update = $pdo->prepare("UPDATE orders SET status = 'locked', locked_at = NOW(), locked_by = ? WHERE id = ?");
$update->execute([$userId, $orderId]);
$pdo->commit();
return ['success' => true];
}
$pdo->rollBack();
} catch (Exception $e) {
$pdo->rollBack();
return ['error' => $e->getMessage()];
}
Redis原子操作
利用Redis的原子性实现高性能抢单:
$redis = new Redis();
$redis->connect('127.0.0.1');
$lockKey = "order_lock:$orderId";
$lockExpire = 30; // 秒
// SETNX+EXPIRE原子操作
if ($redis->set($lockKey, $userId, ['nx', 'ex' => $lockExpire])) {
// 抢单成功后的业务处理
return ['success' => true];
}
队列消峰处理
使用消息队列缓解并发压力:
- 用户请求进入RabbitMQ/Kafka队列
- 消费者进程顺序处理:
$channel->basic_consume('order_queue', '', false, true, false, false, function($msg) use ($pdo) { $data = json_decode($msg->body, true); // 处理抢单逻辑 processOrder($data['order_id'], $data['user_id']); } );
时间窗口限制
防止用户频繁请求:

$rateLimiter = new RedisRateLimiter($redis);
$key = "user_limit:$userId";
if (!$rateLimiter->attempt($key, 5, 60)) { // 60秒内5次尝试
throw new Exception('操作过于频繁');
}
注意事项
- 锁过期时间需合理设置(通常30-120秒)
- 抢单失败需明确返回原因(已抢完/操作频繁)
- 分布式环境需使用分布式锁
- 考虑添加验证码防止机器人刷单
性能优化建议
- 使用连接池管理数据库连接
- 热点数据预加载到Redis
- 采用HTTP长轮询或WebSocket减少无效请求
- 订单列表分页查询时避免大结果集
以上方案可根据实际业务场景组合使用,高并发场景推荐Redis+队列的组合方案。






