年初我们接了一个同城配送平台的改造需求。
需求看起来很简单:
用户下单之后,能够实时看到骑手的位置、距离还有预计到达时间。
产品经理当时觉得这就是个地图功能,无非是在地图上放个小图标动一动。
结果真正落地的时候才发现,整个链路里面最复杂的反而是位置服务。
上线前后,我们陆续踩了地址解析、GPS失效、坐标系混乱、逆地址解析成本失控等几个大坑。
这篇文章把整个过程记录下来,希望能给正在做类似系统的同学一些参考。
系统整体架构
先看看最终的整体架构。
商家录入地址
│
▼
地址解析(获取坐标)
│
▼
订单创建
骑手接单
│
▼
GPS定位
│
├─成功
│
▼
坐标转换
│
▼
逆地址解析
│
▼
轨迹存储
│
▼
推送用户
GPS失败
│
▼
融合定位(WiFi+基站)
│
▼
逆地址解析
│
▼
轨迹存储
│
▼
推送用户
刚开始我们以为位置服务无非就是经纬度。
后来发现真正需要解决的问题其实有四类:
- 商家地址如何转坐标
- GPS失效怎么办
- 多种坐标系如何统一
- 如何降低逆地址解析成本
而且这几个问题往往是一起出现的。
第一个坑:商家地址解析成功率远比想象中低
刚开始做的时候,我们认为地址解析是一件非常简单的事情。
用户填地址。
系统调一下接口。
返回经纬度。
结束。
结果上线一周之后,运营同事反馈:
有部分订单骑手接单后无法直接导航。
排查日志发现,大量商家填写的地址根本不是标准地址。
例如:
万达负一楼
停车场出口旁边
工商银行背后
xx路88号旁边
老地方
这种地址对于人类来说很好理解。
对于机器来说基本属于谜语。
我们统计了接近3万条商家地址数据。
发现:
- 标准地址解析成功率接近98%
- 非标准地址解析成功率不足80%
别小看这十几个百分点。
对于骑手来说就是:
接单后无法一键导航。
只能手动搜索。
配送效率直接下降。
后来我们把流程改成:
原始地址
▼
地址标准化
▼
地址解析
▼
坐标入库
这样做之后解析成功率明显提升。
目前我们的位置服务层直接接入了LTS的Geocoding能力。
除了返回坐标之外,还会返回标准化后的地址和行政区划信息。
例如:
{
"lat": 30.658,
"lng": 104.065,
"address": {
"name": "四川省成都市锦江区东大街88号"
}
}
这样后续做区域统计、运营分析时也方便很多。
最大的经验就是:
不要相信商家录入的数据质量。
地址解析失败一定要记录日志。
定期人工修正。
否则脏数据会越来越多。
第二个坑:骑手进商场以后位置不动了
这个问题是用户投诉最多的。
用户反馈:
骑手明明已经到商场了
地图上的位置还停留在500米外
刚开始我们以为是App卡死。
后来排查发现:
根本原因是GPS失效。
例如:
- 商场内部
- 地下停车场
- 写字楼电梯间
- 地铁口附近
这些场景GPS信号非常差。
很多时候根本拿不到有效坐标。
最初我们的逻辑是:
GPS失败
▼
不上报
结果用户看到的就是:
位置长时间不更新。
后来改成三级降级链路:
GPS
▼
融合定位
▼
定位失败状态
流程变成:
优先GPS
GPS精度低于阈值
自动切换WiFi+基站定位
仍然失败
上报定位失败状态
这里有个有意思的数据。
我们统计了一周线上数据:
用户投诉的位置异常订单中:
- 87%发生在室内场景
- 9%发生在地下停车场
- 4%其它情况
真正发生在室外GPS漂移的反而很少。
后来增加融合定位之后:
位置中断时间从平均3分钟下降到30秒以内。
用户投诉量下降非常明显。
这里我们没有自己维护WiFi和基站数据库。
直接通过LTS的位置融合接口获取结果。
对于业务团队来说省了很多基础设施建设成本。
第三个坑:坐标系不统一,轨迹回放直接崩了
这是整个项目里最痛的一次事故。
当时系统已经上线。
客服突然反馈:
有些骑手轨迹非常诡异。
打开轨迹回放之后发现:
骑手在马路上
↓
瞬移到小区里面
↓
又跳回主干道
↓
再穿过一栋楼
看起来像开了瞬移外挂。
排查两天后终于找到原因。
系统里混进了三种坐标:
GPS
WGS84
融合定位
GCJ02
历史系统
GCJ02 + BD09
全部存在同一张表。
而且没有记录坐标系字段。
导致回放时直接混用。
地图渲染自然乱套。
后来我们花了接近两周时间做数据迁移。
把历史数据全部重新转换。
从那以后团队立下一个铁规矩:
任何位置数据
必须带坐标系字段
数据库设计也改成:
coord_system TINYINT NOT NULL
并且在服务层强制校验。
最终统一规范:
所有数据入库前
全部转换GCJ02
数据库只存GCJ02
前端不做任何转换
事实证明:
提前统一规范的成本远远小于事后迁移。
第四个坑:逆地址解析把配额打爆了
刚开始我们做得非常直接。
用户每次刷新位置:
获取坐标
▼
调用逆地址解析
▼
返回地址
看起来没问题。
直到运营突然发现:
位置服务费用开始异常增长。
统计后发现:
骑手上报频率:15秒
用户刷新频率:5秒
在线骑手:100+
意味着同一个位置可能被反复解析几十次。
大量请求完全重复。
后来我们把逻辑改成:
写路径解析
读路径直接读取
即:
骑手上报位置
▼
逆地址解析
▼
位置+地址一起存库
用户读取:
直接查库
不再实时解析。
改造之后调用量下降超过60%。
后来又增加了一层GeoHash缓存:
GeoHash(7)
▼
Redis
▼
逆地址缓存
同一个区域直接复用解析结果。
最终逆地址解析调用量再次下降接近40%。
这类优化看起来不起眼。
但对于高频位置业务来说非常关键。
最终的位置能力架构
经过几轮重构之后。
整套位置能力最终变成:
商家录入
▼
地址解析
▼
标准化地址+坐标
骑手接单
▼
GPS定位
▼
坐标统一转换GCJ02
▼
逆地址解析
▼
轨迹存储
▼
消息推送
GPS失败
▼
融合定位
▼
GCJ02坐标
▼
逆地址解析
▼
轨迹存储
▼
消息推送
用户查看
▼
读取轨迹表
▼
直接返回坐标+地址
整个过程中我们实际上没有自己维护:
- 地址解析库
- 基站数据库
- WiFi指纹库
- 坐标转换服务
这些能力最终都沉淀到统一的位置服务层。
目前正地址解析、逆地址解析、坐标转换、融合定位几块能力都统一接在LTS上。
最大的收益其实不是省钱。
而是避免不同服务商之间的数据格式和坐标体系差异。
维护起来简单很多。
总结
做骑手实时追踪系统最大的坑。
其实从来不是地图展示。
真正容易出问题的是:
- 地址脏数据
- GPS失效
- 坐标系混乱
- 逆解析成本失控
这些问题单独出现都不严重。
但同时出现的时候。
用户感知会非常明显。
我们这套系统上线半年之后。
日均处理数万次位置更新。
回头看最重要的几个经验其实只有四条:
第一,坐标系从第一天统一。
不要让多种坐标系混进数据库。
第二,设计完整降级链路。
GPS → 融合定位 → 失败状态。
每一步都要有明确处理逻辑。
第三,逆解析走写路径。
不要把成本放到读请求上。
第四,地址解析失败必须记录。
脏数据不会自己消失。
只会越积越多。




