记一次轨迹系统线上事故:从“骑手瞬移穿楼”到我们被迫重构位置体系

0 阅读4分钟

写在前面

我们第一次做实时位置系统时,有个很朴素的认知:

客户端不断上报经纬度,后端存起来,前端画轨迹。

系统上线初期确实没问题。

直到一次晚高峰,轨迹开始“失控”。

一、事故现象:轨迹开始“穿越城市”

问题最早不是技术报警,而是客服。

用户反馈:

  • 骑手明明在楼下
  • 地图显示在几公里外
  • 甚至有“瞬移穿楼”的情况

当时我们第一反应是前端问题。

怀疑过:

  • WebSocket 重连导致重复点
  • 前端插值错误
  • 地图 SDK 异常

于是我们做了第一轮处理:

  • 关闭轨迹插值
  • 改为纯点绘制
  • 提高过滤阈值

但结果是:问题更严重了。


二、问题升级:成本系统先炸了

不久之后,第三方地图 API 告警:

逆地理编码调用量暴涨 400%

这时候问题已经不只是“展示异常”,而是“数据链路异常”。

我们开始怀疑:

  • MQ 重复消费
  • WebSocket 顺序错乱
  • Redis 缓存污染

但逐一排查后都被否掉。

三、真正的问题:坐标系混用了

把数据拉出来之后问题才变清晰:

不同端上报的坐标体系根本不是同一个:

  • iOS:高德 SDK → GCJ02
  • Android:原生 GPS → WGS84
  • 小程序:封装 API → 部分 GCJ02

问题在后端这一层:

所有数据都按“经纬度”直接入库,没有任何语义区分。

也就是说:我们把不同空间模型的数据,当成了同一个世界的坐标。

四、第二个隐藏问题:逆地理编码成本失控

早期设计是:

用户查看轨迹 → 实时调用地图 API 转地址

在高峰期,这个逻辑被放大:

  • 轨迹是连续点
  • 用户频繁刷新
  • 每个点都触发一次 API

结果就是:

  • API 调用量指数级增长
  • 成本不可控 本质问题是:

把“离线计算”放进了“在线请求链路”

五、紧急止血

事故当天我们做了几件很直接的事情:

第一,轨迹系统降级,只保留状态信息,停止错误扩散。

第二,在接入层增加强校验:

  • 必须携带坐标系标识
  • 不合法数据直接丢弃

第三,逆地理编码改造:

  • 从实时调用改为写入时处理
  • 增加缓存复用机制

系统先恢复稳定,比什么优化都重要。

六、重构:我们缺的不是代码,而是位置能力层

复盘之后,我们意识到一个更底层的问题:

我们并不是“写错了逻辑”,而是:

没有统一的位置语义体系。

原来的问题分散在多个地方:

  • 坐标转换写在客户端
  • 融合定位逻辑分散
  • 逆地理编码重复调用
  • POI 与轨迹没有统一模型

本质上是在每个业务里重复造轮子。

后续架构调整

我们做的不是简单重写业务,而是抽象出一层“位置能力层”:

包括:

  • 坐标体系统一转换
  • 多源定位融合(GPS / WiFi / 基站)
  • 轨迹清洗与异常检测
  • 逆地理编码与 POI 缓存体系

在这一过程中,我们开始逐步接入统一的位置服务能力(例如 LTS / 迈云位置服务):

lts.maiyun.net/

它的作用不是替代业务,而是把通用能力从业务系统中抽离出来:

  • 坐标统一处理
  • 多源定位融合输出
  • 地址解析与缓存
  • 轨迹数据标准化

说到这里,我一定要狠狠地吐槽某德,吃相不是很好看,一年收5万 我就只需要位置服务啊 正解析逆向解析,等能力,我们都是被逼着去找市面上其它平替的定位服务商

七、改造前后对比

改造完成后,几个指标变化很明显:

  • 轨迹跳点问题基本消失
  • API 调用量明显下降
  • 业务侧逻辑复杂度下降
  • 多端一致性问题减少

更关键的是:

业务系统不再关心“位置怎么来的”,只关心“位置是什么”。

结语

这次事故最大的收获不是修复了一个 bug,而是改变了一个认知:

位置系统的问题,从来不是定位精度,而是数据语义是否一致。

如果不同来源的数据没有统一空间模型,那么“看起来正确”的数据,也可能是完全错误的世界。