system-design-notes第22章:设计酒店预订系统完整指南
2026/9/17 10:39:41 网站建设 项目流程

system-design-notes第22章:设计酒店预订系统完整指南

【免费下载链接】system-design-notesNotes of the book System Desgin Interview - An Insider's Guide项目地址: https://gitcode.com/GitHub_Trending/sy/system-design-notes

本文是 system-design-notes 第 22 章的完整指南,带你用系统设计的标准流程——需求分析、容量估算、数据建模、并发控制、分片扩展与数据一致性——一步步设计一个酒店预订系统。酒店预订系统是面试高频题,同一套思路也适用于 Airbnb、机票预订、电影售票等场景。读完本文,你将掌握避免"重复预订"、数据库分片和微服务一致性的实用方案。

🎯 第1步:先弄清需求,明确酒店预订系统的边界

在动手设计前,先和面试官确认范围。第22章给出的核心设定:

  • 一个拥有5000 家酒店、100 万间客房的酒店集团网站
  • 用户在预订时全额付款,仅通过网站/App 下单,支持取消预订
  • 允许10% 的超售(overbooking)——酒店会多卖房间,赌有人会取消
  • 房价每天变化,价格与当日入住率相关

非功能性需求:高峰期大量用户可能同时订同一家酒店,系统要支持高并发;预订延迟中等即可(几秒内完成可以接受)。

💡 面试技巧:先问清楚"预订的是具体房间还是房型",这会直接影响数据模型设计。

用"纸面估算"算清系统规模

假设 70% 客房被占用、平均住 3 天,则每日约100万 × 0.7 / 3 ≈ 24 万笔预订,折算平均TPS 只有 3 左右——写压力很小。但页面浏览量按漏斗反推:

![酒店预订系统的QPS估算漏斗图:从预订到房型详情页的访问量推算](https://raw.gitcode.com/GitHub_Trending/sy/system-design-notes/raw/9d8388721e7231442763ad37398b8d82224aa68f/22. Hotel Reservation System/images/qps-estimation.png?utm_source=gitcode_repo_files)

若 3 笔预订、每页 10% 转化率,则预订详情页约 30 次/秒、房型页约 300 次/秒。读多写少,这个特征决定了后面的数据库选型。

🏗️ 第2步:高层设计——API、数据模型与微服务架构

酒店预订系统的 RESTful API 设计

核心接口分三组(详见 22. Hotel Reservation System/README.md):

分组关键接口
酒店GET /v1/hotels/{id}POST /v1/hotels(仅运营)
房间GET /v1/hotels/{id}/rooms/{id}
预订POST /v1/reservationsDELETE /v1/reservations/{id}

注意预订请求中带有一个reservationID字段,它是幂等键,后文解决重复预订时要用到。

为什么选关系型数据库

  • 读多写少,关系型数据库(如 MySQL)正合适
  • 预订涉及金钱,必须靠ACID 事务防止重复扣款、负数库存
  • 酒店-房型-预订的结构非常清晰,关系模型好表达

初始表结构包含 hotel、room、reservation 三张表,其中status字段描述房间状态机:

![酒店预订系统schema设计:hotel、room、reservation三张核心表](https://raw.gitcode.com/GitHub_Trending/sy/system-design-notes/raw/9d8388721e7231442763ad37398b8d82224aa68f/22. Hotel Reservation System/images/schema-design.png?utm_source=gitcode_repo_files)

但这个初版模型有个缺陷:用户预订的是房型而非具体房间号,房间号应在下单时才分配。后面第3步会修正它。

微服务架构图:六个服务的职责划分

![酒店预订系统微服务架构:酒店服务、价格服务、预订服务、支付服务与公共API网关](https://raw.gitcode.com/GitHub_Trending/sy/system-design-notes/raw/9d8388721e7231442763ad37398b8d82224aa68f/22. Hotel Reservation System/images/high-level-design.png?utm_source=gitcode_repo_files)

  • 公共 API 网关:负责限流、认证(限流方案可参考 04. Rate Limiter/Readme.md)
  • 酒店服务 / 价格服务:酒店和房型数据变化慢,可激进缓存;价格服务按未来日期提供房费
  • 预订服务:接收预订并管理房间库存
  • 支付服务:处理付款,成功后更新预订状态
  • 服务间通信用 gRPC 这类 RPC 框架

📊 第3步:改进数据模型——按"房型"预订才是关键

修正后的 API 不再传roomID,而是传roomTypeID+ 数量roomCount。更新后的表结构如下:

![改进后的酒店预订系统schema:新增room_type_rate与room_type_inventory库存表](https://raw.gitcode.com/GitHub_Trending/sy/system-design-notes/raw/9d8388721e7231442763ad37398b8d82224aa68f/22. Hotel Reservation System/images/updated-schema.png?utm_source=gitcode_repo_files)

新增的room_type_inventory表是精髓,每行记录(酒店, 房型, 单一日期)的库存:

  • total_inventory:该房型总房间数(扣除临时下架的)
  • total_reserved:该日期已预订数
  • 判断是否有房:total_reserved + 要订数 ≤ 110% × total_inventory(支持 10% 超售)

行由每日 CRON 任务预生成。按 5000 家酒店 × 20 种房型 × 2 年 × 365 天估算,仅7300 万行,单台数据库服务器就能扛住,加读写副本即可保证高可用。若预订表膨胀,可按hash(hotel_id)分库分表(所有查询都带 hotel_id,分片键天然合适),历史数据可归档到冷存储。

⚔️ 第4步:并发难题——防止重复预订的3种实战方案

这是本章最核心的考点。要解决两个问题:同一用户连点两次"预订";多个用户同时抢同一房型。

问题一:同一用户双击预订

前端禁用按钮只是权宜之计,可靠做法是幂等 API

![酒店预订系统幂等设计:reservation_id重复提交被后端识别为重复订单](https://raw.gitcode.com/GitHub_Trending/sy/system-design-notes/raw/9d8388721e7231442763ad37398b8d82224aa68f/22. Hotel Reservation System/images/idempotency.png?utm_source=gitcode_repo_files)

流程:用户填信息时先生成一个全局唯一的reservation_id;无论点击几次"完成预订",提交的都是同一个 id;数据库对该列建唯一约束,重复记录直接写入失败:

![数据库唯一约束拦截重复预订:第二条相同reservation_id的记录被拒绝](https://raw.gitcode.com/GitHub_Trending/sy/system-design-notes/raw/9d8388721e7231442763ad37398b8d82224aa68f/22. Hotel Reservation System/images/unique-constraint-violation.png?utm_source=gitcode_repo_files)

问题二:多用户同时抢订

在低隔离级别下,两个事务可能都看到"还有房",双双写入成功,造成超售:

![酒店预订系统重复预订场景:用户1和用户2同时下单导致库存检查竞态](https://raw.gitcode.com/GitHub_Trending/sy/system-design-notes/raw/9d8388721e7231442763ad37398b8d82224aa68f/22. Hotel Reservation System/images/double-booking-multiple-users.png?utm_source=gitcode_repo_files)

三种解法对比:

方案1:悲观锁(Pessimistic Locking)

SELECT ... FOR UPDATE在更新前锁住行。

![悲观锁原理图解:SELECT FOR UPDATE锁定库存行直到事务提交](https://raw.gitcode.com/GitHub_Trending/sy/system-design-notes/raw/9d8388721e7231442763ad37398b8d82224aa68f/22. Hotel Reservation System/images/pessimistic-locking.png?utm_source=gitcode_repo_files)

  • ✅ 实现简单,串行化更新,适合高争用场景
  • ❌ 长事务会阻塞其他所有事务,可能死锁,扩展性差

方案2:乐观锁(Optimistic Locking)⭐ 本章推荐

给表加version列,更新时校验版本号并 +1,失败则重试。

![乐观锁原理图解:版本号校验防止基于旧数据的更新](https://raw.gitcode.com/GitHub_Trending/sy/system-design-notes/raw/9d8388721e7231442763ad37398b8d82224aa68f/22. Hotel Reservation System/images/optimistic-locking.png?utm_source=gitcode_repo_files)

  • ✅ 不加数据库锁,性能好;避免基于过期数据修改
  • ❌ 高争用下大量回滚,性能下降
  • 酒店预订 TPS 只有个位数,争用低,乐观锁是最佳选择(建议用版本号而非时间戳,服务器时钟可能不准)

方案3:数据库约束

直接用 CHECK 约束兜底:total_inventory - total_reserved >= 0

![数据库约束防止库存为负:check_room_count约束拦截非法UPDATE](https://raw.gitcode.com/GitHub_Trending/sy/system-design-notes/raw/9d8388721e7231442763ad37398b8d82224aa68f/22. Hotel Reservation System/images/database-constraint.png?utm_source=gitcode_repo_files)

  • ✅ 最简单,小争用场景表现好
  • ❌ 部分数据库不支持,约束难以像代码一样版本管理

💡 面试加分点:说明"乐观锁 + 唯一约束"组合拳——幂等键防单人重复,版本/约束防多人竞态,数据库约束做最后防线。

🚀 第5步:可扩展性——数据库分片 + Redis 缓存

若系统被 booking.com 级别的流量采用(QPS 放大 1000 倍),瓶颈在哪?

所有服务都是无状态,直接横向复制即可(原理见 01. Scaling/Readme.md)。真正难扩展的是有状态的数据库,解法是分片:

![酒店预订系统数据库分片:按hotel_id将数据分布到多个分片集群](https://raw.gitcode.com/GitHub_Trending/sy/system-design-notes/raw/9d8388721e7231442763ad37398b8d82224aa68f/22. Hotel Reservation System/images/database-sharding.png?utm_source=gitcode_repo_files)

假设 QPS 达 30,000,拆成 16 个分片后每片仅 1875 QPS,在单 MySQL 集群能力之内。

再叠加Redis 库存缓存,库存 key 按hotelID_roomTypeID_{date}存储:

![Redis库存缓存架构:CDC异步同步数据库变更到Redis缓存层](https://raw.gitcode.com/GitHub_Trending/sy/system-design-notes/raw/9d8388721e7231442763ad37398b8d82224aa68f/22. Hotel Reservation System/images/inventory-cache.png?utm_source=gitcode_repo_files)

  • 缓存与数据库通过CDC 流式机制(如 Debezium)异步同步
  • 短暂不一致没关系:最终写入时数据库约束会拦截非法预订,最坏情况只是用户刷新页面看到"没房了"
  • 为已过期日期设置 TTL 自动清理

缓存的代价是一致性难维护,需评估其对用户体验的影响——这是经典的缓存与一致性权衡(可延伸阅读 06. Key-Value Store/Readme.md)。

🧩 第6步:微服务间的数据一致性权衡

单体应用可以用同一个关系型数据库的事务保证原子性;而拆成微服务后,跨服务操作不再原子:

![微服务非原子操作:预订服务与支付服务分离后无法用单库事务保证一致性](https://raw.gitcode.com/GitHub_Trending/sy/system-design-notes/raw/9d8388721e7231442763ad37398b8d82224aa68f/22. Hotel Reservation System/images/microservice-non-atomic-operation.png?utm_source=gitcode_repo_files)

本章的务实选择是混合架构:预订和库存 API 放在同一个服务内,继续享受 ACID 事务的红利,避免不必要的复杂度。若坚持纯微服务(每服务独立数据库),则要用:

  • 两阶段提交(2PC):跨节点原子提交,但一个节点卡顿会阻塞所有节点,性能差
  • Saga:一系列本地事务 + 失败时的补偿事务,属于最终一致方案

⚖️ 核心思想:跨服务一致性会显著抬高系统复杂度,先问"值不值得",能用单库事务解决的别上分布式协议。

✅ 本章核心要点速记

步骤关键决策
需求与估算TPS≈3 读多写少;支持 10% 超售
技术选型MySQL(ACID)+ 微服务 + gRPC
数据模型按"房型+日期"库存表,CRON 预生成
并发控制幂等键 + 乐观锁 + 唯一约束
扩展hotel_id 分片 + Redis 库存缓存 + CDC 同步
一致性混合架构优先,必要时 Saga / 2PC

延伸阅读:容量估算方法见 02. Back Of the Envelope Estimation/Readme.md,本章完整源码笔记见 22. Hotel Reservation System/README.md。把这套"估算 → 建模 → 防竞态 → 分片 → 一致性"的流程记熟,机票、门票、演出订票类面试题都能直接复用。

【免费下载链接】system-design-notesNotes of the book System Desgin Interview - An Insider's Guide项目地址: https://gitcode.com/GitHub_Trending/sy/system-design-notes

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询