酒店多房间预订重叠问题:MERN应用后端架构优化咨询
酒店预订系统并发预订问题及解决方案咨询
我正在开发一款基于MERN栈的酒店预订应用,用户可预订指定日期区间的酒店房间,数据库表结构如下:
Table Hotel { id: bigint, numberOfRooms: int, } Table Room { id: bigint, hotelId: bigint, } Table Booking { id: bigint, hotelId: bigint, roomId: bigint, checkIn: Date, checkOut: Date }
我编写的JavaScript预订逻辑代码如下:
async function bookRoomInHotel(hotelId, from_date, to_date) { const availableRooms = await findAvailableRooms(hotelId, from_date, to_date); if (!availableRooms) { throw new Error('No room available'); } const bookRoom = availableRooms[0]; const booking = await createBooking(bookRoom._id, from_date, to_date, hotelId); return booking; }
由于JavaScript异步特性,当两名用户同时预订重叠日期区间时,可能出现同一房间被重复预订的情况,甚至酒店的已确认预订数超过可用房间数。
我调研了几种解决方案,但均存在缺陷:
- 使用bull queue逐个处理预订请求:不同酒店的预订请求会被延迟,用户量大时任务队列过载。
- 对Hotel行加锁:死锁概率高。
- 获取酒店所有可用房间并逐个尝试预订,同时在Booking表添加避免日期重叠的约束:无法保证先到先得,数据库响应顺序可能打乱,且可能出现死锁。
- 使用分布式锁:无法保证先到先得,锁被占用时请求会被拒绝,等待的客户端需不断重试。
请问如何设计后端,才能实现接近Airbnb等平台的可靠预订系统?
解决方案设计
要实现可靠的酒店预订系统,核心是解决并发下房间资源的原子性分配问题,同时兼顾性能和用户体验,以下是分层次的落地方案:
一、数据库层:强化约束+细粒度锁
添加Booking表的时间重叠约束
在Booking表上创建约束,强制同一房间的预订时间区间不能重叠,从数据库层面杜绝重复预订:-- 以PostgreSQL为例,添加排除约束 ALTER TABLE Booking ADD CONSTRAINT no_overlapping_bookings EXCLUDE USING gist (roomId WITH =, tstzrange(checkIn, checkOut) WITH &&);若使用MongoDB,可通过唯一复合索引+自定义逻辑实现类似校验。
行级悲观锁锁定目标房间
修改findAvailableRooms查询逻辑,在获取可用房间时直接对房间行加排他锁,避免其他事务读取或修改:async function findAvailableRooms(hotelId, from_date, to_date, session) { // MongoDB示例:使用会话加悲观写锁 const bookedRoomIds = await Booking.distinct('roomId', { hotelId, checkIn: { $lt: to_date }, checkOut: { $gt: from_date } }); return Room.findOne({ hotelId, _id: { $nin: bookedRoomIds } }).session(session).lock('pessimistic_write'); }锁粒度控制在单个房间而非整个酒店,不同房间的预订请求互不阻塞,大幅降低死锁概率。
二、业务逻辑层:两步式预订流程
参考主流平台模式,拆分预订为临时预留+最终确认:
- 临时预留:用户选择日期后,系统为其锁定目标房间(比如15分钟),生成状态为
pending的Booking记录,并设置过期时间。此时该房间仅对当前用户开放。 - 最终确认:用户完成支付等操作后,将
pending状态改为confirmed;若超时未确认,自动触发房间释放逻辑(可通过定时任务或TTL索引实现)。
这种设计既避免了长事务占用锁,又提升了用户体验,同时减少并发冲突的触发概率。
三、并发控制层:酒店粒度的局部队列
针对同一酒店的预订请求,采用酒店专属队列而非全局队列:
- 每个酒店维护独立的处理队列,同一酒店的确认请求串行处理,不同酒店的请求并行执行,既保证了同一酒店的预订顺序,又不会阻塞其他酒店的请求。
- 队列仅处理确认阶段的请求,预留阶段无需排队,不影响用户的初始操作流畅度。
四、异常处理与重试机制
- 冲突自动重试:当出现数据库约束冲突时,自动重试2-3次,重试间隔采用指数退避策略(比如100ms、200ms、400ms),降低偶发冲突导致的失败概率。
- 用户友好提示:重试失败后,告知用户“当前房间紧张,请稍后再试”,或主动推荐同酒店其他可用房间,而非直接抛出错误。
五、性能优化
- 缓存可用房间数:用Redis缓存酒店的实时可用房间数,减少数据库查询压力。当预订状态变更(预留/确认/释放)时,同步更新缓存值。
- 异步处理非核心逻辑:发送预订确认邮件、更新统计数据等非核心操作,放入消息队列异步处理,不阻塞主预订流程。
内容的提问来源于stack exchange,提问作者noob_coder
相关产品推荐
相关产品推荐

