【Java】如何设计一个支持5 亿用户规模的网约车系统?

2024-06-03 19:04

本文主要是介绍【Java】如何设计一个支持5 亿用户规模的网约车系统?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、问题解析

网约车的官方定义是:“以互联网技术为依托,构建服务平台,整合供需信息,使用符合条件的车辆和驾驶员,提供非巡游的预约出租汽车服务的经营活动。”通俗地说就是:利用互联网技术平台,将乘客的乘车信息发送给合适的司机,由司机完成接送乘客的服务。网约车包含专车、快车、拼车等多种形式。

中国目前网约车用户规模约5亿,我们准备开发一个可支撑目前全部中国用户使用的网约车平台,应用名称为“Udi”。

16.1 需求分析

Udi是一个网约车平台,核心功能是将乘客的叫车订单发送给附近的网约车司机,司机接单后,到上车点接乘客并送往目的地,到达后,乘客支付订单。根据平台的分成比例,司机提取一部分金额作为收益,用例图如下:

Udi平台预计注册乘客5亿,日活用户5千万,平均每个乘客1.2个订单,日订单量6千万。平均客单价30元,平台每日总营收18亿元。平台和司机按3:7的比例进行分成,那么平台每天可赚5.4亿元。

另外,平台预计注册司机5千万,日活司机2千万。

16.2 概要设计

网约车平台是共享经济的一种,目的就是要将乘客和司机撮合起来,所以需要开发两个App应用,一个是给乘客的,用来叫车;一个是给司机的,用来接单。Udi整体架构如下图:

相应的,Udi的系统也可以分成两个部分,一个部分是面向乘客的。乘客通过手机App注册成为用户,然后就可以在手机上选择出发地和目的地,进行叫车了。乘客叫车的HTTP请求首先通过一个负载均衡服务器集群,到达网关集群,再由网关集群调用相关的微服务,完成请求处理,如下图:

网关处理叫车请求的过程是:网关首先调用订单微服务,为用户的叫车请求创建一个订单,订单微服务将订单记录到数据库中,并将订单状态设置为“创建”。然后网关调用叫车微服务,叫车微服务将用户信息、出发地、目的地等数据封装成一个消息,发送到消息队列,等待系统为订单分配司机。

Udi系统的另一部分是面向司机的,司机需要不停将自己的位置信息发送给平台,同时,还需要随时接收来自平台的指令。因此,不同于用户通过HTTP发送请求给平台,司机App需要通过TCP长连接和平台服务器保持通信,如下图:

Udi司机App每3秒向平台发送一次当前的位置信息,包括当前车辆经纬度,车头朝向等。位置信息通过TCP连接到达平台的TCP连接服务器集群,TCP连接服务器集群的作用类似网关,只不过是以TCP长连接的方式向App端提供接入服务。TCP连接服务器将司机的位置信息更新到地理位置服务。

对于前面已经写入到消息队列的乘客叫车订单信息,分单子系统作为消息消费者,从消息队列中获取并处理。分单子系统首先将数据库中的订单状态修改为“派单中”,然后调用派单引擎进行派单。派单引擎根据用户的上车出发地点,以及司机上传的地理位置信息进行匹配,选择最合适的司机进行派单。派单消息通过一个专门的消息推送服务进行发送,消息推送服务利用TCP长连接服务器,将消息发送给匹配到的司机,同时分单子系统更新数据库订单状态为“已派单”。

16.3 详细设计

关于Udi的详细设计,我们将关注网约车平台一些独有的技术特点:长连接管理、派单算法、距离计算。此外,因为订单状态模型是所有交易类应用都非常重要的一个模型,所以我们也会在这里讨论Udi的订单状态模型。

16.3.1 长连接管理

因为司机App需要不断向Udi系统发送当前位置信息,以及实时接收Udi推送的派单请求,所以司机App需要和Udi系统保持长连接。因此,我们选择让司机App和Udi系统直接通过TCP协议进行长连接。

TCP连接和HTTP连接不同。HTTP是无状态的,每次HTTP请求都可以通过负载均衡服务器,被分发到不同的网关服务器进行处理,正如乘客App和服务器的连接那样。也就是说,HTTP在发起请求的时候,无需知道自己要连接的服务器是哪一台。

而TCP是长连接,一旦建立了连接,连接通道就需要长期保持,不管是司机App发送位置信息给服务器,还是服务器推送派单信息给司机App,都需要使用这个特定的连接通道。也就是说,司机App和服务器的连接是特定的,司机App需要知道自己连接的服务器是哪一台,而Udi给司机App推送消息的时候,也需要知道要通过哪一台服务器才能完成推送。

所以,司机端的TCP长连接需要进行专门管理,处理司机App和服务器的连接信息,具体架构如下图。

处理长连接的核心是TCP管理服务器集群。司机App会在启动时通过负载均衡服务器,与TCP管理服务器集群通信,请求分配一个TCP长连接服务器。

TCP管理服务器检查ZooKeeper服务器,获取当前可以服务的TCP连接服务器列表,然后从这些服务器中选择一个,返回其IP地址和通信端口给司机App。这样,司机App就可以直接和这台TCP连接服务器建立长连接,并发送位置信息了。

TCP连接服务器启动的时候,会和ZooKeeper集群通信,报告自己的状态,便于TCP管理服务器为其分配连接。司机App和TCP连接服务器建立长连接后,TCP连接服务器需要向Redis集群记录这个长连接关系,记录的键值对是<司机ID, 服务器名>。

当Udi系统收到用户订单,派单引擎选择了合适的司机进行派单时,系统就可以通过消息推送服务给该司机发送派单消息。消息推送服务器通过Redis获取该司机App长连接对应的TCP服务器,然后消息推送服务器就可以通过该TCP服务器的长连接,将派单消息推送给司机App了。

长连接管理的主要时序图如下:

如果TCP服务器宕机,那么司机App和它的长连接也就丢失了。司机App需要重新通过HTTP来请求TCP管理服务器为它分配新的TCP服务器。TCP管理服务器收到请求后,一方面返回新的TCP服务器的IP地址和通信端口,一方面需要从Redis中删除原有的<司机ID, 服务器名>键值对,保证消息推送服务不会使用一个错误的连接线路推送消息。

16.3.2 距离计算

乘客发起一个叫车请求时,Udi需要为其寻找合适的司机并进行派单,所谓合适的司机,最主要的因素就是距离。在[第9讲]的交友系统设计中,我们已经讨论过GeoHash算法,Udi就是直接使用Redis的GeoHash进行邻近计算。司机的位置信息实时更新到Redis中,并直接调用Redis的GeoHash命令georadius计算乘客的邻近司机。

但是Redis使用跳表存储GeoHash,Udi日活司机两千万,每3秒更新一次位置信息,平均每秒就需要对跳表做将近7百万次的更新,如此高并发地在一个跳表上更新,是系统不能承受的。所以,我们需要将司机以及跳表的粒度拆得更小。

Udi以城市作为地理位置的基本单位,也就是说,每个城市在Redis中建立一个GeoHash的key,这样,一个城市范围内的司机存储在一个跳表中。对于北京这样的超级城市,还可以更进一步,以城区作为key,进一步降低跳表的大小和单个跳表上的并发量。

16.3.3 派单算法

前面说过,派单就是寻找合适的司机,而合适的主要因素就是距离,所以最简单的派单算法就是直接通过Redis获取距离乘客上车点最近的空闲网约车即可。

但是这种算法效果非常差,因为Redis计算的是两个点之间的空间距离,但是司机必须沿道路行驶过来,在复杂的城市路况下,也许几十米的空间距离行驶十几分钟也未可知。

因此,我们必须用行驶距离代替空间距离,即Udi必须要依赖一个地理系统,对司机当前位置和上车点进行路径规划,计算司机到达上车点的距离和时间。事实上,我们主要关注的是时间,也就是说,派单算法需要从Redis中获取多个邻近用户上车点的空闲司机,然后通过地理系统来计算每个司机到达乘客上车点的时间,最后将订单分配给花费时间最少的司机。

如果附近只有一个乘客,那么为其分配到达时间最快的司机就可以了。但如果附近有多个乘客,那么就需要考虑所有人的等待时间了。比如附近有乘客1和乘客2,以及司机X和司机Y。司机X接乘客1的时间是2分钟,接乘客2的时间是3分钟;司机Y接乘客1的时间是3分钟,接乘客2的时间是5分钟。

如果按照单个乘客最短时间选择,给乘客1分配司机X,那么乘客2只能分配司机Y了,乘客总的等待时间就是7分钟。如果给乘客1分配司机Y,乘客2分配司机X,乘客总等待时间就是6分钟。司机的时间就是平台的金钱,显然,后者这样的派单更节约所有司机的整体时间,也能为公司带来更多营收,同时也为整体用户带来更好的体验。

这样,我们就不能一个订单一个订单地分别分配司机,我们需要将一批订单聚合在一起,统一进行派单,如下图:

分单子系统收到用户的叫车订单后,不是直接发送给派单引擎进行派单,而是发给一个订单聚合池,订单聚合池里有一些订单聚合桶。订单写完一个聚合桶,就把这个聚合桶内的全部订单推送给派单引擎,由派单引擎根据整体时间最小化原则进行派单。

这里的“写完一个聚合桶”,有两种实现方式,一种是间隔一段时间算写完一个桶,一种是达到一定数量算写完一个桶。最后Udi选择间隔3秒写一个桶。

这里需要关注的是,派单的时候需要依赖地理系统进行路径规划。事实上,乘客到达时间和金额预估、行驶过程导航、订单结算与投诉处理,都需要依赖地理系统。Udi初期会使用第三方地理系统进行路径规划,但是将来必须要建设自己的地理系统。

16.3.4 订单状态模型

对于交易型系统而言,订单是其最核心的数据,主要业务逻辑也是围绕订单展开。在订单的生命周期里,订单状态会多次变化,每次变化都是由于核心的业务状态发生了改变,也因此在前面设计的多个地方都提到订单状态。但是这种散乱的订单状态变化无法统一描述订单的完整生命周期,因此我们设计了订单状态模型,如下图:

用户叫车后,系统即为其创建一个订单,订单进入“创单”状态。然后该订单通过消息队列进入分单子系统,分单子系统调用派单引擎为其派单,订单状态进入“派单中”。派单引擎分配到司机,一方面发送消息给司机,一方面修改订单状态为“已派单”。

如果司机去接到乘客,订单状态就改为“行程中”;如果司机拒绝接单,就需要为乘客重新派单,订单重新进入消息队列,同时订单状态也改回为“派单中”;如果司机到达上车点,但是联系不到乘客,没有接到乘客,那么订单就会标记为“已取消”。如果在派单中,乘客自己选择取消叫车,订单也进入“已取消”状态。“已取消”是订单的一种最终状态,订单无法再转变为其他状态。

司机到达目的地后,通过App确认送达,订单进入“待支付”状态,等待用户支付订单金额。用户支付后,完成订单生命周期,订单状态为“已完成”。

订单状态模型可以帮助我们总览核心业务流程,在设计阶段,可以通过状态图发现业务流程不完备的地方,在开发阶段,可以帮助开发者确认流程实现是否有遗漏。

二、粉丝福利

最近很多同学问我有没有java学习资料,我根据我从小白到架构师多年的学习经验整理出来了一份80W字面试解析文档、简历模板、学习路线图、java必看学习书籍 、 需要的小伙伴 可以关注我
公众号:“ 灰灰聊架构 ”, 回复暗号:“ 159 ”即可获取

这篇关于【Java】如何设计一个支持5 亿用户规模的网约车系统?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1027840

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

基于人工智能的图像分类系统

目录 引言项目背景环境准备 硬件要求软件安装与配置系统设计 系统架构关键技术代码示例 数据预处理模型训练模型预测应用场景结论 1. 引言 图像分类是计算机视觉中的一个重要任务,目标是自动识别图像中的对象类别。通过卷积神经网络(CNN)等深度学习技术,我们可以构建高效的图像分类系统,广泛应用于自动驾驶、医疗影像诊断、监控分析等领域。本文将介绍如何构建一个基于人工智能的图像分类系统,包括环境

水位雨量在线监测系统概述及应用介绍

在当今社会,随着科技的飞速发展,各种智能监测系统已成为保障公共安全、促进资源管理和环境保护的重要工具。其中,水位雨量在线监测系统作为自然灾害预警、水资源管理及水利工程运行的关键技术,其重要性不言而喻。 一、水位雨量在线监测系统的基本原理 水位雨量在线监测系统主要由数据采集单元、数据传输网络、数据处理中心及用户终端四大部分构成,形成了一个完整的闭环系统。 数据采集单元:这是系统的“眼睛”,