news 2026/1/21 22:13:46

仿 12306 售票系统:Spring Boot + Redisson 分布式锁解决“超卖”与“余票缓存一致性”难题

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
仿 12306 售票系统:Spring Boot + Redisson 分布式锁解决“超卖”与“余票缓存一致性”难题

😱 前言:一张票引发的血案

在单机应用中,防止超卖很简单,加个synchronized关键字就行。
但在微服务集群下,synchronized只能锁住当前机器的线程,锁不住由于负载均衡分发到其他机器的请求。

超卖场景还原:
库存剩 1 张。

  1. 服务器 A的线程读库存:stock = 1
  2. 服务器 B的线程同时读库存:stock = 1
  3. A 卖出:stock = 0
  4. B 卖出:stock = 0
    结果:卖出了 2 张票,实际上只有 1 张。这在铁路系统里意味着有人要站票或者上不了车,属于 P0 级事故。

我们需要一把**“分布式锁”**。


🔒 一、 为什么选 Redisson?

Redis 自带的setnx虽然能实现锁,但有巨大缺陷:

  1. 死锁风险:如果服务宕机,锁没释放怎么办?(需要加过期时间)
  2. 过期时间难定:业务执行了 10s,锁 5s 就过期了,导致锁失效(锁误删)。
  3. 不可重入:复杂的业务逻辑调用链无法多次拿锁。

Redisson是 Redis 的 Java 驻内存数据网格,它完美解决了上述问题,特别是它的**“看门狗(Watch Dog)”**机制。

Redisson 锁流程图 (Mermaid):

Redisson 内部机制

1. tryLock()

加锁成功

开启后台线程

每 10s 续期

业务结束

停止看门狗

加锁失败

客户端请求

Redis Master

执行业务逻辑

🐶 看门狗 (Watch Dog)

重置锁过期时间 (默认 30s)

unlock() 释放锁

自旋等待 / 放弃


💻 二、 实战:Redisson 锁住高铁票

1. 引入依赖
<dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.23.0</version></dependency>
2. 抢票核心逻辑 (OrderService)

我们不仅要加锁,还要锁得细粒度。如果锁整个G1024车次,那吞吐量太低。我们应该锁具体座次车厢

@ServicepublicclassTicketService{@AutowiredprivateRedissonClientredissonClient;@AutowiredprivateStringRedisTemplateredisTemplate;publicbooleanbuyTicket(StringtrainNumber,StringseatType){// 关键点:锁的粒度。这里锁住特定车次的特定席别StringlockKey="lock:ticket:"+trainNumber+":"+seatType;RLocklock=redissonClient.getLock(lockKey);try{// 1. 尝试获取锁// waitTime: 等待获取锁的时间,leaseTime: -1 表示开启看门狗自动续期booleanisLocked=lock.tryLock(5,-1,TimeUnit.SECONDS);if(isLocked){// 2. 双重检查 (Double Check) - 防止拿到锁之前库存被扣光// 这里不仅要查 Redis,最稳妥是查数据库或预加载的缓存intstock=getStockFromCache(trainNumber,seatType);if(stock>0){// 3. 扣减库存 (操作数据库 + 更新缓存)decreaseStock(trainNumber,seatType);createOrder();returntrue;}else{returnfalse;// 没票了}}else{returnfalse;// 系统繁忙 (获取锁失败)}}catch(InterruptedExceptione){returnfalse;}finally{// 4. 释放锁 (必须放在 finally 中)if(lock.isHeldByCurrentThread()){lock.unlock();}}}}

🔄 三、 难点攻克:余票缓存一致性

抢票时,用户疯狂刷新查看余票。如果每次都查数据库,数据库必死。我们必须查 Redis。
但这就引出了经典问题:数据库扣减了库存,Redis 里的缓存还没更新,怎么办?

在 12306 这种场景下,我们通常采用Cache-Aside Pattern (旁路缓存模式)的变种,并配合Lua 脚本

方案 A:先更库,再删缓存 (延时双删)

这是通用方案,但在极端高并发下依然有脏数据风险。

方案 B:Redis 预扣减 (12306 推荐)

真正的余票其实是以Redis 为准的。

  1. 初始化:将数据库库存预热到 Redis。
  2. 扣减:直接在 Redis 中扣减 (decr)。
  3. 异步同步:通过 MQ 异步将扣减结果同步回 MySQL,做最终持久化。

Redis Lua 脚本实现 (保证原子性):

-- keys[1]: 库存 key-- argv[1]: 扣减数量localstock=tonumber(redis.call('get',KEYS[1]))if(stock==nil)thenreturn-1endif(stock>=tonumber(ARGV[1]))thenredis.call('decrby',KEYS[1],tonumber(ARGV[1]))return1elsereturn0end

Java 调用:

// 这样就不需要 Redisson 锁住“读”操作,只需要锁住“写”操作// 或者完全依赖 Redis 单线程特性,连分布式锁都可以省去(针对纯扣减逻辑)Longresult=redisTemplate.execute(script,Collections.singletonList(key),"1");if(result==1){// Redis 扣减成功,发送 MQ 消息去异步更新 MySQLsendToMQ(orderInfo);}

🚀 四、 性能优化:分段锁 (Segment Lock)

如果 G1024 次列车只有一把锁,那么全中国想买这趟车的人都要排队。
我们可以借鉴ConcurrentHashMap的思想,将库存分段

假设二等座有 1000 张票:

  • Key1:stock:G1024:second:part1(0-100)
  • Key2:stock:G1024:second:part2(101-200)

用户请求进来时,随机路由到一个分段库存 Key 上。

  • 如果 Key1 有票,直接扣。
  • 如果 Key1 没票,尝试去 Key2 扣。

这样,并发度瞬间提升了 10 倍!


🎯 总结

开发一个简易版的 12306,核心就在于对“共享资源”(库存)的争抢控制。

  1. Redisson 看门狗:解决了锁过期导致的并发安全问题。
  2. Redis 预扣减 + MQ:解决了数据库的性能瓶颈和缓存一致性问题。
  3. 分段锁:解决了热点商品的单点瓶颈。

Next Step:
思考一下,12306 还有一个极其复杂的逻辑:区间票
比如北京 -> 上海的车,中间经停南京。如果我买北京 -> 南京,那么北京 -> 上海的全程票库存也要减 1。
这涉及到Bitmap (位图)技术。下一篇,我们挑战用 Redis Bitmap 实现区间库存管理!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/1/17 11:39:51

lora-scripts与模型压缩技术结合:进一步减小LoRA体积

lora-scripts与模型压缩技术结合&#xff1a;进一步减小LoRA体积 在AI模型日益庞大的今天&#xff0c;一个10亿参数的Stable Diffusion微调任务动辄需要上百GB显存、数天训练时间——这显然不是普通开发者或独立创作者能承受的代价。于是&#xff0c;人们开始寻找更聪明的办法&…

作者头像 李华
网站建设 2026/1/18 1:35:26

lora-scripts开源协议说明:可商用吗?需要署名吗?

LoRA 脚本工具的开源迷雾&#xff1a;能商用吗&#xff1f;要署名吗&#xff1f; 在 AI 创作门槛不断降低的今天&#xff0c;LoRA&#xff08;Low-Rank Adaptation&#xff09;已经成为无数开发者和内容创作者手中的“魔法棒”。它轻量、高效&#xff0c;能在不重训整个大模型的…

作者头像 李华
网站建设 2026/1/16 10:36:45

Ubuntu系统安装Miniconda完整指南

Ubuntu系统安装Miniconda完整指南 小白也能轻松上手的Miniconda安装教程 Miniconda是一个轻量级的Anaconda发行版&#xff0c;用于管理Python环境和包&#xff0c;特别适合在Ubuntu系统上进行科学计算和数据分析。本教程将详细介绍在Ubuntu系统安装Miniconda的完整方法&#x…

作者头像 李华
网站建设 2026/1/19 23:23:22

Web界面集成lora-scripts训练结果:打造可视化AI生成平台

Web界面集成lora-scripts训练结果&#xff1a;打造可视化AI生成平台 在如今这个“人人皆可创造内容”的时代&#xff0c;个性化AI生成模型正以前所未有的速度渗透进设计、艺术与商业领域。无论是想让AI画出自己笔下的漫画风格&#xff0c;还是训练一个懂公司内部术语的智能助手…

作者头像 李华
网站建设 2026/1/19 15:58:32

(C++ AIGC吞吐量优化黄金法则):实测提升300%的编译与运行时技巧

第一章&#xff1a;C AIGC 吞吐量测试概述 在现代高性能计算与人工智能生成内容&#xff08;AIGC&#xff09;融合的背景下&#xff0c;C 作为底层性能关键语言&#xff0c;广泛应用于推理引擎、图像生成和自然语言处理系统的实现中。吞吐量测试成为评估系统处理能力的核心指标…

作者头像 李华