Strcpy

如何设计一个几十万TPS的实时位置上报系统

源代码/数据集已上传到 Github - posts

有100w滴滴司机,司机每3s上报一次自己的位置。请你设计一个系统,能够实时将司机的位置存储进系统,并且能够给另外一个系统实时显示路径(每3秒刷新一次最新点),历史数据能够随时查询。

在脉脉上看到这个题目,挺多人讨论。刚好在前司早期也负责做过这块的东西,复刻一下以前的解决方案。

需求分析

实时位置收集
需求简单拆解可以分为明面上的需求:

  1. 位置上报(我之前负责的业务为1s一次上报)
  2. 业务实时路径查询
  3. 业务历时可查

以及需要解决存在问题和附加需求。

  1. 提交的数据怎么防止被恶意刷接口,影响数据准确?
  2. 接口响应时间必须要低,以支持几十万的请求。
  3. 需要为算法提供数据,以支持路径优化算法在未来变得更加高效。
  4. 需要为风控提供数据,以支持业务风控部门内部反作弊的需求。
  5. 距离计算,以提供给用户查询实时的距离有多少,以当前速度还需要多久送达。

所以就会多出几个有必要去解决的问题。

  1. 数据鉴权,防攻击脏数据
  2. 路径优化
  3. 路径反作弊
  4. 距离计算

技术方案

在涉及点位上报前还有生成订单的环节。在生成订单时,订单的目标地址会被推送到算法路径规划的服务中,进行最短路径输出。在前司的业务场景中,因一个配送员存在顺路的情况,会以最大可用运力做路径规划以达到人效的最高。

因为有最大可用运力的前提,所以某些情况下单个订单看,可以存在路径非最优的情况,另外此处非本次设计方案所涉及到的范围,只简单带过。

由路径规划的服务提早规划好路径后,配送员如果按照规划路径行走,路径服务的压力会非常小。但是实际情况会遇到交通管制,客户电话联系修改配送地址,配送员突然有私事临时解决几分钟,这种场景也会给整个链路带来一些影响。

数据规模

订单量取一个业界相对合理的数量,但非实际数量。
订单量:200w

涉及服务

  • demo-gateway-service
  • demo-auth-service
  • demo-gps-servce
  • demo-dc-service
  • algo-route-service
  • demo-risk-service

流程图

生成最短路径

在订单生成后,订单数据会被同步到算法路径规划服务中。服务会根据地址和站点的位置,计算出相对合理的路线。但是在配送之前,路径规划完成之后,用户再一次修改地址,已经计算完成的最短路径也需要重新计算。

提早计算出来路线需要存在数据库中,用于用户打开App时在用户界面上展示配送的路线。

另外当配送员没有按照实际预测的路线进行前进时,在误差范围内的点位预计时间不会发生变更,只有运动路线展示上进行实际点位连接。

当遇到未知的绕路行为,算法路径规划会二次计算最短路径,重新推送。预计N分钟内送达也会进行相应的调整。

坐标上报流程

配送员在点击开始配送一批订单时,就会开始每秒上报一次坐标点。上报的维度为配送员,在上报的阶段不会与订单产生关联。坐标点在网关处进行鉴权以及参数校验,以防止数据被恶意篡改和被攻击。之后数据就被推入kafka供订阅的相关服务消费。

位置服务数据关联

位置服务在消费到点位数据后,将该配送员位置写入Redis,供用户在App端查看配送员位置以及运力系统在运力预测时参考。(每个手机的定位精度都可能存在偏移,点位上报后不仅需要保存原始上报数据,还需要提供修正后的点位。比如,定位在一条街道上,可以拟合到该街道的经纬度的范围上,让配送的数据在用户端展示得更加真实)

Redis数据写入后获取配送员正在配送的订单,为每个订单增量写入点位,用于正在配送中的历史路径展示。

城市道路在地图上实际往往是以直线展示,当一段直线路程被经历,该段路程就可以以两个点位进行表示。这在秒级别点位上报的数据下,可以大大降低C端接口拉取到的数据量。

数据量评估

以下为部分数据结构:

1
2
3
4
5
6
7
8
9
10
type Pos struct {
Lat float64 `json:"lat"`
Lng float64 `json:"lng"`
}

type DeliveryPath struct {
CurrentPoint Pos `json:"currentPoint"`
ShortestPath [][]Pos `json:"shortestPath"`
ActualPath [][]Pos `json:"actualPath"`
}

根据业务场景,数据分为热点数据,冷数据,归档数据。

热点数据

热点数据里面包含了点位数据,数据量较多。以市面上常见的社区团购电商的30分钟送达为例,订单下单后有20分钟的配送时间,才能保证在30分钟内到达用户手中。

每个订单配送时间20分钟,一天200万订单,从早上6点到晚上11点不区分高峰期和低峰期,计算出每20分钟3.92万订单。预测高峰期为平均值的5倍,低峰期为平均值的0.3倍。可得峰值19.6万单/20分钟,预计每秒订单维度产生19.6万个点位信息。

配送员每次顺路配送3~10单,每秒需要上报1.96万~6.53万个点位数据。

  1. 配送员实时坐标点信息,Redis

空间:57byte * 100000 约等于 5.7MB

1
{"lng":121.472644,"lat":31.231706,"timestamp":1656433895}
  1. 当前路线的点位数据,Redis

该数据用于判断是否已经进入了第二个路线,当进入第二个路线,则将上一个路线的头尾点位写入订单路线点位信息表内。后清空Redis中上一个路线的数据。当订单配送完毕,该订单的点位就会被清除。因此Redis中可能会存在20万个订单的路线点位数据。

空间:90byte * 120 * 200000 约等于 2160MB

1
{"lng":121.472644,"lat":31.231706,"timestamp":1656433895,"orderNum":"2206281180551915103"}
  1. 订单路线点位信息,Mongo

直线只有两个坐标点的情况,正在进行中的路线为上一段路线的结束点位加当前实时点位。社区团购路线按照平均10个点位。

空间:90byte * 10 * 2000000 约等于 1800MB

1
{"lng":121.472644,"lat":31.231706,"timestamp":1656433895,"orderNum":"2206281180551915103"}
  1. 订单每秒点位信息,Mongo

每秒点位数据类似于日志,不会用于业务的查询。落库后会有数仓ETL数据后进行其他业务的数据分析,例如配送员申诉问题等。

根据订单号进行hash分表,表名 xxx_20220628_${hashcode}

空间:90byte * 1200 * 2000000 约等于 21600MB

1
{"lng":121.472644,"lat":31.231706,"timestamp":1656433895,"orderNum":"2206281180551915103"}
冷数据

当订单配送结束,配送点位的数据可以当成冷数据处理。在热点表里面呆满两天就可以放心转入冷数据表,表结构与订单路线点位信息一致。最直接的目的就是减少热点表的数据,避免数据插入索引重排导致性能降低以及索引层级过深导致查询性能降低。

数据按照订单号进行拆表存放,转入冷数据后查询都可以按照正常查询走,冷数据位置信息的回查率不高于1%。所以一般保持根据业务情况保持30~90天可实时查询即可。

如果很有必要实时查询更长时间的数据,则可以对超过90天的数据放到存储价格不高独立的冷数据库。

归档数据

当超过30~90天后,就可以将数据转入归档表进行归档存放。只会有一个订单号作为索引进行检索表名,当订单有被查询的需求,会返回消息告知用户,N分钟后进行查询。此时异步任务需要从归档表里面抽取数据进行准备,等待用户查询。

总结

以上方案为之前项目复刻方案,与滴滴3秒100w可能存在一些请求量级上的差异。但思路应该大同小异。

免责

公司千千万,复刻的方案如有雷同纯属巧合。文章中业务量数据已脱敏,未包含任何商业数据,如有雷同相似,公司业务动态调整纯属巧合。


edit this page last updated at 2022-07-22

Big Image