在剧院中搜索您最喜欢的电影,检查座位可用性,并在短短 5-10 分钟内在 BookMyShow 应用程序上预订门票真的很容易……
我们都知道 BookMyShow 的服务(毕竟我们都喜欢看电影……lolz)以及它是如何工作的,但你能想象在这个巨大的网站背后,工程师们是如何用他们的大脑来构建这个系统的复杂架构的。
如果我们要求您在短短 45 分钟(或更短时间)内设计这个系统呢(这是开玩笑吗…… ?? )……?我们不是开玩笑,但如果你是一个准备进入顶级科技巨头公司的人,你可能会在面试中面临系统设计轮(特别是高级工程师的角色),设计一个像BookMyShow这样的系统是相当不错的。本轮常见问题。
在这个博客中,我们将讨论如何设计一个像 BookMyShow 这样的在线订票系统,但在我们进一步讨论之前,我们希望您阅读文章“如何在面试中破解系统设计?”。它会让你知道这一轮是什么样的,你应该做什么,以及在面试官面前应该避免哪些错误。
1. 定义目标和要求
告诉你的面试官你将支持以下功能。如果面试官想添加更多功能,他/她会提到。
- 门户网站应列出剧院所在的不同城市。 (关系型数据库)
- 一旦用户选择了城市,它应该向该用户显示在该特定城市发行的电影。
- 用户选择电影后,门户网站应显示运行该电影的电影院和可用节目。
- 用户应该能够选择特定剧院的演出并预订门票(第三方支付支持)。
- 通过短信通知或电子邮件发送门票副本。 (工人和 GCM)
- 登录时的电影建议(Hadoop 和 Spark 流与 ML 以获得推荐引擎),向用户发送有关新电影发行和其他内容的实时通知。
- 门户应向用户显示影厅的座位安排。
- 用户应该能够根据自己的选择选择多个座位。
- 用户应该能够在他/她完成付款之前保持座位 5-10 分钟。
- 门户网站应以先进先出的方式提供门票
- 评论和评级 (Cassandra)
- 系统应该是高度并发的,因为同一时间会有多个预订请求。
- 门户的核心是订票,这意味着金融交易。所以系统应该是安全的并且符合 ACID。
- 响应式设计(ReactJS 和 Bootstrap)可在各种尺寸的设备上运行,如手机、平板电脑、台式机等。
- 电影信息。
2. Bookmyshow 如何与影院对话?
当您使用移动应用程序或网站访问任何第三方应用程序/电影票聚合器时,您会看到该剧院电影放映的可用座位和已占用座位。现在的问题是这些第三方聚合器如何与剧院交谈、获取可用座位信息并将其显示给用户。当然,该应用程序需要与剧院的服务器配合才能获得座位分配并将其提供给用户。主要有两种策略可以将席位分配给这些聚合器。
- 特定数量的席位将专用于每个聚合器,然后这些席位将提供给用户。在这个策略中,已经为这些聚合器预留了一些座位,因此不需要不断更新所有剧院的座位信息。
- 在第二种策略中,应用程序可以与剧院和其他聚合器一起工作,以不断更新座位可用性信息。然后票将提供给用户。
3. 如何获取可用座位信息?
获取这些信息主要有两种方式……
- 聚合器可以直接连接到剧院的数据库并从数据库表中获取信息。然后可以缓存这些信息并显示给用户。
- 使用剧院的服务器 API 获取可用座位信息并预订门票。
如果多个用户尝试使用不同平台预订同一张票会发生什么?如何解决这个问题呢?
剧院的服务器需要遵循超时锁定机制策略,即在特定时间段(例如 5-10 分钟)内为用户临时锁定座位。如果用户无法在该时间范围内预订座位,则为其他用户释放座位。这应该以先到先得的方式进行。
如果您使用的是影院服务器 API,那么您将从您的服务器到影院服务器发出大量请求或 IO 阻塞调用。为了获得更好的性能,我们应该在Python或 Erlangs 轻量级线程中使用 async 或在 Go 中使用 Go Coroutines。
高层架构
BookMyShow 建立在微服务架构之上。让我们分别看一下组件。
负载均衡器
负载均衡器用于在服务器上分配负载并在我们水平扩展应用服务器时保持系统高并发。负载平衡器可以使用多种技术来平衡负载,这些是……
- 一致性哈希
- 循环赛
- 加权循环
- 最少连接
前端缓存和 CDN
我们使用Varnish进行前端缓存以减少后端基础架构的负载。我们还可以使用 CDN Cloudflare 来缓存页面、API、视频、图像和其他内容。
应用服务器
将有多个应用服务器,BookMyShow 使用Java、Spring Boot、Swagger、Hibernate作为应用服务器。我们还可以使用基于 Python 或 NodeJS 的服务器(取决于需求)。我们还需要水平扩展这些应用程序服务器,以承受繁重的负载并并行处理大量请求。
弹性搜索
弹性搜索用于支持 Bookmyshow 上的搜索 API(用于搜索电影或节目)。 Elastic 搜索是分布式的,它在系统中提供了 RESTful 搜索 API。它还可以用作分析引擎,作为应用程序级搜索引擎来回答来自前端的所有搜索查询。
缓存
为了保存与电影、座位排序、剧院等相关的信息,我们需要使用缓存。我们可以使用Memcache 或 Redis进行缓存,将所有这些信息保存在 Bookmyshow 中。 Redis 是开源的,它也可以用于锁定机制,为用户临时阻止票证。这意味着当用户尝试预订票时,Redis 将使用特定的 TTL 阻止票。
数据库
我们需要将 RDBMS 和 NoSQL 数据库用于不同的目的。我们来分析一下我们的系统需要什么,什么样的数据库适合什么样的数据……
- RDBMS:我们已经提到在我们的系统中需要 ACID 属性。此外,我们有国家、城市、城市中的剧院,这些剧院中有多个屏幕,每个屏幕上都有多排座位。所以在这里很明显我们需要一个适当的关系表示。此外,我们需要处理事务。 RDBMS 适合这些情况。门户将是大量读取的,因此我们需要通过 Geo 对数据进行分片,或者我们需要使用 master-master slave 架构。从设备可用于读取,主设备可用于写入。
- NoSQL:我们还有大量数据,例如电影信息、演员、工作人员、评论和评论。 RDBMS 无法处理这么多数据,所以我们需要使用可分布式的 NoSQL 数据库。 Cassandra 是处理大量信息的不错选择。我们可以在部署在多个区域的多个节点中保存多个数据副本。这确保了数据的高可用性和持久性(如果一个节点宕机,我们将在其他节点上有数据可用)。
- 使用HDFS运行查询以进行分析。
异步工作者
Async worker 的主要任务是执行任务,例如为预订的门票生成图像的 pdf 或 png 并将通知发送给用户。对于推送通知、短信通知或电子邮件,我们需要调用第三方 API。这些是网络 IO,它增加了很多延迟。此外,这些耗时的任务无法同步执行。为了解决这个问题,应用服务器一旦确认订票,就会将消息发送到消息队列,空闲的工作人员会拿起任务,异步执行并提供短信通知,其他通知或电子邮件给用户。 RabbitMQ或Kafka可用于消息队列系统, Python celery可用于工作线程。对于浏览器通知或电话通知,请使用GCM/APN。
商业智能和机器学习
对于业务信息的数据分析,我们需要有一个Hadoop平台。所有日志、用户活动和信息都可以转储到 Hadoop 中,最重要的是我们可以运行PIG/ Hive查询来提取用户行为或用户图等信息。 ML用于了解用户的行为并生成电影推荐等。对于实时分析,我们可以使用Spark Streaming。我们还可以使用 Spark 或Storm流处理引擎找出欺诈检测和缓解策略。
日志管理
ELK (ElasticSearch、Logstash、Kibana)堆栈用于日志系统。所有日志都被推送到 Logstash。 Logstash 通过 Files/Syslog/socket/AMQP 等从所有服务器收集数据,并基于一组不同的过滤器将日志重定向到 Queue/File/Hipchat/Whatsapp/JIRA 等。
逐步工作
- 客户访问门户并过滤位置。要找到位置,我们可以使用 GPS(如果是手机)或 ISP(如果是笔记本电脑)。然后将向用户推荐在该影院上映的影院和电影。数据将由 DB 和 ELK 推荐引擎提供。
- 用户选择电影并检查附近所有剧院的电影的不同时间。
- 用户在他/她自己选择的剧院中为电影选择特定的日期和时间。可用座位信息将从数据库中获取并显示给用户。
- 一旦用户选择了可用座位,BMS 会在接下来的 10 分钟内暂时锁定该座位。 BMS 与影院 DB 交互并为用户锁定座位。将为当前用户和使用不同聚合器或应用程序的所有其他用户临时预订门票,该座位将在接下来的 10 分钟内不可用。如果用户未能在该时间范围内预订机票,则将为其他聚合器释放座位。
- 如果用户继续预订,他/她将检查带有付款选项的发票,通过支付网关付款后,应用服务器将收到付款成功的通知。
- 支付成功后,影院会生成一个唯一的ID,并提供给应用服务器。
- 使用该唯一 ID 将生成带有二维码的票证,并向用户显示票证的副本。此外,将向队列添加一条消息,以通过 SMS 通知或电子邮件将票证的发票副本发送给用户。门票将包含所有详细信息,例如电影、剧院地址、时间、剧院编号等。
- 在电影时间,当顾客参观剧院时,将扫描二维码,如果顾客的门票和剧院的门票上的ID匹配,顾客将被允许进入剧院。
需要的 API
- 获取城市列表()
- GetListOfEventsByCity(CityId)
- GetLocationsByCity(CityId)
- GetLocationsByEventandCity(cityid, eventid)
- GetEventsByLocationandCity(CityId, LocationId)
- GetShowTiming(eventid, locationid)
- GetAvailableSeats(eventid, locationid, showtimeid)
- VarifyUserSelectedSeatsAvailable(eventid、locationid、showtimeid、seats)
- BlockUserSelectedSeats()
- BookUserSelectedSeat()
- GetTimeoutForUserSelectedSeats()
关系型数据库表
- 地点(保存任何给定剧院的层次结构数据,如国家、州、城市和街道)
- 剧院
- 屏幕
- 层(座位层)
- 座位
- 电影
- 优惠
- 票
- 用户
RDBMS 表之间的关系
- 一对多:地方和剧院。
- 一对多:剧院和银幕
- 一对多:屏幕和层级
- 一对多:等级和座位
- 一对一:屏幕和电影
- 一对多:用户和票证
- 一对多:门票和座位
NoSQL 表
这些表之间不会有任何关系。
- 评论
- 评分
- 电影信息
- 预告片或画廊
- 艺术家
- 演职人员
- 评论
- 分析数据
Bookmyshow 使用的技术
- 用户界面: ReactJS 和 BootStrapJS
- 服务器语言和框架: Java、Spring Boot、Swagger、Hibernate
- 安全性: Spring Security
- 数据库: MySQL
- 服务器:雄猫
- 缓存:在内存缓存 Hazelcast。
- 通知: RabbitMQ。用于推送通知的分布式消息队列。
- 支付API:流行的有Paypal、Stripe、Square
- 部署: Docker & Ansible
- 代码仓库: Git
- 日志记录: Log4J
- 日志管理: Logstash、ELK Stack
- 负载均衡器: Nginx
许多候选人更害怕系统设计轮而不是编码轮。原因是……他们不知道在这个有限的时间范围内应该涵盖哪些主题和权衡。他们需要记住,系统设计回合是非常开放的,没有标准答案之类的东西。对于同样的问题,不同面试官的对话可能会有所不同。你的实践经验、你的知识、对现代软件系统的理解,以及你在面试中如何清楚地表达自己对成功设计系统非常重要。