酒店预订系统5分钟房间保留功能实现方案及Redis锁疑问咨询
酒店房间5分钟锁定的系统设计方案探讨
问题背景
面试中遇到的系统设计问题:如何为用户保留房间5分钟?
库存数据结构
| Hotel-id | room_type_id | date | total_inventory | total_reserved |
|---|---|---|---|---|
| 123 | 001 | 2023-09-30 | 100 | 99 |
| 123 | 001 | 2023-10-01 | 100 | 96 |
业务流程
用户进入预订详情页后,Reservation Service先从缓存查询库存,随后需要锁定机制为用户保留房间5分钟。以2023-09-30为例,剩余1间可用房:
- 用户A操作时,可继续完成预订;
- 若此时用户B尝试操作,系统需识别该房型当日已被锁定,提示“该房间不可用”。
我的疑问
- 是否应采用Redis锁管理实现该功能,还是有其他方案?
- 锁定机制具体如何运作?实际锁定的是什么?理想情况下不应锁定整行数据,因为当
total_inventory - total_reserved > 0时,其他用户仍应可预订房间,对吗?
我的初步猜想
曾提出用Redis锁锁定房间并设置5分钟TTL自动释放,但无法清晰解释步骤,具体猜想如下:
- 在Redis中为每个库存条目添加
on-hold字段,示例:
key = 123_001_2023_09_30 # 格式:hotel_id_room_type_id_date value = { total_inventory: 100, total_reserved: 99, on_hold: 1} TTL = 5分钟
- 其他用户尝试预订时,先检查
total_reserved + on_hold >= total_inventory,若为真则阻止操作; - 5分钟到期后,Redis自动将
on_hold值减1(不确定是否可行)
专业解答
1. 技术方案选择:Redis是最优解,也可搭配数据库方案
- Redis方案:适合高并发场景,能快速响应库存查询与锁定操作,天然支持TTL自动释放,是这类临时锁定需求的首选。
- 备选方案:
- 数据库乐观锁:通过版本号或
CAS(Compare And Swap)操作控制,但高并发下冲突率高,性能不如Redis; - 数据库悲观锁:直接锁定整行数据,会导致并发能力骤降,不符合“剩余库存>0时其他用户仍可预订”的需求,不推荐。
- 数据库乐观锁:通过版本号或
2. 锁定机制的核心逻辑:锁定单份可预订库存额度,而非整行数据
你的理解是对的——不能锁定整行数据,否则会浪费剩余库存的预订能力。实际锁定的是当前用户申请的那1间(或N间)可预订房间额度,核心流程如下:
完整Redis实现步骤
- 初始化缓存库存:将数据库中的库存数据同步到Redis,结构采用哈希表(Hash)存储,每个
hotel_id_room_type_id_date作为哈希键,字段包括total_inventory、total_reserved、on_hold(初始为0)。 - 用户申请锁定:
- 用Redis Lua脚本执行原子操作(避免并发竞争):
-- 计算可用库存 = total_inventory - total_reserved - on_hold local available = tonumber(redis.call('HGET', KEYS[1], 'total_inventory')) - tonumber(redis.call('HGET', KEYS[1], 'total_reserved')) - tonumber(redis.call('HGET', KEYS[1], 'on_hold')) if available >= 1 then -- 可用库存足够,增加on_hold计数 redis.call('HINCRBY', KEYS[1], 'on_hold', 1) -- 设置键的TTL为5分钟(若键已存在则刷新TTL) redis.call('EXPIRE', KEYS[1], 300) return 1 -- 锁定成功 else return 0 -- 锁定失败 end - 若脚本返回1,说明锁定成功,允许用户进入后续预订流程;返回0则提示“房间不可用”。
- 用Redis Lua脚本执行原子操作(避免并发竞争):
- 锁定到期或预订完成后的清理:
- 预订完成:用户成功支付后,调用Lua脚本原子更新
total_reserved +=1,同时on_hold -=1; - 锁定到期:Redis的TTL到期后无法自动修改
on_hold值,需通过两种方式处理:- 开启Redis键空间通知,监听键过期事件,触发服务端逻辑将对应
on_hold减1; - 定时任务扫描Redis中所有库存键,清理过期的锁定额度(适合低并发场景)。
- 开启Redis键空间通知,监听键过期事件,触发服务端逻辑将对应
- 预订完成:用户成功支付后,调用Lua脚本原子更新
3. 对你的猜想的点评
你的思路方向是对的,但有一处关键问题:Redis的TTL到期后无法自动执行on_hold减1的操作,因为TTL仅负责删除键,没有内置的字段修改逻辑,需要通过上述的键空间通知或定时任务来实现锁定额度的回收。
另外,建议用Redis哈希表存储每个字段,而非序列化的对象,这样能更高效地单独修改on_hold、total_reserved等字段,避免序列化/反序列化的开销。
内容的提问来源于stack exchange,提问作者zzf
相关产品推荐
相关产品推荐

