在高并发场景下,200个骑手同时抢一个外卖单,要确保只有一个骑手成功接单,可以采用以下方案:
方案 1:基于 Redis 分布式锁(SETNX)
Redis 的 SETNX(SET if Not eXists)+ 过期时间可以确保只有一个骑手成功获取订单。
实现步骤
- 所有骑手尝试抢单:
• SET order_123 "rider_id" NX EX 5
• 只有第一个成功执行 SETNX(抢到锁)的骑手能接单。
• 失败的骑手说明订单已经被别人抢到,无法接单。
- 成功抢单的骑手:
• 订单状态变更为 “已接单”。
• 通知骑手。
- 未成功的骑手:
• 立即返回抢单失败,或者提示 “已被抢走”。
Redis 代码示例
import redis
r = redis.Redis()
def grab_order(order_id, rider_id):
key = f"order_{order_id}"
success = r.set(key, rider_id, nx=True, ex=5) # 5秒过期,防止死锁
if success:
print(f"骑手 {rider_id} 抢到了订单 {order_id}")
return True
else:
print(f"骑手 {rider_id} 抢单失败")
return False
# 200个骑手同时尝试抢单
from concurrent.futures import ThreadPoolExecutor
order_id = 123
riders = [f"rider_{i}" for i in range(200)]
with ThreadPoolExecutor() as executor:
executor.map(lambda rider: grab_order(order_id, rider), riders)
✅ 优势:
• Redis 操作原子性保证,只有一个骑手能成功抢单。
• 高性能:Redis 的 SETNX 操作是 O(1) 复杂度,能支持高并发。
🚨 注意点:
• 需要设置 EX 过期时间,防止异常情况下锁一直存在,订单卡死。
方案 2:MySQL 乐观锁
利用 MySQL 的行级锁,确保多个骑手同时抢单时,只有一个能成功。
实现步骤
- 订单表增加状态字段 status:
• 0:待接单
• 1:已被骑手接单
- 骑手抢单时,使用乐观锁更新:
• UPDATE orders SET rider_id = ? , status = 1 WHERE id = ? AND status = 0
• 只有第一个执行成功的骑手能抢到订单。
MySQL SQL 语句
UPDATE orders
SET rider_id = 'rider_5', status = 1
WHERE id = 123 AND status = 0;
✅ 优势:
• 数据库层面保证唯一性,即使 Redis 崩溃也不会影响逻辑。
• 适用于中等并发场景(如 1w QPS 内)。
🚨 注意点:
• 高并发时可能导致数据库压力大,需要配合Redis 缓存降低数据库压力。
方案 3:基于消息队列(MQ)顺序处理
如果订单量很大,可以使用 Kafka / RabbitMQ 让骑手排队抢单。
实现步骤
-
骑手请求进入 MQ 队列。
-
消费者进程顺序消费 MQ 中的请求,确保只处理第一个骑手的请求。
-
成功接单的骑手被通知,其他骑手请求被丢弃。
✅ 优势:
• 消息顺序处理,避免竞争。
• 适用于超高并发(百万 QPS) 。
🚨 缺点:
• 需要额外维护 MQ,适合超大规模场景。
最佳实践(混合方案)
在真实业务中,可以结合多个方案:
-
Redis SETNX 确保高并发下的唯一性(主方案)。
-
MySQL 乐观锁作为兜底方案(防止 Redis 崩溃)。
-
大规模并发时,结合 MQ 进行削峰(防止数据库或 Redis 过载)。
🚀 最终目标:在 200 个骑手同时抢单的情况下,确保只有一个骑手能成功接单!