马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
目次
弁言
Redis GEO下令概述
什么是GEO下令?
重要下令详解
下令应用示例
添加地点信息
查询两地间隔
查询附近的都会
实现"查找附近的人"功能
功能需求与实现思绪
根本需求
实现思绪
下令实现方案
存储用户位置
查询附近的用户
Java代码实现详解
使用Redis GEO的上风与留意事项
上风
留意事项
弁言
在移动互联网期间,基于地理位置的服务已成为浩繁应用的标配功能。无论是打车软件、外卖平台还是交际应用,"附近的XX"功能险些无处不在。这类功能的核心技能寻衅在于:怎样高效存储地理位置数据并举行快速检索?Redis 3.2版本引入的GEO(地理空间)下令集完满办理了这一题目,为开辟者提供了简单高效的地理位置数据处理处罚方案。
本文将深入浅出地先容Redis GEO下令及其工作原理,通过实际案例和代码示例,资助你轻松实现"查找附近的人"等地理位置干系功能。无论你是Redis新手还是有履历的开辟者,都能从中获取有代价的信息。
Redis GEO下令概述
什么是GEO下令?
GEO是"Geolocation"(地理定位)的简写,Redis GEO是Redis专门为地理位置信息存储和检索筹划的下令集。它答应我们将经纬度坐标存储到Redis数据库中,并支持按间隔查询、盘算两点间间隔等多种地理空间使用。
底层实现上,Redis GEO使用了地理空间索引算法(Geohash),将二维的经纬度转换为一维的字符串,并通过Redis的有序聚集(Sorted Set)来存储,这使得地理位置的存取和盘算变得非常高效。
重要下令详解
Redis GEO重要提供了以下几个核心下令:
- # 将指定的地理空间位置(经度、纬度、名称)添加到指定的key中
- # 可以一次添加多个位置
- GEOADD key longitude latitude member [longitude latitude member ...]
复制代码
- # 返回两个给定位置之间的距离
- # unit参数指定返回值的单位,可以是m(米)、km(千米)、mi(英里)或ft(英尺)
- GEODIST key member1 member2 [unit]
复制代码
- # 返回一个或多个位置元素的Geohash表示
- # Geohash是一种将经纬度编码为字符串的方法
- GEOHASH key member [member ...]
复制代码
- # 返回一个或多个位置元素的Geohash表示
- # Geohash是一种将经纬度编码为字符串的方法
- GEOHASH key member [member ...]
复制代码
- # 返回指定名称位置的经纬度坐标
- GEOPOS key member [member ...]
复制代码
- # 以给定的经纬度为中心,返回键中包含的位置元素当中,与中心的距离不超过给定半径的所有位置元素
- GEORADIUS key longitude latitude radius unit [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]
复制代码 WITHDIST: 在返回位置元素的同时,将位置元素与中心之间的间隔也一并返回
WITHCOORD: 将位置元素的经度和纬度也一并返回
WITHHASH: 以52位无符号整数的情势返回位置元素的geohash值(重要用于调试)
COUNT n: 限定返回的纪录数量
ASC|DESC: 根据中心的位置,按照从近到远(ASC)或从远到近(DESC)的序次返回位置元素
- # 在指定范围内搜索,范围可以是圆形或矩形
- GEOSEARCH key [FROMMEMBER member] [FROMLONLAT longitude latitude] [BYRADIUS radius unit] [BYBOX width height unit] [WITHDIST] [WITHCOORD] [WITHHASH] [COUNT count] [ASC|DESC]
复制代码
- GEOSEARCHSTORE: 在指定范围内搜刮并将效果存储
- # 与GEOSEARCH功能相同,但可以将结果存储到指定的key中
- GEOSEARCHSTORE destination key [FROMMEMBER member] [FROMLONLAT longitude latitude] [BYRADIUS radius unit] [BYBOX width height unit] [WITHDIST] [WITHCOORD] [WITHHASH] [COUNT count] [ASC|DESC]
复制代码 这些下令共同构成了一个完备的地理空间数据处理处罚工具集,可以或许满意大多数基于位置的服务需求。
下令应用示例
让我们通过一个详细的例子来明确GEO下令的使用:
添加地点信息
- # 上述命令将"东京"和"吉隆坡"两个城市的经纬度信息添加到名为"locations"的地理空间集合中。
- GEOADD locations 139.781210 35.774426 "东京" 101.653962 5.205122 "吉隆坡"
复制代码 查询两地间隔
- # 这个命令会返回东京和吉隆坡之间的距离(单位:公里)。
- GEODIST locations "东京" "吉隆坡" km
复制代码 查询附近的都会
- # 这个命令会查找距离指定坐标点(经度139.0,纬度35.0)1000公里范围内的所有城市,并同时返回它们与中心点的距离。
- GEORADIUS locations 139.0 35.0 1000 km WITHDIST
复制代码 实现"查找附近的人"功能
"查找附近的人"是移动应用中的常见功能,下面我们将详细解说怎样使用Redis GEO下令来实现。
功能需求与实现思绪
根本需求
- 存储每个用户的地理位置信息(经纬度)
- 可以或许查询指定用户附近肯定范围内的其他用户
- 返回的用户列表按照间隔排序
实现思绪
- 使用GEOADD下令将用户ID及其经纬度信息存储在Redis中
- 当必要查询"附近的人"时,使用GEORADIUS下令,以查询用户的位置为中心,指定半径范围举行搜刮
下令实现方案
假设我们正在开辟一个交际应用,必要实现广州市用户查找1000公里范围内其他用户的功能:
存储用户位置
- GEOADD user_location 113.267548 23.142979 "user1"
- GEOADD user_location 113.300000 23.150000 "user2"
- GEOADD user_location 114.057868 22.543099 "user3"
复制代码 查询附近的用户
- # 命令会返回距离广州市指定坐标1000公里范围内的所有用户,并显示他们与查询点的具体距离。
- GEORADIUS user_location 113.254325 23.144043 1000 km WITHDIST
复制代码 Java代码实现详解
下面是使用Java语言和Jedis客户端实现"查找附近的人"功能的代码示例:
- import redis.clients.jedis.GeoCoordinate;
- import redis.clients.jedis.GeoRadiusResponse;
- import redis.clients.jedis.GeoUnit;
- import redis.clients.jedis.Jedis;
- import redis.clients.jedis.params.GeoRadiusParam;
- import java.util.List;
- import java.util.Map;
- import java.util.HashMap;
- import java.util.stream.Collectors;
- /**
- * Redis GEO功能示例:实现"附近的人"功能
- *
- * @author Muller
- */
- public class RedisGeoDemo {
- private static final String USER_LOCATION_KEY = "user_location";
-
- /**
- * 存储用户地理位置信息
- *
- * @param userId 用户ID
- * @param longitude 经度
- * @param latitude 纬度
- * @param jedis Redis连接
- * @return 添加成功的数量
- */
- public static Long saveUserLocation(String userId, double longitude, double latitude, Jedis jedis) {
- try {
- return jedis.geoadd(USER_LOCATION_KEY, longitude, latitude, userId);
- } catch (Exception e) {
- System.err.println("保存用户位置信息失败: " + e.getMessage());
- return 0L;
- }
- }
-
- /**
- * 批量存储多个用户的地理位置信息
- *
- * @param userLocations 用户位置Map,key为用户ID,value为经纬度坐标
- * @param jedis Redis连接
- * @return 添加成功的数量
- */
- public static Long saveUserLocations(Map<String, double[]> userLocations, Jedis jedis) {
- try {
- Map<String, GeoCoordinate> memberCoordinateMap = new HashMap<>();
-
- for (Map.Entry<String, double[]> entry : userLocations.entrySet()) {
- String userId = entry.getKey();
- double[] coordinates = entry.getValue();
- memberCoordinateMap.put(userId, new GeoCoordinate(coordinates[0], coordinates[1]));
- }
-
- return jedis.geoadd(USER_LOCATION_KEY, memberCoordinateMap);
- } catch (Exception e) {
- System.err.println("批量保存用户位置信息失败: " + e.getMessage());
- return 0L;
- }
- }
-
- /**
- * 查询附近的人
- *
- * @param longitude 经度
- * @param latitude 纬度
- * @param radius 半径
- * @param jedis Redis连接
- * @return 附近用户ID列表
- */
- public static List<String> getNearbyUsers(double longitude, double latitude, double radius, Jedis jedis) {
- try {
- List<GeoRadiusResponse> responses = jedis.georadius(
- USER_LOCATION_KEY,
- longitude,
- latitude,
- radius,
- GeoUnit.KM,
- GeoRadiusParam.geoRadiusParam().withDist().sortAscending()
- );
-
- return responses.stream()
- .map(GeoRadiusResponse::getMemberByString)
- .collect(Collectors.toList());
- } catch (Exception e) {
- System.err.println("查询附近用户失败: " + e.getMessage());
- return List.of();
- }
- }
-
- /**
- * 获取用户详细地理信息(包含距离)
- *
- * @param longitude 经度
- * @param latitude 纬度
- * @param radius 半径
- * @param jedis Redis连接
- * @return 附近用户详细信息列表
- */
- public static List<UserGeoInfo> getNearbyUsersWithDistance(double longitude, double latitude, double radius, Jedis jedis) {
- try {
- List<GeoRadiusResponse> responses = jedis.georadius(
- USER_LOCATION_KEY,
- longitude,
- latitude,
- radius,
- GeoUnit.KM,
- GeoRadiusParam.geoRadiusParam().withDist().withCoord().sortAscending()
- );
-
- return responses.stream()
- .map(response -> new UserGeoInfo(
- response.getMemberByString(),
- response.getDistance(),
- response.getCoordinate().getLongitude(),
- response.getCoordinate().getLatitude()
- ))
- .collect(Collectors.toList());
- } catch (Exception e) {
- System.err.println("查询附近用户详细信息失败: " + e.getMessage());
- return List.of();
- }
- }
-
- /**
- * 计算两个用户之间的距离
- *
- * @param userId1 用户1的ID
- * @param userId2 用户2的ID
- * @param jedis Redis连接
- * @return 两用户间距离(单位:公里),如果计算失败返回-1
- */
- public static double getDistanceBetweenUsers(String userId1, String userId2, Jedis jedis) {
- try {
- Double distance = jedis.geodist(USER_LOCATION_KEY, userId1, userId2, GeoUnit.KM);
- return distance != null ? distance : -1;
- } catch (Exception e) {
- System.err.println("计算用户距离失败: " + e.getMessage());
- return -1;
- }
- }
-
- /**
- * 获取用户的地理坐标
- *
- * @param userId 用户ID
- * @param jedis Redis连接
- * @return 用户坐标[经度,纬度],如果不存在返回null
- */
- public static double[] getUserPosition(String userId, Jedis jedis) {
- try {
- List<GeoCoordinate> positions = jedis.geopos(USER_LOCATION_KEY, userId);
-
- if (positions != null && !positions.isEmpty() && positions.get(0) != null) {
- GeoCoordinate pos = positions.get(0);
- return new double[] { pos.getLongitude(), pos.getLatitude() };
- }
-
- return null;
- } catch (Exception e) {
- System.err.println("获取用户坐标失败: " + e.getMessage());
- return null;
- }
- }
-
- /**
- * 用户地理信息包装类
- */
- public static class UserGeoInfo {
- private String userId;
- private double distance;
- private double longitude;
- private double latitude;
-
- public UserGeoInfo(String userId, double distance, double longitude, double latitude) {
- this.userId = userId;
- this.distance = distance;
- this.longitude = longitude;
- this.latitude = latitude;
- }
-
- public String getUserId() {
- return userId;
- }
-
- public double getDistance() {
- return distance;
- }
-
- public double getLongitude() {
- return longitude;
- }
-
- public double getLatitude() {
- return latitude;
- }
-
- @Override
- public String toString() {
- return "用户ID: " + userId +
- ", 距离: " + String.format("%.2f", distance) + "公里" +
- ", 坐标: [" + longitude + ", " + latitude + "]";
- }
- }
-
- /**
- * 示例用法
- */
- public static void main(String[] args) {
- // 这里仅用于演示,实际使用应通过连接池获取Jedis实例
- try (Jedis jedis = new Jedis("localhost", 6379)) {
- // 清除之前可能存在的测试数据
- jedis.del(USER_LOCATION_KEY);
-
- // 存储几个测试用户的位置(广州及周边城市的坐标)
- saveUserLocation("user1", 113.267548, 23.142979, jedis); // 广州
- saveUserLocation("user2", 114.057868, 22.543099, jedis); // 深圳
- saveUserLocation("user3", 113.030396, 22.938259, jedis); // 佛山
- saveUserLocation("user4", 116.397128, 39.916527, jedis); // 北京
-
- System.out.println("===== 查询广州周边1000公里范围内的用户 =====");
- List<String> nearbyUsers = getNearbyUsers(113.267548, 23.142979, 1000, jedis);
- System.out.println("附近的用户: " + nearbyUsers);
-
- System.out.println("\n===== 查询广州周边1000公里范围内的用户(包含距离信息) =====");
- List<UserGeoInfo> nearbyUsersWithDist = getNearbyUsersWithDistance(113.267548, 23.142979, 1000, jedis);
- nearbyUsersWithDist.forEach(System.out::println);
-
- System.out.println("\n===== 计算用户间距离 =====");
- double distance = getDistanceBetweenUsers("user1", "user2", jedis);
- System.out.println("广州(user1)到深圳(user2)的距离: " + String.format("%.2f", distance) + "公里");
-
- distance = getDistanceBetweenUsers("user1", "user4", jedis);
- System.out.println("广州(user1)到北京(user4)的距离: " + String.format("%.2f", distance) + "公里");
-
- System.out.println("\n===== 获取用户坐标 =====");
- double[] pos = getUserPosition("user1", jedis);
- if (pos != null) {
- System.out.println("用户user1的坐标: [" + pos[0] + ", " + pos[1] + "]");
- }
- }
- }
- }
复制代码 使用Redis GEO的上风与留意事项
上风
- 性能高效:Redis基于内存使用,地理位置查询性能极高
- 使用简单:GEO下令集筹划直观,轻易上手
- 功能完满:提供了从添加、查询到盘算间隔的完备功能集
- 可扩展性好:可以轻松处理处罚百万级别的POI(爱好点)数据
- 与Redis其他功能协同:可以连合Redis的缓存、事件等功能
留意事项
- 精度限定:GEO下令的精度受到Geohash算法的限定,对于必要极高精度的应用场景(如军事)大概不实用
- 内存斲丧:大量GEO数据会占用较多内存,必要公道规划Redis服务器资源
- 经纬度范围:Redis GEO只担当有用的经纬度范围(经度:-180到180,纬度:-85.05112878到85.05112878)
- 数据长期化:使用AOF长期化模式大概会导致重启时间延伸,需权衡数据安全性和重启速率
- 实用场景:最得当"附近的XX"这类不必要复杂地理形状盘算的场景,如需多边形地域盘算等高级地理信息功能,大概必要专业GIS体系
|