一文读懂:高并发场景避免超卖少卖的实战攻略

嘿,兄弟们好,我是飞哥,临近过年没事,再来唠唠我做过的票务系统。

在票务这行,库存就是命脉。“超卖”(Over-selling)让你赔钱丢名声;“少卖”(Under-selling)让老板觉得你技术不行,票明明有却卖不出去。

今天飞哥就结合这几年在票务系统摸爬滚打的经验,跟大家好好唠唠这里面的深水区。

1. 为什么“超卖”和“少卖”是系统的生死劫?

很多兄弟初学并发,觉得写个 synchronized 或是 ReentrantLock 就能高枕无忧了。但在分布式架构下,这就像是用塑料袋去兜洪水。

  • 超卖: 就像 10 个人同时挤进一个窄门,大家看到货架上还有最后一张票,结果 10 个人都下单成功了。
  • 少卖: 又叫“库存空转”。用户抢了票占了座,结果不付钱。你把票锁死了,别人买不到,最后演出开始了,座位还空着,白白浪费钱。

2. 三个段位的防御战:从行锁到 Lua 脚本

咱们票务系统处理库存,通常会经历三个阶段。我做了个对比表,大家对号入座:

青铜

DB 行锁 (

UPDATE...WHERE stock > 0

)

绝对一致,简单粗暴

并发一高数据库直接宕机

内部员工购票、小场次

白银

分布式锁 (Redisson)

逻辑清晰,保护 DB

锁竞争剧烈,响应时间长

中等流量促销

黄金

Redis + Lua 脚本

原子操作,极高性能

逻辑略复杂,需考虑一致性

大促、万人抢票(首选)

3. 飞哥的看家本领:Redis + Lua 丝滑扣减

在抢票这种瞬时爆发场景,我们通常把库存预热到 Redis 里。

为什么一定要用 Lua?因为 Redis 执行 Lua 脚本是原子性的。它能保证“查询库存 -> 判断余量 -> 扣减库存”这三步,像德芙一样丝滑,中间不会被任何请求插队。

Java 核心逻辑参考:

// Lua 脚本:原子扣减
String luaScript = 
    "local stock = tonumber(redis.call('get', KEYS[1])) " +
    "if (stock > 0) then " +
        "redis.call('decr', KEYS[1]) " +
        "return 1 " + // 扣减成功
    "else " +
        "return 0 " + // 库存不足
    "end";

// 执行扣减
Long result = redisTemplate.execute(
    new DefaultRedisScript<>(luaScript, Long.class), 
    Collections.singletonList("show_101_stock")
);

if (result == 1) {
    // 抢到预扣名额,赶紧去异步创建订单
    sendOrderMessage(userId, showId);
} else {
    throw new BusinessException("票已售罄,下次早点来!");
}


4. 别让“占座不买票”拖垮你:延时回滚策略

超卖防住了,那“少卖”怎么办?票务系统最怕用户抢了票不付钱。

我们的标准打法是:“预扣库存 + 延迟检查”。请看这张流程图:

飞哥敲黑板: 回滚库存时一定要注意幂等性。别因为网络抖动回滚了两次,那库存就凭空变多了,成了“灵异事件”。

5. 飞哥的血泪复盘:缓存和 DB 的“信任游戏”

记得刚入行那会儿,我有次只做了 Redis 扣减,没做后台对账。结果 Redis 意外宕机,重启后虽然有持久化,但还是丢了几个计数。

那天晚上,DB 里的订单票数和 Redis 里的库存数对不上,差了十几张。别小看这十几张票,那是几十通投诉电话和客服小姐姐的眼泪。

反思:缓存只是冲锋队的盾牌,数据库才是最后的防线。 现在我们的系统都会跑一个异步对账程序,每隔几分钟对一次账。如果发现 Redis 里的数和 DB 差异过大,立马报警并人工介入。

最后

很多人觉得搞定高并发就是堆机器、用牛逼的中间件。

其实干了这么多年票务,飞哥最深的感触是:技术方案没有完美的,只有最合适的。 你能防住 99.99% 的异常,剩下的 0.01% 靠的是完善的监控和快速响应的“ plan B ”。

写代码时多想一步“要是挂了怎么办”,你的系统就能比别人稳一倍。

更新好文,可关注《码上实战》

#面试##面试时最害怕被问到的问题#
全部评论

相关推荐

02-09 16:14
武汉大学 Java
1.&nbsp;问一下本科经历2.&nbsp;介绍一下你第一个项目3.&nbsp;DDD分层架构比传统的MVC有哪些好处?4.&nbsp;你设计的业务分配的算法介绍一下?5.&nbsp;算法有哪些优化思路?6.&nbsp;动态标签列设计怎么思考的?7.&nbsp;数据量有多大?8.&nbsp;数据量很大的话,数据存储怎么优化?9.&nbsp;如何保证缓存和数据库之间的数据一致性?10.&nbsp;相对于你这个项目用哪种方案?11.&nbsp;项目中遇到的最大的困难是什么?12.&nbsp;介绍一下第二个项目13.&nbsp;模型分析diff的上下文怎么考虑?14.&nbsp;如果diff的关联的上下文很长超过token,你会怎么办?15.&nbsp;你想的这种方案,最后输入给模型的prompt是什么?16.&nbsp;对于大模型的其他组件如RAG和skills有了解吗?17.&nbsp;那你有想过把代码拆分成一些知识库放在rag里面吗?18.&nbsp;有对比过其他模型的分析效果吗?19.&nbsp;golang有了解吗?20.&nbsp;HashMap的底层结构21.&nbsp;为什么要用红黑树?22.&nbsp;红黑树增删的时间复杂度?23.&nbsp;MySQL事务隔离级别24.&nbsp;MVCC实现原理25.&nbsp;手撕算法:lc402&nbsp;移掉k位数字&nbsp;-&gt;&nbsp;没想到单调栈,暴力枚举了QAQ反问面试官之后,感觉我的缺点主要在于项目太过于玩具了,对于高并发什么的思考处于比较浅的地步,还有就是code-review对于call&nbsp;graph还有一些成熟的方案不怎么了解过,相当于纯demo,面过几场才知道QAQ,估计是没啥希望了,继续沉淀了噶人们
查看25道真题和解析
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务