云计算百科
云计算领域专业知识百科平台

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

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

请添加图片描述

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

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


分布式锁的核心要求

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

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

  • 主流解决方案对比
    方案实现原理优点缺点适用场景
    数据库悲观锁 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是最佳实践:

    // 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();
    }
    }
    }
    }


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

  • 为什么不能只用数据库?

    — 悲观锁方案(不推荐)
    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:

  • 部署一个Redis集群(3主3从)
  • 引入redisson-spring-boot-starter
  • 改造核心库存接口
  • 后续如果库存争抢极度激烈(如春运抢票),可升级为分段锁或消息队列异步扣库存,但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:可重入性(同一个用户多次操作同一资源)

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

    // 伪代码展示嵌套调用
    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非原子导致的死锁风险。

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


    2. 防死锁( Deadlock-Free )

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

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

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

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


    3. 可重入性( Reentrancy )

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

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

    实现:Redisson使用Hash结构存储threadId和count。同线程重复获取时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官网已不推荐),体现技术视野深度。

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

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 【快刷面试-高并发锁篇】- 基于票务系统在不同服务器,分布式场景中该如何解决
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!