获得无忧的交通服务(Uber、Ola)是非常容易的,但是构建这些拥有数百名软件工程师为之工作十年的庞大应用程序也同样简单吗?当然不是。这些系统有着复杂得多的架构,并且有很多组件在内部连接在一起,以在世界范围内提供乘车服务。
!uber
1. 需求分析
1.1 功能性需求
- 用户应该能够看到所有价格最低且预计到达时间(ETA)的可用出租车
- 用户应该能够为他们的目的地预订出租车
- 用户应该能够看到司机的位置
- 用户应该能够随时取消他们的行程
1.2 非功能性需求
- 高可用性
- 高可靠性
- 高可扩展性
- 低延迟
2. 容量估算
让我们假设我们的应用程序有 500 万活跃用户,拥有 20 万名司机,平均每天有 100 万次行程。如果用户平均执行 5 次操作,那么我们需要每天处理 500 万个请求。
每秒请求数: 约 58 RPS(500 万请求/天)
每日所需存储: 约 2.32 GB(假设每条消息 500 字节)
3. Uber 应用低层级设计
我们都很熟悉 Uber 的服务。用户可以通过应用程序请求乘车,几分钟内,司机就会到达他/她的附近位置,带他们去往目的地。
- 早期的 Uber 是基于“单体”软件架构模型构建的。
- 他们拥有一个后端服务、一个前端服务和单一的数据库。
- 他们使用 Python 及其框架,以及 SQLAlchemy 作为数据库的 ORM 层。
- 这种架构对于少数城市中的少量行程来说是可以的,但当服务开始扩展到其他城市时,Uber 团队开始面临应用程序的问题。
- 2014 年之后,Uber 团队决定转向“面向服务的架构”,现在 Uber 还处理外卖配送和货运业务。
!Uber-System-Design-High-Level-Architecture
3.1 探讨面临的挑战
Uber 服务的主要任务之一是将乘客与出租车匹配,这意味着我们需要在架构中拥有两个不同的服务,即:
- 供给服务(针对出租车)
- 需求服务(针对乘客)
Uber 在其架构中拥有一个调度系统(Dispatch optimization/DISCO)来匹配供给和需求。该调度系统使用移动电话,并负责将司机与乘客(供给与需求)进行匹配。
3.2 调度系统是如何工作的?
DISCO 必须达成以下目标……
- 减少额外的驾驶。
- 最少的等待时间
- 最少的整体 ETA
调度系统完全基于地图和位置数据/GPS 运行,所以重要的是第一件事是对我们的地图和位置数据进行建模。
- 地球是球形的,所以很难通过使用经纬度来进行汇总和近似计算。为了解决这个问题,Uber 使用了 Google S2 库。该库将地图数据划分成微小的单元(例如 3km),并为每个单元分配一个唯一的 ID。这是在分布式系统中传播数据并轻松存储它的一种简单方法。
- S2 库可以轻松覆盖任何给定的形状。假设你想弄清楚城市 3km 半径内的所有可用供给。
- 使用 S2 库,你可以画一个半径为 3km 的圆,它会过滤掉所有位于该特定圆内的 ID 单元。
- 通过这种方式,你可以轻松地将乘客与司机匹配,并且你可以轻松找出特定区域内的可用汽车数量(供给)。
3.3 供给服务及其工作原理?
- 在我们的案例中,出租车是供给服务,它们将通过地理位置(经度和纬度)被追踪。
- 所有活跃的出租车通过 Web 应用防火墙和负载均衡器,每 4 秒向服务器发送一次位置信息。
- 准确的 GPS 位置在通过负载均衡器后,通过 Kafka 的 Rest API 发送到数据中心。这里我们使用 Apache Kafka 作为数据中心。
- 一旦 Kafka 更新了最新位置,它就会缓慢地传递到相应工作节点的主内存中。
- 此外,位置的副本(状态机/出租车的最新位置)将被发送到数据库和调度优化器,以保持最新位置的更新。
- 我们还需要跟踪一些其他信息,例如座位数、是否有儿童座椅、车辆类型、是否可以容纳轮椅以及分配情况(例如,一辆出租车可能有四个座位,但其中两个已被占用)。
3.4 需求服务及其工作原理?
- 需求服务通过 Web socket 接收出租车的请求,并追踪用户的 GPS 位置。它还接收不同类型的请求