【快刷面试-高并发锁篇】- 基于票务系统在不同服务器,分布式场景中该如何解决

【快刷面试-高并发锁篇】- 基于票务系统在不同服务器,分布式场景中该如何解决

分布式场景下的锁解决方案

票务系统在多个服务器部署时,单机锁(如Java的synchronizedReentrantLock)失效的原因很简单:这些锁只在单个JVM进程内有效 ,无法跨服务器协调。三台服务器同时读取i=100并各自执行i--,最终都写入i=99,导致超卖。


分布式锁的核心要求

实现分布式锁必须满足四个条件(互防可高):

  1. 互斥性:同一时刻只有一个客户端能持有锁
  2. 防死锁:锁必须有自动超时释放机制,防止客户端崩溃导致永久阻塞
  3. 可重入性(可选):同一个客户端可多次获取同一把锁
  4. 高可用:锁服务不能是单点故障源

主流解决方案对比
方案 实现原理 优点 缺点 适用场景
数据库悲观锁 SELECT ... FOR UPDATE 简单,无需额外组件 性能差,易死锁,无法优雅处理超时 并发量极低(<10 TPS)
Redis分布式锁 SET NX PX + Lua脚本 性能极高(万级TPS),实现简洁 过期时间难评估,主从切换可能丢锁 绝大多数互联网业务
Redisson框架 Redis + WatchDog机制 自动续期,支持可重入,有成熟框架 依赖Redis集群稳定性 强烈推荐
ZooKeeper临时节点 创建临时顺序节点 可靠性高,自动释放,无死锁 性能较低(千级TPS),需维护ZK集群 金融级强一致场景
Etcd租约机制 Lease + Revision 强一致性,TTL自动过期 运维复杂,社区较小 Kubernetes生态

实战推荐:Redisson分布式锁

对于票务系统这类高并发、性能敏感 的场景,Redisson是最佳实践

java 复制代码
// 1. 引入依赖
// Maven: <artifactId>redisson-spring-boot-starter</artifactId>

// 2. 配置Redis集群
@Configuration
public class RedissonConfig {
    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        config.useClusterServers()
            .addNodeAddress("redis://127.0.0.1:7001", "redis://127.0.0.1:7002");
        return Redisson.create(config);
    }
}

// 3. 业务代码改造
@Service
public class TicketService {
    @Autowired
    private RedissonClient redissonClient;
    
    public boolean sellTicket(Long ticketId) {
        // 锁的粒度要细,用ticketId作Key
        RLock lock = redissonClient.getLock("ticket_lock:" + ticketId);
        
        try {
            // 尝试加锁,最多等待5秒,锁自动释放30秒
            // WatchDog会自动续期(默认每10秒续一次)
            boolean isLocked = lock.tryLock(5, 30, TimeUnit.SECONDS);
            if (!isLocked) {
                throw new RuntimeException("系统繁忙,请重试");
            }
            
            // 执行业务:查询库存 → 减库存
            Ticket ticket = ticketMapper.selectById(ticketId);
            if (ticket.getStock() > 0) {
                ticket.setStock(ticket.getStock() - 1);
                ticketMapper.updateById(ticket);
                return true;
            }
            return false;
            
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return false;
        } finally {
            // 必须释放锁,且只能释放自己持有的锁
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
}

关键设计要点
  1. 锁粒度 :按ticketId加锁,避免全局锁阻塞所有票种
  2. Key命名业务:模块:资源ID,如ticket:stock:12345
  3. 防误删:Redisson的锁实例会存储线程ID,防止A线程释放B线程的锁
  4. 超时时间:必须设置,且要远大于业务执行时间(WatchDog会续期)
  5. 锁重试tryLock支持等待时间,避免立即失败

为什么不能只用数据库?
sql 复制代码
-- 悲观锁方案(不推荐)
BEGIN;
SELECT stock FROM ticket WHERE id = 123 FOR UPDATE;  -- 阻塞其他事务
UPDATE ticket SET stock = stock - 1 WHERE id = 123;
COMMIT;

问题 :性能极差,TPS通常<50,且长事务会拖垮数据库。Redis方案性能是其100倍以上


架构演进建议

如果你们的系统已经用MySQL,最小成本接入Redisson

  1. 部署一个Redis集群(3主3从)
  2. 引入redisson-spring-boot-starter
  3. 改造核心库存接口

后续如果库存争抢极度激烈(如春运抢票),可升级为分段锁消息队列异步扣库存,但90%的场景下Redisson分布式锁已完全够用。

总结:分布式锁的本质是把多机并发转为单机排队,Redis+Redisson是目前工业界最成熟的方案,没有之一。

【详解-锁的4大条件】

我用春运抢票这一个完整故事,把四个条件串起来给你讲透:


故事背景

2025年春运,10万用户抢最后1000张火车票,系统部署在5台服务器上,库存服务共用MySQL。


条件1:互斥性(同一时间只有1个人能抢到最后票)

场景:用户张三和李四同时看到"最后1张票"。

  • 无锁灾难 :两台服务器同时执行SELECT stock=1 → 都判断有票 → 都执行stock-1 → 库存变成-1,超卖1张,两人付款成功,系统崩溃。
  • 分布式锁介入 :Redisson在Redis中创建ticket_lock:123键,只有一台服务器能SET成功,另一台等待。张三的请求先拿到锁,李四的请求阻塞等待。

口诀:锁就是"独木桥",一次只能过一人。


条件2:防死锁(服务器跪了,锁必须自动释放)

场景 :服务器A拿到锁后,刚要扣减库存,突然JVM OOM宕机了。

  • 死锁灾难 :锁ticket_lock:123永远留在Redis,其他服务器都以为"有人持锁",全部阻塞。票卖不出,系统卡死。
  • Redisson方案 :加锁时设置30秒自动过期 + WatchDog每10秒续期。服务器A宕机后,WatchDog线程一起死,30秒后Redis自动删除锁,其他服务器恢复正常抢票。

口诀:锁带"定时炸弹",持有人死了就炸开锁。


条件3:可重入性(同一个用户多次操作同一资源)

场景 :张三付款成功后,系统要更新订单状态 + 发送短信通知,这两个方法都需要确认库存已扣减。

java 复制代码
// 伪代码展示嵌套调用
public void processPayment(Long ticketId) {
    lock.lock(); // 第一次获取锁
    try {
        deductStock(ticketId);      // 扣库存
        updateOrderStatus(orderId); // 内部也需要锁
        sendSmsNotification();      // 内部也需要锁
    } finally {
        lock.unlock(); // 释放锁
    }
}
  • 不可重入灾难updateOrderStatus内部再次尝试获取同一把锁,发现自己被"自己"阻塞,永远卡死。
  • Redisson方案 :锁记录线程ID重入次数 。同线程可重复获取,每次unlock()计数减1,计数到0才真正释放。

口诀:自己家的钥匙,可以反复开门,出门时关几次门才算真正锁上。


条件4:高可用(锁服务不能是单机)

场景 :春运高峰期,Redis主节点扛不住10万并发,突然挂了

  • 单点故障灾难:所有锁请求失败,抢票系统全面瘫痪,客服电话被打爆。
  • Redisson方案 :部署3主3从Redis Cluster ,锁数据分片存储。主节点挂掉,从节点自动提升,客户端无感知切换。配合哨兵模式,故障恢复<10秒。

口诀:锁服务要像"双11客服",一个倒下,另一个立刻顶上。


四条件串联记忆模板(快刷用)

复制代码
故事线:春运抢票 → 服务器宕机 → 嵌套调用 → Redis集群故障

互斥性:10万人抢1张票,锁保证只有1人成功
防死锁:服务器A拿到锁后OOM,30秒自动释放
可重入性:张三付款流程内,订单/短信方法复用同一把锁
高可用:Redis主节点挂了,从节点秒级接管

技术映射:
互斥 → SET NX原子命令
防死锁 → PX过期时间 + WatchDog续期
可重入 → Hash结构记录threadId + count
高可用 → Redis Cluster / Sentinel

一句话总结 :分布式锁就是给"跨服务器的临界资源"配一个带自动解锁、可重复进入、集群部署的"智能门卫"

这个串联故事你可以直接用,面试时讲出来比背八股文生动10倍。

简单购票示例一句话说清楚

一句话版本:

分布式锁确保"最后1张票只能被1人成功购买,若买家服务器崩溃锁30秒自动释放防卡死,同买家付款流程可重复获取锁,锁服务集群部署避免单机故障导致系统瘫痪"。


极简关键词版:

分布式锁通过 互斥抢票、崩溃自愈、同用户可重入、集群高可用 四大机制,确保10万人抢最后1张票时绝不超卖。

【面试版本】

面试高分回答模板(总分总结构)

总起 :分布式锁的本质是解决多机环境下临界资源竞争,以春运抢票系统为例,分布式锁必须满足四个条件才能保障业务正确性:


1. 互斥性( Mutual Exclusion )

定义 :同一时刻,集群中仅有一个节点能持有锁操作库存。

场景 :10万用户抢最后1张票,5台服务器同时读到stock=1。若无互斥,每台都会执行stock-1,导致库存超卖为-4

实现 :Redis的SET NX原子命令,只有唯一客户端能创建ticket_lock:123键。Redisson通过Lua脚本保证"判断存在+设置值+设置TTL"的原子性,避免SET+EXPIRE非原子导致的死锁风险。

细节 :锁的粒度必须精确到资源IDticket_lock:${ticketId}),而非全局锁,否则所有车次串行化,性能崩溃。


2. 防死锁( Deadlock-Free )

定义 :持有锁的节点崩溃时,锁必须自动释放,防止其他节点永久阻塞。

场景:服务器A获取锁后,JVM OOM宕机,锁未释放→所有服务器等待→系统卡死,1000张票无法售卖。

实现 :Redisson采用WatchDog机制:锁默认30秒过期,客户端启动后台线程每10秒续期。若客户端宕机,续期停止,30秒后Redis自动删锁。

对比 :ZooKeeper用临时节点,会话断开自动删除,可靠性更高但性能较差。数据库悲观锁无自动超时,需额外心跳表,实现复杂且低效。


3. 可重入性( Reentrancy )

定义 :同一客户端的同一线程可多次获取已持有的锁,避免自死锁。

场景 :用户下订单时,sellTicket()调用链嵌套updateOrder()sendSms(),三者均需校验库存。若不可重入,第二次lock()会阻塞自己。

实现 :Redisson使用Hash结构 存储threadIdcount。同线程重复获取时count++,每次unlock()count--,计数归零才真正删除锁。

价值:提升业务封装灵活性,避免方法调用链因锁问题耦合。


4. 高可用( High Availability )

定义 :锁服务不能是单点,必须支持故障自动转移

场景:Redis单机部署,主节点宕机→所有锁请求失败→春运高峰期系统瘫痪,引发P0级故障。

实现 :Redis Cluster集群(3主3从)+ Sentinel哨兵。锁数据分片存储,主节点故障时从节点秒级提升,Redisson客户端自动切换连接。

权衡 :ZooKeeper通过ZAB协议保证强一致,但写入性能仅为Redis的1/10 。Redis主从异步复制可能丢锁(主宕机从未同步),需根据业务容忍度选型:超卖零容忍 用ZK,性能优先用Redis。


总结升华

四个条件环环相扣:互斥性是目标,防死锁是兜底,可重入是工程友好,高可用是底线 。实际选型中,Redisson + Redis Cluster 是互联网业务标配,兼顾性能与可用性;金融场景可接受性能损耗则选ZooKeeper。回答时可主动提及Redlock算法争议(Redis官网已不推荐),体现技术视野深度。

一句话收尾 :分布式锁的本质是将"多机并行"转为"单机串行",而这四个条件是保证该转换正确、可靠、高效的基石。

相关推荐
A尘埃2 小时前
PyTorch的分布式训练策略:DDP + DeepSpeed + TensorFlow的分布式训练策略:MirroredStrategy
pytorch·分布式·tensorflow
草莓熊Lotso2 小时前
Makefile 完全指南:从入门到工程化,自动化构建不再难
linux·运维·服务器·人工智能·经验分享·后端·自动化
lhrimperial2 小时前
Kafka核心技术深度解析
分布式·kafka·linq
想学后端的前端工程师2 小时前
【Redis实战与高可用架构设计:从缓存到分布式锁的完整解决方案】
redis·分布式·缓存
吴佳浩2 小时前
Go 1.25.5 通关讲解
后端·面试·go
一水鉴天10 小时前
整体设计 定稿 之9 最后收束 app.py: 应用项目的结构及其模型和框架 (豆包助手)
服务器·windows·microsoft
wanhengidc11 小时前
云手机的适配性怎么样?
运维·服务器·安全·智能手机·云计算
梁辰兴11 小时前
计算机网络基础:使用集线器的星型拓扑
服务器·网络·计算机网络·集线器·计算机网络基础·梁辰兴·星型拓扑
jimy111 小时前
安卓里运行Linux
linux·运维·服务器