基于地理位置的附近人搜索功能需要存储用户的经纬度信息,再通过距离计算筛选出指定范围内的用户,不同存储和计算方案各有优劣,下面分别介绍两种主流实现方式。

一、功能基础准备
1. 数据库表结构设计
首先需要创建用户位置表,存储用户的唯一标识和经纬度信息,MySQL版本需要5.7及以上才支持空间函数功能。
CREATE TABLE user_location (
user_id BIGINT PRIMARY KEY COMMENT '用户ID',
longitude DOUBLE NOT NULL COMMENT '经度',
latitude DOUBLE NOT NULL COMMENT '纬度',
location POINT NOT NULL COMMENT '空间位置点',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '位置更新时间',
SPATIAL INDEX idx_location (location)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户位置表';
2. 依赖引入
Java项目需要引入MySQL驱动和Redis客户端依赖,这里使用Jedis作为Redis操作工具。
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.4.3</version>
</dependency>
二、基于MySQL空间函数实现
1. 写入用户位置数据
插入用户位置时,需要同时构造POINT类型的空间数据,这里使用ST_GeomFromText函数生成空间点。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
public class MysqlLocationService {
private static final String URL = "jdbc:mysql://127.0.0.1:3306/test_db?useUnicode=true&characterEncoding=utf8";
private static final String USER = "root";
private static final String PASSWORD = "123456";
public void saveUserLocation(Long userId, Double longitude, Double latitude) throws Exception {
Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
// 构造空间点SQL,格式为POINT(经度 纬度)
String sql = "INSERT INTO user_location (user_id, longitude, latitude, location) VALUES (?, ?, ?, ST_GeomFromText(?, 4326))";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setLong(1, userId);
ps.setDouble(2, longitude);
ps.setDouble(3, latitude);
ps.setString(4, "POINT(" + longitude + " " + latitude + ")");
ps.executeUpdate();
ps.close();
conn.close();
}
}
2. 查询附近的人
使用ST_Distance_Sphere函数计算两个空间点之间的球面距离,单位是米,筛选出距离小于指定阈值的用户。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
public class MysqlLocationService {
// 省略连接配置和保存方法
public List<Long> findNearbyUsers(Double centerLng, Double centerLat, Double maxDistance) throws Exception {
Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
// 计算距离并筛选,ST_Distance_Sphere返回球面距离(米)
String sql = "SELECT user_id, longitude, latitude, " +
"ST_Distance_Sphere(location, ST_GeomFromText(?, 4326)) AS distance " +
"FROM user_location " +
"WHERE ST_Distance_Sphere(location, ST_GeomFromText(?, 4326)) <= ? " +
"ORDER BY distance ASC";
PreparedStatement ps = conn.prepareStatement(sql);
String centerPoint = "POINT(" + centerLng + " " + centerLat + ")";
ps.setString(1, centerPoint);
ps.setString(2, centerPoint);
ps.setDouble(3, maxDistance);
ResultSet rs = ps.executeQuery();
List<Long> userIds = new ArrayList<>();
while (rs.next()) {
userIds.add(rs.getLong("user_id"));
}
rs.close();
ps.close();
conn.close();
return userIds;
}
}
三、基于Redis GEO指令实现
1. 写入用户位置数据
Redis GEO使用有序集合存储地理位置信息,通过GEOADD指令添加成员的经纬度,成员可以是用户ID字符串。
import redis.clients.jedis.Jedis;
public class RedisGeoService {
private static final String GEO_KEY = "user_geo_location";
public void saveUserLocation(String userId, Double longitude, Double latitude) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
// GEOADD 参数:key 经度 纬度 成员
jedis.geoadd(GEO_KEY, longitude, latitude, userId);
jedis.close();
}
}
2. 查询附近的人
使用GEORADIUS指令查询指定坐标范围内的成员,同时可以返回每个成员与中心点的距离,单位支持米、千米等。
import redis.clients.jedis.Jedis;
import redis.clients.jedis.GeoRadiusResponse;
import redis.clients.jedis.params.GeoRadiusParam;
import java.util.List;
public class RedisGeoService {
// 省略key和保存方法
public List<String> findNearbyUsers(Double centerLng, Double centerLat, Double radius, String unit) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
// GEORADIUS 参数:key 经度 纬度 半径 单位 额外参数(这里返回距离)
List<GeoRadiusResponse> responses = jedis.georadius(
GEO_KEY,
centerLng,
centerLat,
radius,
unit,
GeoRadiusParam.geoRadiusParam().withDist()
);
List<String> userIds = new ArrayList<>();
for (GeoRadiusResponse response : responses) {
userIds.add(response.getMemberByString());
}
jedis.close();
return userIds;
}
}
四、两种方案对比
| 对比维度 | MySQL空间函数 | Redis GEO |
|---|---|---|
| 查询性能 | 依赖空间索引,大数据量下性能一般 | 基于有序集合实现,查询性能极高 |
| 数据持久化 | 原生支持,数据可靠性高 | 需要额外配置持久化策略 |
| 适用场景 | 数据量小、需要强一致性的场景 | 高并发、实时性要求高的场景 |
| 距离计算精度 | 支持球面距离计算,精度高 | 底层使用Geohash实现,精度略低 |
五、方案选择建议
如果业务场景是低频查询、数据量不大且需要保证位置数据的强一致性,优先选择MySQL空间函数方案。如果是高并发的社交类应用,需要快速返回附近人结果,建议选择Redis GEO方案,同时可以定期将Redis中的位置数据同步到MySQL做持久化备份,兼顾性能和数据可靠性。