江西雨林听声网络科技有限公司

如何用Go实现一款类似滴滴优步的网络约车软件(含源码)

日期:2024-07-16 00:00 / 作者:网络

如何用Go实现一款类似滴滴优步的网络约车软件(含源码)

导读:我们经常使用打车软件出行,也经常思考其架构设计。本文作者在所在国家也负责开发一款打车软件,并且开源了其中大部分代码,可以帮助我们更好了解网络约车软件的架构体系。本文由高可用架构翻译。

各位读者好,本文将给大家分享我们如何通过内存存储实现地图动画车效果。 我们公司也运营了一个类似 Uber 的软件 Namba Taxi,我们需要在客户端主屏幕上显示动画车。 这篇文章是关于功能如何完整实现的文章,主要目的不是介绍 Go 语言。

开始

这个故事始于2015年,我们的移动开发人员开发一款软件,工作主题是为出租车司机提供打车服务。 在应用程序中,动画汽车看起来像下面的图中动画那样 [1] 。

我们的第一个挑战是缺乏地图跟踪数据。我们每 15 秒获取一次位置数据。 我们不能简单减小上报间隔,因为当司机端程序上行数据时候,同时需要获取当前订单,下一个订单,以及一些警报功能(一个SOS按钮, 当司机按下它,其他司机就可以帮助他)。当我们减少更新间隔时,系统流量更大。 我们不确认我们是否能够扛住如此大的刷新。

实现的第一步

我们第一次的尝试比较简单:

  1. 处理请求并保存坐标。

  2. 创建另一个请求并为汽车设置动画。

显而易见,这样做存在一些问题,如大家在一些打车软件所见,我们不能正确地绘制汽车路线,汽车可能跑在田野,森林,湖泊和公寓上,用这种方法后效果看起来是这样的 [2]。

作为问题的解决方案,我们使用 OpenStreetMap Routeing Machine(OSRM)来规划线路并改进我们的算法,并使用相同的超时设置。

  1. 发起请求。

  2. 获取坐标。

  3. 将保存的坐标发送到服务器。

  4. 通过 OSRM 构建路线。

  5. 返回数据到客户端。

通过线路规划体系,现在似乎可以工作了,但我们又面临单向道路的问题

例如,司机停留在红点的十字路口。 但他的设备位置准确性有问题,导致数据标记在十字路口的对面。 在客户端,我们获取这些坐标,保存并发送到后端,OSRM 建立一个合法的路线,并返回给应用程序。因为客户端移动得非常快,所以这种情况路线规划很可笑。

我们以一种朴素的方式解决了这个问题。 我们检查两点之间的*短距离,并且不建立距离小于 20 米的路线。 使用该算法经过几天的测试后,我们决定发布我们的应用程序并希望获取一些反馈。

尽管如此,我们的版本还存在一些问题,所以我们决定进行第二次迭代。

  1. 第一是车费计算器,计算是在司机端(客户端)完成,这样避免发送无用的请求,可以节约很多服务端资源。 另一方面,为了安全等方面考虑,我们需要在服务器端复制数据并保存它。

  2. 此外,我们意识到每 15 秒一次上报太少,因为用户在屏幕打开后,15秒后才会看到车在移动。

  3. 此外,我们在司机端的 GPS 模块有很多问题,这个可能跟司机的手机设备相关。

  4. *后,我们想要在主屏幕上渲染动画车。


还需要解决的问题

  1. 从司机收集更多的数据

  2. 在主屏幕上显示动画车

  3. 在服务器端存储行车过程中计费数据

  4. 节约移动流量

  5. 每秒收集一次数据

我想谈一谈有关节约移动流量带宽的问题。在我们国家,出租车收费非常便宜,我们像使用公共交通那样使用出租车。 例如,从城市的一边跑到另一边可能只需要 2 欧元,这就跟在巴黎坐地铁价格差不多。但另外一方面移动带宽成本还也很高,如果我们每秒节约 100 字节,那么我们将给为公司节省差不多 2000 美元。

数据追踪

  1. 司机位置(纬度,经度)

  2. 司机当前的 session 信息,在登录时我们会给司机端提供 session id

  3. 订单信息(订单 ID 和车费)

我们决定每一次数据上报应小于 100 字节。 我们寻找传输协议来解决这个问题

正如你可以看到,我们审视了以下几个协议:

  1. HTTP

  2. WebSockets

  3. TCP

  4. UDP

对我们来说理想的选择是 UDP,因为:

  1. 我们只发送数据报

  2. 我们不需要保证送达

  3. 极简主义

  4. 保存大量数据

  5. 只有 20 字节开销

  6. 在我们的国家的移动网络没有被阻止

至于数据序列化,我们考察了:

  1. JSON

  2. MsgPack

  3. Protobuf

我们选择 ProtoBuf,因为它对小数据非常有效。

以看到*近的竞争对手是 PB 的三倍。(小编:可以参考 TimYang 的一条微博 [3] )

每次上报总共的数据

  1. 42 字节的业务数据

  2. 加上 20 字节的 IP 报头

  3. 得到每次上报 62 字节数据

当我们获得数据时,我们考虑如何存储。

数据存储

我们需要存储这些数据:

  1. 标识司机的会话信息 session id

  2. 车牌号

  3. 订单 ID 和计费信息

  4. 执行搜索的*后位置

  5. N 次*后位置以规划路线


使用的存储

  1. 使用 Percona 存储所有数据。 我们存储司机,订单,计费等。

  2. Redis 作为用于缓存。

  3. Elasticsearch 用于地理编码

如上所述,当有大量在线司机时候,使用这些存储来保存数据并不方便。 所以我们需要地理索引。

我们评估了两个地理索引:

  1. KD 树

  2. R 树。

我们对地理索引的要求:

  1. 搜索 N 个*近的点。

  2. 我们需要一个平衡树,以在*糟糕的情况下提供*好的搜索

KD 树

KD 树并不适合我们的需要,因为它是不平衡的,只能搜索一个*近的点。 我们可以在 kd-tree 上实现 k-nearest 邻居,但是没必要重造轮子,因为 R-tree 已经解决了这个问题。

R-树

它看起来像这样。 我们可以执行搜索 N 个*近点,并且它是平衡树。 我们选择了这个。

您可以得到它的 Go 语言实现源码 [5]。

另外,我们需要一个过期机制,因为我们需要使司机的超时机制,比如司机端 900 秒没有响应则在服务器删除会话。 所以我们需要 LRU 数据结构来存储*后的位置。 同时因为我们只存储 N 个位置。 如果我们尝试添加数据时候,队列存储已满,我们则删除*少使用的那个条目。 

下面是我们的存储架构。

  1. 我们将所有数据存储在内存中。

  2. 我们使用 R-tree 执行搜索*近的司机

  3. 此外,我们使用两个检索图,可以并按车牌号或session执行搜索

我们打车软件*终算法

这里是后端的*终算法:

  1. 使用 UDP 传输数据

  2. 尝试从存储获取司机

  3. 如果存储不存在 – 则从 Redis 获取司机

  4. 检查并验证数据

  5. 将司机保存到存储

  6. 如果不存在 – 初始化 LRU

  7. 更新 r-tree

HTTP 接口

我们实现了这些接口:

  1. 返回*近的司机;

  2. 从存储中删除司机(通过车牌号或session id)

  3. 获取行程信息

  4. 获取司机信息


结论

*后,我想给出我们在后端系统中总结的经验:

  1. UDP + Protobuf 以节省数据

  2. 内存存储

  3. R 树获取*近的司机

  4. LRU 缓存用于存储*后的 n 个位置

  5. OSRM 用于地图匹配和定制路线

您可以在 github [5] 上查看上面整个过程的源代码。现在功能还比较简单,但实现了文章中描述的许多功能。

参考资源

  1. GIF 动画下载:https://cdn-images-1.medium.com/max/1600/1*nI6cNApASR1mg6F5Sjgp7Q.gif

  2. https://cdn-images-1.medium.com/max/1600/1*KfGB1SARPoqOUtPtl4NNBg.gif

  3. http://weibo.com/10503/24F1QpDmL

  4. https://github.com/dhconnelly/rtreego

  5. https://github.com/maddevsio/openfreecabs

  6. 本文英文原文:https://blog.maddevs.io/how-we-built-a-backend-system-for-uber-like-map-with-animated-cars-on-it-using-go-29d5dcd517a#.npo2x5788

推荐阅读

本文由高可用架构翻译,转载请注明出处,技术原创及架构实践文章,欢迎通过公众号菜单「联系我们」进行投稿。

高可用架构

改变互联网的构建方式


长按二维码 关注「高可用架构」公众号