Redis GEO 下令详解:轻松实现“附近的人“功能

[复制链接]
发表于 2025-10-10 02:37:21 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
目次

弁言
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重要提供了以下几个核心下令:


  • GEOADD: 添加地理空间信息
  1. # 将指定的地理空间位置(经度、纬度、名称)添加到指定的key中
  2. # 可以一次添加多个位置
  3. GEOADD key longitude latitude member [longitude latitude member ...]
复制代码


  • GEODIST: 盘算两点间间隔
  1. # 返回两个给定位置之间的距离
  2. # unit参数指定返回值的单位,可以是m(米)、km(千米)、mi(英里)或ft(英尺)
  3. GEODIST key member1 member2 [unit]
复制代码


  • GEOHASH: 获取经纬度的Geohash表现
  1. # 返回一个或多个位置元素的Geohash表示
  2. # Geohash是一种将经纬度编码为字符串的方法
  3. GEOHASH key member [member ...]
复制代码


  • GEOHASH: 获取经纬度的Geohash表现
  1. # 返回一个或多个位置元素的Geohash表示
  2. # Geohash是一种将经纬度编码为字符串的方法
  3. GEOHASH key member [member ...]
复制代码


  • GEOPOS: 获取位置的经纬度
  1. # 返回指定名称位置的经纬度坐标
  2. GEOPOS key member [member ...]
复制代码


  • GEORADIUS: 查找指定半径内的成员
  1. # 以给定的经纬度为中心,返回键中包含的位置元素当中,与中心的距离不超过给定半径的所有位置元素
  2. 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: 在指定范围内搜刮
  1. # 在指定范围内搜索,范围可以是圆形或矩形
  2. GEOSEARCH key [FROMMEMBER member] [FROMLONLAT longitude latitude] [BYRADIUS radius unit] [BYBOX width height unit] [WITHDIST] [WITHCOORD] [WITHHASH] [COUNT count] [ASC|DESC]
复制代码


  • GEOSEARCHSTORE: 在指定范围内搜刮并将效果存储
  1. # 与GEOSEARCH功能相同,但可以将结果存储到指定的key中
  2. GEOSEARCHSTORE destination key [FROMMEMBER member] [FROMLONLAT longitude latitude] [BYRADIUS radius unit] [BYBOX width height unit] [WITHDIST] [WITHCOORD] [WITHHASH] [COUNT count] [ASC|DESC]
复制代码
这些下令共同构成了一个完备的地理空间数据处理处罚工具集,可以或许满意大多数基于位置的服务需求。
下令应用示例

让我们通过一个详细的例子来明确GEO下令的使用:
添加地点信息

  1. # 上述命令将"东京"和"吉隆坡"两个城市的经纬度信息添加到名为"locations"的地理空间集合中。
  2. GEOADD locations 139.781210 35.774426 "东京" 101.653962 5.205122 "吉隆坡"
复制代码
查询两地间隔

  1. # 这个命令会返回东京和吉隆坡之间的距离(单位:公里)。
  2. GEODIST locations "东京" "吉隆坡" km
复制代码
查询附近的都会

  1. # 这个命令会查找距离指定坐标点(经度139.0,纬度35.0)1000公里范围内的所有城市,并同时返回它们与中心点的距离。
  2. GEORADIUS locations 139.0 35.0 1000 km WITHDIST
复制代码
实现"查找附近的人"功能

        "查找附近的人"是移动应用中的常见功能,下面我们将详细解说怎样使用Redis GEO下令来实现。
功能需求与实现思绪

根本需求



  • 存储每个用户的地理位置信息(经纬度)
  • 可以或许查询指定用户附近肯定范围内的其他用户
  • 返回的用户列表按照间隔排序
实现思绪


  • 使用GEOADD下令将用户ID及其经纬度信息存储在Redis中
  • 当必要查询"附近的人"时,使用GEORADIUS下令,以查询用户的位置为中心,指定半径范围举行搜刮
下令实现方案

假设我们正在开辟一个交际应用,必要实现广州市用户查找1000公里范围内其他用户的功能:
存储用户位置

  1. GEOADD user_location 113.267548 23.142979 "user1"
  2. GEOADD user_location 113.300000 23.150000 "user2"
  3. GEOADD user_location 114.057868 22.543099 "user3"
复制代码
查询附近的用户

  1. # 命令会返回距离广州市指定坐标1000公里范围内的所有用户,并显示他们与查询点的具体距离。
  2. GEORADIUS user_location 113.254325 23.144043 1000 km WITHDIST
复制代码
Java代码实现详解

下面是使用Java语言和Jedis客户端实现"查找附近的人"功能的代码示例:
  1. import redis.clients.jedis.GeoCoordinate;
  2. import redis.clients.jedis.GeoRadiusResponse;
  3. import redis.clients.jedis.GeoUnit;
  4. import redis.clients.jedis.Jedis;
  5. import redis.clients.jedis.params.GeoRadiusParam;
  6. import java.util.List;
  7. import java.util.Map;
  8. import java.util.HashMap;
  9. import java.util.stream.Collectors;
  10. /**
  11. * Redis GEO功能示例:实现"附近的人"功能
  12. *
  13. * @author Muller
  14. */
  15. public class RedisGeoDemo {
  16.     private static final String USER_LOCATION_KEY = "user_location";
  17.    
  18.     /**
  19.      * 存储用户地理位置信息
  20.      *
  21.      * @param userId 用户ID
  22.      * @param longitude 经度
  23.      * @param latitude 纬度
  24.      * @param jedis Redis连接
  25.      * @return 添加成功的数量
  26.      */
  27.     public static Long saveUserLocation(String userId, double longitude, double latitude, Jedis jedis) {
  28.         try {
  29.             return jedis.geoadd(USER_LOCATION_KEY, longitude, latitude, userId);
  30.         } catch (Exception e) {
  31.             System.err.println("保存用户位置信息失败: " + e.getMessage());
  32.             return 0L;
  33.         }
  34.     }
  35.    
  36.     /**
  37.      * 批量存储多个用户的地理位置信息
  38.      *
  39.      * @param userLocations 用户位置Map,key为用户ID,value为经纬度坐标
  40.      * @param jedis Redis连接
  41.      * @return 添加成功的数量
  42.      */
  43.     public static Long saveUserLocations(Map<String, double[]> userLocations, Jedis jedis) {
  44.         try {
  45.             Map<String, GeoCoordinate> memberCoordinateMap = new HashMap<>();
  46.             
  47.             for (Map.Entry<String, double[]> entry : userLocations.entrySet()) {
  48.                 String userId = entry.getKey();
  49.                 double[] coordinates = entry.getValue();
  50.                 memberCoordinateMap.put(userId, new GeoCoordinate(coordinates[0], coordinates[1]));
  51.             }
  52.             
  53.             return jedis.geoadd(USER_LOCATION_KEY, memberCoordinateMap);
  54.         } catch (Exception e) {
  55.             System.err.println("批量保存用户位置信息失败: " + e.getMessage());
  56.             return 0L;
  57.         }
  58.     }
  59.    
  60.     /**
  61.      * 查询附近的人
  62.      *
  63.      * @param longitude 经度
  64.      * @param latitude 纬度
  65.      * @param radius 半径
  66.      * @param jedis Redis连接
  67.      * @return 附近用户ID列表
  68.      */
  69.     public static List<String> getNearbyUsers(double longitude, double latitude, double radius, Jedis jedis) {
  70.         try {
  71.             List<GeoRadiusResponse> responses = jedis.georadius(
  72.                 USER_LOCATION_KEY,
  73.                 longitude,
  74.                 latitude,
  75.                 radius,
  76.                 GeoUnit.KM,
  77.                 GeoRadiusParam.geoRadiusParam().withDist().sortAscending()
  78.             );
  79.             
  80.             return responses.stream()
  81.                     .map(GeoRadiusResponse::getMemberByString)
  82.                     .collect(Collectors.toList());
  83.         } catch (Exception e) {
  84.             System.err.println("查询附近用户失败: " + e.getMessage());
  85.             return List.of();
  86.         }
  87.     }
  88.    
  89.     /**
  90.      * 获取用户详细地理信息(包含距离)
  91.      *
  92.      * @param longitude 经度
  93.      * @param latitude 纬度
  94.      * @param radius 半径
  95.      * @param jedis Redis连接
  96.      * @return 附近用户详细信息列表
  97.      */
  98.     public static List<UserGeoInfo> getNearbyUsersWithDistance(double longitude, double latitude, double radius, Jedis jedis) {
  99.         try {
  100.             List<GeoRadiusResponse> responses = jedis.georadius(
  101.                 USER_LOCATION_KEY,
  102.                 longitude,
  103.                 latitude,
  104.                 radius,
  105.                 GeoUnit.KM,
  106.                 GeoRadiusParam.geoRadiusParam().withDist().withCoord().sortAscending()
  107.             );
  108.             
  109.             return responses.stream()
  110.                     .map(response -> new UserGeoInfo(
  111.                         response.getMemberByString(),
  112.                         response.getDistance(),
  113.                         response.getCoordinate().getLongitude(),
  114.                         response.getCoordinate().getLatitude()
  115.                     ))
  116.                     .collect(Collectors.toList());
  117.         } catch (Exception e) {
  118.             System.err.println("查询附近用户详细信息失败: " + e.getMessage());
  119.             return List.of();
  120.         }
  121.     }
  122.    
  123.     /**
  124.      * 计算两个用户之间的距离
  125.      *
  126.      * @param userId1 用户1的ID
  127.      * @param userId2 用户2的ID
  128.      * @param jedis Redis连接
  129.      * @return 两用户间距离(单位:公里),如果计算失败返回-1
  130.      */
  131.     public static double getDistanceBetweenUsers(String userId1, String userId2, Jedis jedis) {
  132.         try {
  133.             Double distance = jedis.geodist(USER_LOCATION_KEY, userId1, userId2, GeoUnit.KM);
  134.             return distance != null ? distance : -1;
  135.         } catch (Exception e) {
  136.             System.err.println("计算用户距离失败: " + e.getMessage());
  137.             return -1;
  138.         }
  139.     }
  140.    
  141.     /**
  142.      * 获取用户的地理坐标
  143.      *
  144.      * @param userId 用户ID
  145.      * @param jedis Redis连接
  146.      * @return 用户坐标[经度,纬度],如果不存在返回null
  147.      */
  148.     public static double[] getUserPosition(String userId, Jedis jedis) {
  149.         try {
  150.             List<GeoCoordinate> positions = jedis.geopos(USER_LOCATION_KEY, userId);
  151.             
  152.             if (positions != null && !positions.isEmpty() && positions.get(0) != null) {
  153.                 GeoCoordinate pos = positions.get(0);
  154.                 return new double[] { pos.getLongitude(), pos.getLatitude() };
  155.             }
  156.             
  157.             return null;
  158.         } catch (Exception e) {
  159.             System.err.println("获取用户坐标失败: " + e.getMessage());
  160.             return null;
  161.         }
  162.     }
  163.    
  164.     /**
  165.      * 用户地理信息包装类
  166.      */
  167.     public static class UserGeoInfo {
  168.         private String userId;
  169.         private double distance;
  170.         private double longitude;
  171.         private double latitude;
  172.         
  173.         public UserGeoInfo(String userId, double distance, double longitude, double latitude) {
  174.             this.userId = userId;
  175.             this.distance = distance;
  176.             this.longitude = longitude;
  177.             this.latitude = latitude;
  178.         }
  179.         
  180.         public String getUserId() {
  181.             return userId;
  182.         }
  183.         
  184.         public double getDistance() {
  185.             return distance;
  186.         }
  187.         
  188.         public double getLongitude() {
  189.             return longitude;
  190.         }
  191.         
  192.         public double getLatitude() {
  193.             return latitude;
  194.         }
  195.         
  196.         @Override
  197.         public String toString() {
  198.             return "用户ID: " + userId +
  199.                    ", 距离: " + String.format("%.2f", distance) + "公里" +
  200.                    ", 坐标: [" + longitude + ", " + latitude + "]";
  201.         }
  202.     }
  203.    
  204.     /**
  205.      * 示例用法
  206.      */
  207.     public static void main(String[] args) {
  208.         // 这里仅用于演示,实际使用应通过连接池获取Jedis实例
  209.         try (Jedis jedis = new Jedis("localhost", 6379)) {
  210.             // 清除之前可能存在的测试数据
  211.             jedis.del(USER_LOCATION_KEY);
  212.             
  213.             // 存储几个测试用户的位置(广州及周边城市的坐标)
  214.             saveUserLocation("user1", 113.267548, 23.142979, jedis);  // 广州
  215.             saveUserLocation("user2", 114.057868, 22.543099, jedis);  // 深圳
  216.             saveUserLocation("user3", 113.030396, 22.938259, jedis);  // 佛山
  217.             saveUserLocation("user4", 116.397128, 39.916527, jedis);  // 北京
  218.             
  219.             System.out.println("===== 查询广州周边1000公里范围内的用户 =====");
  220.             List<String> nearbyUsers = getNearbyUsers(113.267548, 23.142979, 1000, jedis);
  221.             System.out.println("附近的用户: " + nearbyUsers);
  222.             
  223.             System.out.println("\n===== 查询广州周边1000公里范围内的用户(包含距离信息) =====");
  224.             List<UserGeoInfo> nearbyUsersWithDist = getNearbyUsersWithDistance(113.267548, 23.142979, 1000, jedis);
  225.             nearbyUsersWithDist.forEach(System.out::println);
  226.             
  227.             System.out.println("\n===== 计算用户间距离 =====");
  228.             double distance = getDistanceBetweenUsers("user1", "user2", jedis);
  229.             System.out.println("广州(user1)到深圳(user2)的距离: " + String.format("%.2f", distance) + "公里");
  230.             
  231.             distance = getDistanceBetweenUsers("user1", "user4", jedis);
  232.             System.out.println("广州(user1)到北京(user4)的距离: " + String.format("%.2f", distance) + "公里");
  233.             
  234.             System.out.println("\n===== 获取用户坐标 =====");
  235.             double[] pos = getUserPosition("user1", jedis);
  236.             if (pos != null) {
  237.                 System.out.println("用户user1的坐标: [" + pos[0] + ", " + pos[1] + "]");
  238.             }
  239.         }
  240.     }
  241. }
复制代码
使用Redis GEO的上风与留意事项

上风



  • 性能高效:Redis基于内存使用,地理位置查询性能极高
  • 使用简单:GEO下令集筹划直观,轻易上手
  • 功能完满:提供了从添加、查询到盘算间隔的完备功能集
  • 可扩展性好:可以轻松处理处罚百万级别的POI(爱好点)数据
  • 与Redis其他功能协同:可以连合Redis的缓存、事件等功能
留意事项



  • 精度限定:GEO下令的精度受到Geohash算法的限定,对于必要极高精度的应用场景(如军事)大概不实用
  • 内存斲丧:大量GEO数据会占用较多内存,必要公道规划Redis服务器资源
  • 经纬度范围:Redis GEO只担当有用的经纬度范围(经度:-180到180,纬度:-85.05112878到85.05112878)
  • 数据长期化:使用AOF长期化模式大概会导致重启时间延伸,需权衡数据安全性和重启速率
  • 实用场景:最得当"附近的XX"这类不必要复杂地理形状盘算的场景,如需多边形地域盘算等高级地理信息功能,大概必要专业GIS体系
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表