找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2936

积分

0

好友

394

主题
发表于 1 小时前 | 查看: 4| 回复: 0

“附近的XX”是怎么实现的?社交软件的附近人,零售app的附近商家等这些看似简单的功能,背后藏着怎样的技术魔法?Redis GEO模块就是实现这一功能的神器!

GEO原理

地图元素的位置数据使用二维的经纬度表示,经度范围(-180, 180],纬度范围(-90,90],纬度正负以赤道为界,北正南负,经度正负以本初子午线(英国格林尼治天文台)为界,东正西负。业界比较通用的地理位置距离排序算法是 GeoHash 算法,Redis也使用GeoHash算法。

GeoHash算法将二维的经纬度数据映射到一维的整数,这样所有的元素都将在挂载到一条线上,距离靠近的二维坐标映射到一维后的点之间距离也会很接近。当我们想要计算「附近的人时」,首先将目标位置映射到这条线上,然后在这个一维的线上获取附近的点就行了。

Redis 里面,经纬度使用 52 位的整数进行编码,放进了 zset 里面,zset 的 value 是元素的 key,score 是 GeoHash 的 52 位整数值。

比如北京的经度和维度,(116.28,39.55),转化成二进制是0010 1101 0110 11000000 1111 0111 0011,然后进行二进制的组合:第 0 位是经度的第 0 位 0,第 1 位是纬度的第 0 位 0,第 2 位是经度的第 1 位 0,第 3位是纬度的第 1 位 0,以此类推。最后得到最终的编码,然后就使用最后得出的这个二进制数组,保存为zset的分数(大概的思想和方式)。

GeoHash算法:经纬度二进制位交错编码示意图

Redis GEO的常用命令

GEOADD:添加地理位置(经纬度)到指定的 Key 中

geoadd key longitude latitude member [longitude latitude member ...]
其中longitude、latitude、member分别是该地理位置的经度、纬度、成员,例如:

Redis GEOADD 命令示例

GEODIST:计算两个位置之间的距离

geodist key member1 member2 [unit]

其中unit代表返回结果的单位,包含以下四种:

  • m (meters)代表米
  • km (kilometers)代表公里
  • mi (miles)代表英里
  • ft(feet)代表尺

Redis GEODIST 命令示例

GEORADIUS、GEORADIUSBYMEMBER获取指定位置范围内的地理信息位置集合

georadius key longitude latitude radius m|km|ft|mi [withcoord][withdist][withhash][COUNT count] [asc|desc] [store key] [storedist key]
georadiusbymember key member radius m|km|ft|mi [withcoord][withdist][withhash] [COUNT count][asc|desc] [store key] [storedist key]

georadiusgeoradiusbymember两个命令的作用是一样的,都是以一个中心点查询半径内的位置。不同的是georadius命令的中心位置给出了具体的经纬度,georadiusbymember只需给出成员即可。其中radius m|km|ft|mi是必需参数,指定了半径(带单位)。

这两个命令有很多可选参数:

  • withcoord:返回结果中包含经纬度。
  • withdist:返回结果中包含离中心节点位置的距离。
  • withhash:返回结果中包含geohash。
  • COUNT count:指定返回结果的数量。
  • asc|desc:返回结果按照离中心节点的距离做升序或者降序。
  • store key:将返回结果的地理位置信息保存到指定键。
  • storedist key:将返回结果离中心节点的距离保存到指定键。

Redis GEORADIUS 命令示例

GEOHASH

GEOHASH命令可以查看任意位置的Geohash值。开发调试时这个小工具特别有用,能直观看到位置编码的变化规律。

Redis GEOHASH 命令示例

Redis GEO的Java实现

下面是一个使用 Spring Data Redis 操作 Redis GEO 功能的 Java 服务类示例:

@Component
@RequiredArgsConstructor
public class GeoService {
    private static final String GEO_KEY = “cities:locations”; // Redis Key
    @Resource
    private RedisTemplate<String, String> redisTemplate;
    /**
     * 添加位置
     *
     * @param member 成员
     * @param lng    经度
     * @param lat    纬度
     */
    public void addLocation(String member, double lng, double lat) {
        redisTemplate.opsForGeo().add(GEO_KEY, new Point(lng, lat), member);
    }
    /**
     * 查询附近的位置
     *
     * @param lng    用户经度
     * @param lat    用户纬度
     * @param radius 查询半径(单位:公里)
     * @return 附近的位置列表
     */
    public List<String> findNearbys(double lng, double lat, double radius) {
        // 创建查询范围
        Circle circle = new Circle(new Point(lng, lat), new Distance(radius, Metrics.KILOMETERS));
        // 查询附近位置
        GeoResults<RedisGeoCommands.GeoLocation<String>> results = redisTemplate.opsForGeo()
                .radius(GEO_KEY, circle, RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeDistance().sortAscending());
        // 提取成员
        return results.getContent().stream()
                .map(geoLocationGeoResult -> geoLocationGeoResult.getContent().getName())
                .collect(Collectors.toList());
    }
    /**
     * 计算两个成员位置之间的距离
     *
     * @param member1 位置1
     * @param member2 位置2
     * @return 距离(单位:公里)
     */
    public double calculateDistance(String member1, String member2) {
        Distance distance = redisTemplate.opsForGeo()
                .distance(GEO_KEY, member1, member2, Metrics.KILOMETERS);
        return Objects.nonNull(distance) ? distance.getValue() : 0;
    }
}

通过上述原理、命令和代码的讲解,相信你对如何利用 Redis 实现“附近”功能有了清晰的了解。从社交应用到本地生活服务,这套方案都能高效支撑。想了解更多 数据库Java 开发实战技巧,欢迎访问 云栈社区 进行深入交流。




上一篇:小红书视觉无障碍设计实践:从0到1的屏幕阅读器适配与社区共建
下一篇:支付系统风控架构实战:事前事中事后全流程设计与核心指标解析
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-4-11 09:20 , Processed in 0.705808 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表