微服务架构下电影订票系统临时锁座及超时释放方案咨询
电影订票系统座位临时锁定实现与自动释放方案
一、座位临时锁定的核心实现逻辑
这类BMS订票系统的临时锁本质是为了在支付等待窗口内避免同个座位被多用户重复选中,从根源防范超卖,基础流程如下:
- 用户提交选座请求时,订票专用API首先做两层校验:一是校验对应场次的座位基础状态,二是校验是否存在有效临时锁/已售记录。校验通过后通过原子操作写入锁记录:标记座位为「临时锁定」状态,同时绑定锁定用户ID、对应场次ID、精确到毫秒的锁过期时间戳(当前时间+120秒)三个核心字段。
- 锁写入成功后才会引导用户进入支付环节:如果支付成功,直接把座位状态更新为「已售出」,绑定对应订单号,清除临时锁标记;如果支付失败、用户主动关闭支付页面,不会立刻触发锁释放,等锁到期后统一处理。
- 所有新的选座请求查询座位状态时会加一层业务兜底判断:如果座位处于临时锁定状态,但当前时间已经超过绑定的过期时间戳,直接将其判定为可占用的空闲座位,不会因为锁释放延迟导致座位无法被选。
二、替代固定频率批量作业的自动释放方案
固定频率跑批的方案天然存在调度间隔带来的延迟,比如每1分钟跑一次批,最坏情况下座位要在锁到期后近1分钟才会被释放,根本卡不住2分钟的时效要求,生产环境常用的可行方案有以下几种:
1. 存储层原生TTL过期方案
这是落地成本最低的方案:
- 用Redis这类带原生过期机制的KV存储存临时锁数据,加锁时直接通过原子命令
SET lock:场次ID:座位ID 用户ID NX EX 120写入,自动给锁Key设置2分钟的过期时间,到点Redis会自动删除过期的锁Key。 - 业务层查询座位状态时,只要Redis中查不到对应锁Key,就判定座位为可售状态;同时配合持久化存储(比如MySQL)中存的锁过期时间戳做二次校验,完全不会出现超期占用问题。
- 注意不要完全依赖Redis的主动过期通知:Redis采用惰性删除+定期删除的策略,少量过期Key可能不会被立刻删除,业务层的前置校验是兜底,两层配合下锁释放的延迟可以控制在毫秒级。
2. 延迟队列方案
- 用户选座成功生成临时锁时,同步往延迟队列投递一条延迟时长为2分钟的消息,消息体携带场次ID、座位ID、锁记录唯一标识。
- 2分钟到期后延迟队列将消息投递给消费端,消费端拿到消息先做状态校验:如果对应座位仍处于临时锁定状态、且没有绑定成功支付的订单,就执行释放逻辑将座位改回空闲状态;如果座位已经售出、或者已经被释放,直接跳过消息不做处理。
- 实现方式非常灵活:用Redis的话可以基于
ZSET结构实现,把过期时间戳存为score,起一个轻量轮询协程不断拉取score小于当前时间戳的记录处理即可;如果技术栈已经在用消息队列,RabbitMQ的死信队列、RocketMQ的原生延迟消息都可以直接支撑,延迟误差能控制在秒级以内。
3. 时间轮本地调度方案
- 如果是中小体量的订票服务,可以直接用时间轮算法做本地任务调度:生成临时锁的时候,把锁释放的校验任务挂到时间轮对应2分钟后的时间槽上,到点自动触发任务执行。
- 这个方案没有网络开销,延迟最低,但要注意多实例部署、服务重启时本地任务可能丢失,必须配合前面提到的业务层过期时间校验做兜底,避免服务宕机导致锁永久占用。
内容的提问来源于stack exchange,提问作者Gunjit
相关产品推荐
相关产品推荐

