关注我的公众号:【编程朝花夕拾】,可获取首发内容。
1、引言
京东外卖于2025年2月11日正式开放,标志着京东正式进军外卖行业。浏览页面时,在筛选项里面看到了「距离最近」的筛选项,点了一下就按照商户和当前位置正序排列了。

这个功能其实其他外卖类、旅游类、地图类的APP中经常看到。今天我们一起聊一下背后的技术方案。
2、背后的技术
地理位置(Geolocation
)距离的不再是简单的距离比较,而是实时的经纬度的计算。因为你在不同的地方,商家离你的位置都是变化的。
Geolocation
的简称是GEO
。地球经纬度,将地球看做一个球体。将A、B两点的作为一个剖面,就会得到一个圆形,A、B两点的之间的距离不再试一条直线,而是一条弧线。两点的距离就是这个弧长。

常用的计算方式,离不开数学的三角函数:
- 球面余弦定理:适用于远距离计算,但近距离可能有舍入误差
- Vincenty公式:基于椭球模型,精度更高但计算复杂
- Haversine公式:平衡精度与复杂度的最佳选择,适用于导航、地图服务等常见场景
了解一下晦涩难懂的Haversine
公式:

看着复杂,其实按照参数代入即可。当然这个不是今天的目的。
Java作为一门面向对象的语言,可以不用关注具体的实现,只要有现成的工具,调用即可。
3、问题难点
了解了经纬度的计算,无论是我们自己编码、套用公式还是使用一些在线工具根据两点计算出距离,这些都不能满足类似「京东外卖」这样的排序。
因为排序功能是针对全量的数据。如果单纯的计算出距离,不说海量万级数据都有可能将内存榨干,更不用说影响用户体验了。
难点来了:如何通过经纬度实时计算给万级数据排序呢?
4、Mysql 5.7.6+
Mysql
是大部分程序员都会接触的数据库,用来存储数据。Mysql
从 5.7.6 版本 开始正式支持基于地理坐标系(如WGS84)的经纬度计算功能,关键改进是引入了 ST_Distance_Sphere
函数,用于计算球面距离(即地球表面的实际距离)。

数据准备:
上海(POINT(121.4737, 31.2304))、北京(POINT(116.4074, 39.9042))
mysql
SELECT ST_Distance_Sphere(
POINT(116.4074, 39.9042), -- 北京
POINT(121.4737, 31.2304) -- 上海
) AS distance_meters;
-- 结果约为 1,066,000 米(约1066公里)
距离正序排列的使用:
mysql
-- 单位米
SELECT
id,
latitude,
longitude,
ST_DISTANCE_SPHERE(
POINT(target_longitude, target_latitude),
POINT(longitude, latitude)
) AS distance
FROM table_name
ORDER BY distance ASC;
5、Redis GEO
Redis中的GEO
数据类型是Redis3.2.0
开始有的。

实战用到的函数:GEORADIUS
shell
# 根据当前经纬度获取方圆范围内的成员
GEORADIUS key longitude latitude radius M | KM | FT | MI [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count [ANY]] [ASC | DESC] [STORE key | STOREDIST key]
参数含义:
- WITHCOORD 获取经纬度
- WITHDIST 获取距离
- WITHHASH 获取成员Hash
- COUNT 限制罗列的成员数量
- ASE | DESC 排序
- STORE key 将返回的地理位置信息保存到一个key里面
- STOREDIST key 将返回的目标距离保存到一个key里面
实战:
假设当前的地点位置:上海(POINT(121.4737, 31.2304)
shell
# 查询cities这个key中距离上海(POINT(121.4737, 31.2304) 50公里以内的元素,并正序排列
GEORADIUS cities 121.4737 31.2304 50 KM WITHDIST ASC
像了解关于Redis GEO更多知识可以查看以往文章:
mp.weixin.qq.com/s/MbQXBvN2k...
6、小结
上面介绍两种最常用且简单的方法,你还知道其他方法么?评论区留言讨论。
方法再多,只不过使我们使用的工具或者手段。日常中你可能平时常见的一个小功能,背后可能都有极其复杂的技术支撑。比如我们进场使用的地图,已经接入北斗导航,背后的技术更是不言而喻。