高并发抢单逻辑

高并发流量拦截,数据库落库,性能抗压;
10个请求同事争抢数据库的同一行数据锁,大量请求阻塞排队,不仅瞬时打满数据库连接池,还会造成频的线程的上下文切换,极大消耗了服务器的性能,这时候Redis就派上用场,它不仅仅速度快,它解决抢单这种冲突是王牌,因为redis是单线程,串行执行模型,10个司机在抢单请求,在外网层面,是并行涌入的,但是全部打入到Redis后,所有请求就会强制排队,串行执行,不存在并发抢占的冲突,我们提前为当前的订单在Redis中设置一个唯一抢单令牌,利用redis的原始命令进行抢占判定最先到达的请求成功抢占到令牌,直接判定抢单成功,其余9名后续排队请求,查询令牌已经被占用了,直接快速返回抢单失败,全程没有锁竞争内耗,没有线程切换开销,1秒就能完成争抢判定,轻松顶住海量并发请求。
消息队列,异步解耦的思想,只要Redis层面判定司机抢单成功,系统立刻响应用户弹窗接单成功,提升了用户体验。而修改订单状态,绑定司机信息,还有像更新订单数据,这些耗时的数据库操作,全部封装成消息投递到MQ中,由后台消费程序,异步平缓执行入库操作,让前端响应极致流畅,让数据库从容处理数据落地,前后端各司其职。系统稳定拉满。

AI总结:
作为架构师,我会把这道题答成:高并发抢单不是“Redis 快所以用 Redis”,而是用 Redis 做原子预占、把冲突拦截在 DB 之前,再用可靠 MQ 异步落库,最后用 DB 唯一约束、幂等、对账、补偿兜底一致性。

核心一句话:

入口限流 → Redis 原子抢占 → 快速失败 → 可靠 MQ 削峰 → 异步落库 → DB 唯一约束/乐观锁 → 幂等消费 → 对账补偿 → 监控降级。

Redis 解决的是“高并发冲突判定”和“数据库行锁阻塞”,不是替代数据库;MQ 解决的是“异步解耦和削峰”,不是绕过一致性。


一、先纠偏原答案

  1. “Redis 单线程”要准确说
    Redis 6.0 之前命令执行单线程;6.0 之后网络 IO 多线程,但命令执行仍是单线程。Lua 脚本原子执行。所以多个抢单命令到 Redis 后是串行处理,但不是说客户端没有网络等待。

  2. “没有锁竞争”不准确
    Redis 内部也是串行处理,只是没有 DB 行锁等待和大量线程上下文切换。准确说:把数据库行锁竞争前移为 Redis 原子命令竞争,失败快速返回。

  3. “1 秒完成”不准确
    Redis 单命令亚毫秒级,加上网络 RTT,通常几毫秒到几十毫秒。说“毫秒级”更专业。

  4. “MQ 异步落库就完事”不准确
    Redis 抢单成功、MQ 发送成功、DB 落库失败,仍可能不一致。必须做:可靠消息、消费幂等、重试、死信、对账、补偿。核心订单不能只靠“发个 MQ”就返回成功。

  5. “修改订单状态、绑定司机全部异步”要分场景
    如果业务允许“抢单成功,确认中”,可以异步。如果必须强一致,至少要同步写可靠消息表或事务消息,不能裸发 MQ。


二、2 分钟面试逐字稿

这个场景本质是同一订单或同一库存行被大量请求争抢,如果直接打 DB,会行锁排队、连接池打满、线程上下文切换,最后雪崩。

我的方案分五层。

第一,入口拦截。网关限流、防重、黑名单、验证码、Sentinel 限流,先把无效和重复流量挡掉。

第二,Redis 原子抢占。为订单生成 key,比如 order:grab:{orderId},用 SET key driverId NX PX ttl,或者 Lua 脚本原子判断“是否已被抢、司机是否重复、司机接单上限”,成功写入占位,失败立即返回。这样第一个请求成功,其余快速失败,不进入 DB。

第三,快速响应。Redis 判定成功后,立即返回用户“抢单成功”或“确认中”。同时发送可靠 MQ 消息,消息体带 orderId、driverId、requestId、traceId。

第四,异步落库。消费者更新订单状态、绑定司机、写流水。DB 层用唯一索引和乐观锁兜底,比如 update orders set driver_id=?, status='ACCEPTED' where id=? and status='WAITING',影响行数为 1 才算成功。消费端必须幂等,用消息 ID 去重、Redis SETNX 或 DB 唯一索引。

第五,可靠性和补偿。MQ 用 RocketMQ 事务消息或本地消息表/Outbox,保证 Redis 成功后消息不丢。消费失败重试、死信、对账、补偿。如果最终落库失败,要回滚 Redis 占位、通知用户或转人工。监控 Redis 延迟、MQ 积压、DB 连接池、锁等待、重复派单率。

总结:Redis 负责高并发原子抢占,MQ 负责削峰异步落库,DB 负责最终一致和唯一约束,监控补偿兜底。这样既抗并发,又不丢数据。


三、完整链路

用户请求
  ↓
网关限流 / 防重 / 鉴权
  ↓
Redis 原子抢占:SET NX PX 或 Lua
  ├─ 失败:快速返回“已被抢”
  └─ 成功:写占位,发可靠 MQ
        ↓
     立即响应“抢单成功/确认中”
        ↓
MQ 削峰
        ↓
消费者异步落库
        ↓
DB 唯一索引 + 乐观锁 + 短事务
        ↓
成功:更新订单、绑定司机、写流水
失败:重试、死信、对账、补偿、回滚 Redis

四、Java 落地要点

1. Redis 原子抢占

简单版:

Boolean ok = redisTemplate.opsForValue()
    .setIfAbsent("order:grab:" + orderId, driverId, Duration.ofSeconds(30));
if (Boolean.TRUE.equals(ok)) {
    // 抢到,发可靠 MQ
} else {
    // 快速失败
}

复杂版用 Lua:

-- KEYS[1] = order:grab:{orderId}
-- ARGV[1] = driverId
-- ARGV[2] = ttl ms
if redis.call('exists', KEYS[1]) == 1 then
    return 0
end
redis.call('set', KEYS[1], ARGV[1], 'PX', ARGV[2])
return 1

注意:

  • Redis 集群下多 key 要用 hash tag {orderId} 保证同 slot;
  • TTL 防止死锁;
  • 误删要用 Lua 校验 value;
  • 司机维度去重:driver:grabbing:{driverId}。

2. 可靠 MQ

  • RocketMQ 事务消息;
  • 本地消息表 / Outbox;
  • 先写消息表,再发 MQ,定时补偿;
  • 消息带唯一 requestId;
  • 消费端幂等:Redis SETNX、DB 唯一索引、去重表。

3. DB 落库

update orders
set driver_id = ?, status = 'ACCEPTED', update_time = now()
where id = ? and status = 'WAITING';
  • 影响行数 1 成功,0 失败;
  • 唯一索引:uk_order_driver(order_id) 防一单多司机;
  • 司机接单上限用 Redis 计数或 DB 约束;
  • 短事务,禁止事务内远程调用。

4. Java 并发与连接池

  • HikariCP 控制连接池,不要盲目加大;
  • 线程池隔离,抢单接口独立线程池;
  • Sentinel 限流降级;
  • Micrometer + Prometheus + Grafana 监控。

5. 降级预案

  • Redis 挂了:降级 DB 乐观锁 + 强限流 + 排队;
  • MQ 积压:扩容消费者、限流生产、降级非核心;
  • DB 压力大:读写分离、分库分表、异步落库。

五、面试官可能追问

Q1:为什么不用 DB 行锁直接抢?

行锁会让大量请求阻塞,占连接、占线程,上下文切换高,容易打满连接池。Redis 原子抢占把冲突前移,失败快速返回。

Q2:Redis 成功,DB 失败怎么办?

可靠消息 + 重试 + 死信 + 对账 + 补偿。最终失败要回滚 Redis 占位、通知用户。核心是消息不能丢,消费必须幂等。

Q3:消息重复消费怎么办?

消费端幂等:消息 ID 去重、Redis SETNX、DB 唯一索引、乐观锁。重复消息直接返回成功。

Q4:怎么防一单多司机?

Redis Lua 原子占位 + DB 唯一索引 uk_order_driver(order_id) + 乐观锁更新。

Q5:Redis 热 key 怎么办?

抢单单订单 key 量有限;秒杀库存热 key 可分片 stock:{skuId}:{shard},本地缓存,读写分离。

Q6:Redis 挂了怎么办?

降级 DB 乐观锁 + 强限流 + 排队;或返回稍后重试。不能把 DB 直接暴露给全量流量。

Q7:双写能不能保证一致?

业务层双写不能保证原子性。用事务消息、本地消息表、CDC、对账补偿,接受最终一致。


六、收尾话术

所以高并发抢单的核心是:入口限流,Redis 原子抢占,失败快速返回,成功发可靠 MQ,异步落库,DB 唯一约束和乐观锁兜底,消费幂等,对账补偿,监控降级。Redis 负责抗并发,MQ 负责削峰,DB 负责最终一致。这样既能顶住海量流量,又能保证订单不丢、不重、不超卖。

posted @ 2026-06-12 17:41  堭鍙銤  阅读(37)  评论(0)    收藏  举报