如果一个外卖配送单子要发布,现在有200个骑手都想要接这一单,如何保证只有一个骑手接到单子?

150 阅读3分钟

在高并发场景下,200个骑手同时抢一个外卖单,要确保只有一个骑手成功接单,可以采用以下方案:

方案 1:基于 Redis 分布式锁(SETNX)

Redis 的 SETNX(SET if Not eXists)+ 过期时间可以确保只有一个骑手成功获取订单

实现步骤

  1. 所有骑手尝试抢单

• SET order_123 "rider_id" NX EX 5

• 只有第一个成功执行 SETNX(抢到锁)的骑手能接单。

• 失败的骑手说明订单已经被别人抢到,无法接单。

  1. 成功抢单的骑手

• 订单状态变更为 “已接单”。

• 通知骑手。

  1. 未成功的骑手

• 立即返回抢单失败,或者提示 “已被抢走”。

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 的行级锁,确保多个骑手同时抢单时,只有一个能成功。

实现步骤

  1. 订单表增加状态字段 status:

• 0:待接单

• 1:已被骑手接单

  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骑手排队抢单

实现步骤

  1. 骑手请求进入 MQ 队列

  2. 消费者进程顺序消费 MQ 中的请求,确保只处理第一个骑手的请求。

  3. 成功接单的骑手被通知,其他骑手请求被丢弃。

优势

消息顺序处理,避免竞争

适用于超高并发(百万 QPS)

🚨 缺点

需要额外维护 MQ,适合超大规模场景。

最佳实践(混合方案)

真实业务中,可以结合多个方案:

  1. Redis SETNX 确保高并发下的唯一性(主方案)。

  2. MySQL 乐观锁作为兜底方案(防止 Redis 崩溃)。

  3. 大规模并发时,结合 MQ 进行削峰(防止数据库或 Redis 过载)。

🚀 最终目标:在 200 个骑手同时抢单的情况下,确保只有一个骑手能成功接单