You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

酒店多房间预订重叠问题: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等平台的可靠预订系统?


解决方案设计

要实现可靠的酒店预订系统,核心是解决并发下房间资源的原子性分配问题,同时兼顾性能和用户体验,以下是分层次的落地方案:

一、数据库层:强化约束+细粒度锁

  1. 添加Booking表的时间重叠约束
    在Booking表上创建约束,强制同一房间的预订时间区间不能重叠,从数据库层面杜绝重复预订:

    -- 以PostgreSQL为例,添加排除约束
    ALTER TABLE Booking ADD CONSTRAINT no_overlapping_bookings
    EXCLUDE USING gist (roomId WITH =, tstzrange(checkIn, checkOut) WITH &&);
    

    若使用MongoDB,可通过唯一复合索引+自定义逻辑实现类似校验。

  2. 行级悲观锁锁定目标房间
    修改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');
    }
    

    锁粒度控制在单个房间而非整个酒店,不同房间的预订请求互不阻塞,大幅降低死锁概率。

二、业务逻辑层:两步式预订流程

参考主流平台模式,拆分预订为临时预留+最终确认:

  1. 临时预留:用户选择日期后,系统为其锁定目标房间(比如15分钟),生成状态为pending的Booking记录,并设置过期时间。此时该房间仅对当前用户开放。
  2. 最终确认:用户完成支付等操作后,将pending状态改为confirmed;若超时未确认,自动触发房间释放逻辑(可通过定时任务或TTL索引实现)。

这种设计既避免了长事务占用锁,又提升了用户体验,同时减少并发冲突的触发概率。

三、并发控制层:酒店粒度的局部队列

针对同一酒店的预订请求,采用酒店专属队列而非全局队列:

  • 每个酒店维护独立的处理队列,同一酒店的确认请求串行处理,不同酒店的请求并行执行,既保证了同一酒店的预订顺序,又不会阻塞其他酒店的请求。
  • 队列仅处理确认阶段的请求,预留阶段无需排队,不影响用户的初始操作流畅度。

四、异常处理与重试机制

  1. 冲突自动重试:当出现数据库约束冲突时,自动重试2-3次,重试间隔采用指数退避策略(比如100ms、200ms、400ms),降低偶发冲突导致的失败概率。
  2. 用户友好提示:重试失败后,告知用户“当前房间紧张,请稍后再试”,或主动推荐同酒店其他可用房间,而非直接抛出错误。

五、性能优化

  • 缓存可用房间数:用Redis缓存酒店的实时可用房间数,减少数据库查询压力。当预订状态变更(预留/确认/释放)时,同步更新缓存值。
  • 异步处理非核心逻辑:发送预订确认邮件、更新统计数据等非核心操作,放入消息队列异步处理,不阻塞主预订流程。

内容的提问来源于stack exchange,提问作者noob_coder

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 18:05:27