百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

Java使用Redis的GeoHash数据结构

mhr18 2025-03-13 18:07 32 浏览 0 评论

GeoHash介绍

GeoHash目前比较主流实现位置服务的技术,Geohash算法将经纬度二维数据编码为一个字符串,本质是一个将降维的过程。

举个简单的例子,在地球上为了表示一个地标点,人们通过经度和纬度的交叉点来确定,但是这个地标点的表示必须是二维的。用二维数据存储的情况下,如果搜索某个地标点A周边5公里的酒店,如果将每个点到A的距离计算一遍,计算量非常大。

简单优化如果把地球划分两块,先判断经纬度范围,就可以减少一半的计算量,以此类推,当划分块越小计算量就越少。

geohash

进一步优化如果把地球划分成很多区块(因为地球并不是正圆,所以划分的区块是东西窄南北宽的长方形),一个小块的所有经纬坐标都视为小块的中心点的坐标(编码为字符串方便索引),显然A点属于某个小区块内的点,搜索A点5公里酒店时,只需计算A点到周边8个小块中心点的距离,如果某个小块不满足,继续取不满足区块周边的小区块直到满足条件,将全部小区块放到集合中去重,然后取出所有符合区块内的所有酒店计算一遍距离,剔除不满足条件的酒店,这样一来就将复杂的二维降低到一维,达到快速搜索的目的。

GeoHash如何编码等过于详细的内容就不多说了,大家可以网上搜一下。

Redis的GeoHash底层存储

Redis实现GeoHash底层使用了SortedSet存储。

遇到的问题

当需要存储的元素比较多时,不能存储到一个Rediskey中,这样会因大Key的查询导致网络堵塞,但是对于Redis的GeoHash不会有这个问题,查询时会根据经纬度计算出来符合条件的元素返回,我就遇到了下面的报警,感觉是误报,既然报警就要解决,请继续往下看。

Redis报警

解决思路

对于大Key,首先想到的方法是进行拆分,如何拆分呢?

刚开始是想着根据经纬度拆分,rediskey的设计是key_lon_lat,经度和维度取整,做也勉强可以,仔细想想还是不太合适。既然有GeoHash了,为什么不用GeoHash计算出来的编码作为Rediskey呢?为了区分集群中其他类型的Rediskey,再加个前缀表示地理位置的就完全解决拆分问题了。

注意事项

  • rediskey设计:使用String.format("geohash_%s", geoHash.toBase32()),下图直观看下geohash的输出结果。下图直观看下geoHash.toBase32()输出结果:

  • 精度选择:如果geohash的位数是9位数的时候,大概为附近2米。如下图:

精度对比图

代码片断

import ch.hsr.geohash.GeoHash;
import ch.hsr.geohash.WGS84Point;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.kn.util.CoordinateUtil;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import org.junit.Test;
import redis.clients.jedis.GeoRadiusResponse;
import redis.clients.jedis.GeoUnit;
import redis.clients.jedis.Jedis;

import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @Description: Redis GeoHash测试
 */
public class RedisGeoHashTest {
    @Getter
    @Setter
    @Data
    @AllArgsConstructor
    class H {
        private String id;
        private Double longitude;
        private Double latitude;
    }

    @Test
    public void redisGeoHash() {
        Jedis jedis = new Jedis("127.0.0.1");
        //确定精度
        int numberOfCharacters = 5;
        //Mock数据并完成数据存储
        List list = Lists.newArrayList(new H("1", 121.514523, 31.10110537)
                , new H("2", 121.624523, 31.20110537)
                , new H("3", 121.734523, 31.30110537));
        for (H h : list) {
            GeoHash geoHash = GeoHash.withCharacterPrecision(h.getLatitude(), h.getLongitude(), numberOfCharacters);
            String rediskey = String.format("geohash_%s", geoHash.toBase32());
            jedis.geoadd(rediskey, h.getLongitude(), h.getLatitude(), h.id);
            jedis.setex(String.format("h_%s", h.getId()), 86400, h.toString());
        }

        //优化写入:大批量数据写入可以用分批方式
        //Map memberCoordinateMap = Maps.newHashMap();
        //jedis.geoadd("rediskey",memberCoordinateMap);
        //jedis.mset("rediskey",String[])
    }

    /**
     * 搜索5千米内符合条件的数据
     */
    @Test
    public void queryList() {
        Jedis jedis = new Jedis("127.0.0.1");
        double radius = 5000;
        double latitude = 31.22416;
        double longitude = 121.366384;
        //计算坐标点所在区块编码,并获取符合条件的周边区块
        GeoHash geoHash = GeoHash.withCharacterPrecision(latitude, longitude, 5);
        Set pointSet = Sets.newHashSet();
        pointSet.addAll(Arrays.stream(geoHash.getAdjacent()).collect(Collectors.toSet()));
        pointSet.addAll(getAdjacent(geoHash, radius, longitude, latitude));
        System.out.println(pointSet.size());
        List list = Lists.newArrayList();
        for (GeoHash gh : pointSet) {
            list.addAll(jedis.georadius(String.format("geohash_%s", gh.toBase32()), longitude, latitude, radius, GeoUnit.M));
        }
        Set hList = list.stream().map(e -> e.getMemberByString()).collect(Collectors.toSet());
        //获取H集合
        List keys = hList.stream().map(e -> String.format("h_%s", e)).collect(Collectors.toList());
        List rtnList = jedis.mget(keys.toArray(new String[0]));
        rtnList.forEach(System.out::println);
    }

    /**
     * 返回指定经纬度到周边区块中心点小于指定距离的区块集合
     *
     * @param geoHash
     * @param radius
     * @param longitude
     * @param latitude
     * @return
     */
    public Set getAdjacent(GeoHash geoHash, double radius, double longitude, double latitude) {
        WGS84Point point = geoHash.getOriginatingPoint();
        double v = CoordinateUtil.calDisByLonAndLatSimple(longitude, latitude, point.getLongitude(), point.getLatitude());
        if (v < radius) {
            return Sets.newHashSet(geoHash);
        }
        Set set = Sets.newHashSet();
        GeoHash[] adjacent = geoHash.getAdjacent();
        for (GeoHash gh : adjacent) {
            set.addAll(getAdjacent(gh, radius, longitude, latitude));
        }
        return set;
    }
}

需要引入如下pom:

        
            redis.clients
            jedis
            3.3.0
         
			
            ch.hsr
            geohash
            1.4.0
        
        
            org.projectlombok
            lombok
            1.18.22
        

觉得有用就收藏分享,关注我更多有价值的文章会第一时间推荐给你!

相关推荐

【推荐】一个开源免费、AI 驱动的智能数据管理系统,支持多数据库

如果您对源码&技术感兴趣,请点赞+收藏+转发+关注,大家的支持是我分享最大的动力!!!.前言在当今数据驱动的时代,高效、智能地管理数据已成为企业和个人不可或缺的能力。为了满足这一需求,我们推出了这款开...

Pure Storage推出统一数据管理云平台及新闪存阵列

PureStorage公司今日推出企业数据云(EnterpriseDataCloud),称其为组织在混合环境中存储、管理和使用数据方式的全面架构升级。该公司表示,EDC使组织能够在本地、云端和混...

对Java学习的10条建议(对java课程的建议)

不少Java的初学者一开始都是信心满满准备迎接挑战,但是经过一段时间的学习之后,多少都会碰到各种挫败,以下北风网就总结一些对于初学者非常有用的建议,希望能够给他们解决现实中的问题。Java编程的准备:...

SQLShift 重大更新:Oracle→PostgreSQL 存储过程转换功能上线!

官网:https://sqlshift.cn/6月,SQLShift迎来重大版本更新!作为国内首个支持Oracle->OceanBase存储过程智能转换的工具,SQLShift在过去一...

JDK21有没有什么稳定、简单又强势的特性?

佳未阿里云开发者2025年03月05日08:30浙江阿里妹导读这篇文章主要介绍了Java虚拟线程的发展及其在AJDK中的实现和优化。阅前声明:本文介绍的内容基于AJDK21.0.5[1]以及以上...

「松勤软件测试」网站总出现404 bug?总结8个原因,不信解决不了

在进行网站测试的时候,有没有碰到过网站崩溃,打不开,出现404错误等各种现象,如果你碰到了,那么恭喜你,你的网站出问题了,是什么原因导致网站出问题呢,根据松勤软件测试的总结如下:01数据库中的表空间不...

Java面试题及答案最全总结(2025版)

大家好,我是Java面试陪考员最近很多小伙伴在忙着找工作,给大家整理了一份非常全面的Java面试题及答案。涉及的内容非常全面,包含:Spring、MySQL、JVM、Redis、Linux、Sprin...

数据库日常运维工作内容(数据库日常运维 工作内容)

#数据库日常运维工作包括哪些内容?#数据库日常运维工作是一个涵盖多个层面的综合性任务,以下是详细的分类和内容说明:一、数据库运维核心工作监控与告警性能监控:实时监控CPU、内存、I/O、连接数、锁等待...

分布式之系统底层原理(上)(底层分布式技术)

作者:allanpan,腾讯IEG高级后台工程师导言分布式事务是分布式系统必不可少的组成部分,基本上只要实现一个分布式系统就逃不开对分布式事务的支持。本文从分布式事务这个概念切入,尝试对分布式事务...

oracle 死锁了怎么办?kill 进程 直接上干货

1、查看死锁是否存在selectusername,lockwait,status,machine,programfromv$sessionwheresidin(selectsession...

SpringBoot 各种分页查询方式详解(全网最全)

一、分页查询基础概念与原理1.1什么是分页查询分页查询是指将大量数据分割成多个小块(页)进行展示的技术,它是现代Web应用中必不可少的功能。想象一下你去图书馆找书,如果所有书都堆在一张桌子上,你很难...

《战场兄弟》全事件攻略 一般事件合同事件红装及隐藏职业攻略

《战场兄弟》全事件攻略,一般事件合同事件红装及隐藏职业攻略。《战场兄弟》事件奖励,事件条件。《战场兄弟》是OverhypeStudios制作发行的一款由xcom和桌游为灵感来源,以中世纪、低魔奇幻为...

LoadRunner(loadrunner录制不到脚本)

一、核心组件与工作流程LoadRunner性能测试工具-并发测试-正版软件下载-使用教程-价格-官方代理商的架构围绕三大核心组件构建,形成完整测试闭环:VirtualUserGenerator(...

Redis数据类型介绍(redis 数据类型)

介绍Redis支持五种数据类型:String(字符串),Hash(哈希),List(列表),Set(集合)及Zset(sortedset:有序集合)。1、字符串类型概述1.1、数据类型Redis支持...

RMAN备份监控及优化总结(rman备份原理)

今天主要介绍一下如何对RMAN备份监控及优化,这里就不讲rman备份的一些原理了,仅供参考。一、监控RMAN备份1、确定备份源与备份设备的最大速度从磁盘读的速度和磁带写的带度、备份的速度不可能超出这两...

取消回复欢迎 发表评论: