地理位置数据存储方案——Redis GEO
mhr18 2024-10-22 12:34 28 浏览 0 评论
一 题外话
说起这个话题,就总会不由得想起刚毕业的时候,当时在导师的带领下,调研并使用了geo server和postgreSQL。geo server做图层和位置信息展示,而pg则用来存储地理位置数据。一转眼至今已有十年光景,真是让人感慨,十年,弹指一挥间。
二 GEO存储方案与空间索引
2.1 存储方案
目前支持空间数据存储的方案很多,Esri公司的ArcSDE(Spatial Database Engine,空间数据库引擎),包括Oracle,SQL Server,IBM DB2都做了很好的支持,不过都是商业数据库,需要收费。开源领域,mysql、redis、elasticsearch、mongodb、postgreSQL等都做了相关支持。实现方案各自不同,使用上也有差异,简单理解,都是数据+索引结构组成的支撑,通过api来进行调用(废话)。
2.2 空间索引
目前空间索引的实现有R树和其变种GIST树、四叉树、网格索引等。GeoHash也是空间索引的一种方式,并且特别适合点数据,而对线、面数据采用R树索引更有优势。
三 Redis GEO
3.1 命令
Redis 3.2 版本新增了geo相关命令,用于存储和操作地理位置信息。提供的命令包括添加、计算位置之间距离、根据中心点坐标和距离范围来查询地理位置集合等,说明如下:
- geoadd:添加地理位置的坐标。
- geopos:获取地理位置的坐标。
- geodist:计算两个位置之间的距离。
- georadius:根据用户给定的经纬度坐标来获取指定范围内的地理位置集合。
- georadiusbymember:根据储存在位置集合里面的某个地点获取指定范围内的地理位置集合。
- geohash:返回一个或多个位置对象的 geohash 值。
3.2 原理:redis源码解析
3.2.1 数据结构简述
Redis geo并不是全新的数据结构,而是基于Sorted Set来实现的(这点我们会在后面进行说明)。说起sorted set,大家肯定了解zset,也是redis中常用的数据结构。
我们看一下redis geo的源码,从中可以更好地理解数据结构和操作原理。redis源码可从https://github.com/redis/redis获取,我们切换到正在使用的3.2branch(也可以根据实际使用情况,切换到对应版本的分支)。3.2下的geo相关源码文件主要是src下的geo.h 和 geo.c,以及deps/geohash-int下的geohash.c,geohash.h,geohash_helper.h 和 geohash_helper.c。
3.2.2 geo.h
geo.h是数据结构定义,里面包括了geoPoint 和 geoArray两个结构体,内容如下:
#ifndef __GEO_H__
#define __GEO_H__
#include "server.h"
/* Structures used inside geo.c in order to represent points and array of
* points on the earth. */
typedef struct geoPoint {
double longitude;
double latitude;
double dist;
double score;
char *member;
} geoPoint;
typedef struct geoArray {
struct geoPoint *array;
size_t buckets;
size_t used;
} geoArray;
#endif
可见geoPoint的字段包括 经度longitude、纬度latitude 这两个标识位置的基本字段,dist表示距离,member是成员(点)的名称/标识,以及score。score的含义是什么?聪明的小伙伴可能已经想到,应该是我们最开始提到的geohash值。其他的小伙伴不要着急,我们一起到geo.c中寻找答案。
3.2.3 geo.c
geo.c是geo核心方法定义,内容不算很多,3.2版本的geo.c文件只有825行,所以阅读起来也并不复杂。
这里定义了我们从redis客户端输入各redis命令的处理函数。geoaddCommand,georadiusCommand,geohashCommand,geoposCommand,geodistCommand等等,其中还有georadius的一系列包装函数,在void georadiusGeneric(client *c, int flags) 函数中定义了具体的处理逻辑:
我们再详细看一下geoaddCommand(client *c)方法:
409-414行是校验逻辑,判断是否存在语法错误;
接下来是参数提取和处理。在419和420行,我们可以看到熟悉的命令:zadd;
接下来就更清晰了,注释中就已经明确写到:
创建参数向量并调用zadd方法,来把所有的score,value队插入到zset中,这里score实际上是lat,long的编码版本。
什么编码?438-441行明确写出了答案,geohash(geohashEncodeWGS84,使用wgs84坐标系的geohash编码。wgs84坐标系即大地坐标系)。
3.3 操作实践
上面我们分析了,redis geo虽然是通过geopos,geoadd等提供了操作命令,但底层实际上是基于zset来存储的,并且在geoadd命令中,也出现了转zadd操作的源码,那么我们是否可以直接使用zset的相关命令来操作redis geo的存储呢?
3.3.1 redis环境
redis server版本3.2,本地单机部署,未设置密码。
3.3.2 命令行客户端连接
redis-cli -h mylocalhost -p 8179 --raw
注意:这里加上了--raw的参数。这是因为,当我们在redis中存储value包含中文时,如果不加上--raw,就会显示为unicode编码格式,如下:
--raw参数,官网的解释中,包括以下两个作用:1.按数据原有格式打印数据,不展示额外的类型信息(例如整数value之前的 (integer) 2);2. 显示中文。
3.3.3 geo-zset操作验证
先通过geoadd添加一条记录:
geopos查看成员位置:
重点来了,接下来我们通过zrange 来查询集合元素:
显然是可以的。也就是说,user_local就是一个zset。
接下来,我们看一下刚刚添加进来的test_1这个成员的score:
score值为 4174690127103984,回顾之前我们看过的geoadd源码,也就是说test_1的经纬度,对应的geohash值就是4174690127103984。
四 springframework与redis geo
springframework中已经加入了对redis geo的支持,相关的类都在org.springframework.data.geo包下。而对redis的命令行交互,也提供了org.springframework.data.redis相关的类来支持相关开发。
为了在项目中方便使用,整理工具代码如下,主要封装了:
1、添加元素到redisgeo;
2、计算某指定集合下,给定中心和查询范围,获取区域内成员的方法;
3、计算两个成员的距离
4、查询某指定成员(数组)的位置信息
相关方法,如有需要可供参考:
package tool;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.geo.GeoResult;
import org.springframework.data.geo.Metrics;
import org.springframework.data.geo.Point;
import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class RedisGeoTool {
@Autowired
private RedisTemplate<String,String> redisTemplate;
/**
* 添加节点及位置信息
* @param geoKey 位置集合
* @param pointName 位置点标识
* @param longitude 经度
* @param latitude 纬度
*/
public void geoAdd(String geoKey, String pointName, double longitude, double latitude){
Point point = new Point(longitude, latitude);
redisTemplate.opsForGeo().add(geoKey, point, pointName);
}
/**
*
* @param longitude
* @param latitude
* @param radius
* @param geoKey
* @param metricUnit 距离单位,例如 Metrics.KILOMETERS
* @param metricUnit
* @return
*/
public List<GeoResult<RedisGeoCommands.GeoLocation<String>>> findRadius(String geoKey
, double longitude, double latitude, double radius, Metrics metricUnit, int limit){
// 设置检索范围
Point point = new Point(longitude, latitude);
Circle circle = new Circle(point, new Distance(radius, metricUnit));
// 定义返回结果参数,如果不指定默认只返回content即保存的member信息
RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs
.newGeoRadiusArgs().includeDistance().includeCoordinates()
.sortAscending()
.limit(limit);
GeoResults<RedisGeoCommands.GeoLocation<String>> results = redisTemplate.opsForGeo().radius(geoKey, circle, args);
List<GeoResult<RedisGeoCommands.GeoLocation<String>>> list = results.getContent();
return list;
}
/**
* 计算指定key下两个成员点之间的距离
* @param geoKey
* @param member1
* @param member2
* @param unit 单位
* @return
*/
public Distance calDistance(String geoKey, String member1, String member2
, RedisGeoCommands.DistanceUnit unit){
Distance distance = redisTemplate.opsForGeo()
.distance(geoKey, member1, member2, unit);
return distance;
}
/**
* 根据成员点名称查询位置信息
* @param geoKey geo key
* @param members 名称数组
* @return
*/
public List<Point> geoPosition(String geoKey, String[] members){
List<Point> points = redisTemplate.opsForGeo().position(geoKey, members);
return points;
}
}
五 实战思路
基于上述理解和代码,我们可以实现一些简单的demo了。也可以基于此实现一个基于某中心点查询周围商铺之类的功能,但要应用到实战当中还远远不够。在真实的系统中,还需要考虑以下几个问题:
1、redis作为缓存还是数据库使用?
2、redis geo中存储的信息是否完整?是否还需要存储其他辅助信息?
3、可能会有多类位置点,实际需求会要求根据类别查询?
4、当发生数据迁移时,怎样保证redis geo中的数据完整?最多支持存储多少个空间数据?
....
一些比较容易想到的可能方案,比如结合其他持久化存储使用,做好一致性保障;member中包含id信息,用于查询明细信息;通过多个key对位置数据分类存储等等。但最终还需要根据实际需求,给出整套可行的方案,形成合理的架构设计,这样才能让我们做出的系统不再只是个demo,或者玩具。在后续的文章中,我们会继续进行探讨。
【程序员架构进阶】
相关推荐
- 甲骨文签署多项大型云协议,其一未来可贡献超300亿美元年收入
-
IT之家7月1日消息,根据甲骨文Oracle当地时间6月30日向美国证券交易委员会(SEC)递交的FORM8-K文件,该企业在始于2025年6月1日的202...
- 甲骨文获TEMU巨额合同,后者大部分基础设施将迁移至Oracle云
-
IT之家6月23日消息,Oracle甲骨文创始人、董事长兼首席技术官LarryEllison(拉里埃里森)在本月早些时候的2025财年第四财季和全财年财报电话会议上表示,Oracle...
- Spring Boot 自定义数据源设置,这些坑你踩过吗?
-
你在使用SpringBoot进行后端开发的过程中,是不是也遇到过这样的问题:项目上线后,数据库连接总是不稳定,偶尔还会出现数据读取缓慢的情况,严重影响了用户体验。经过排查,发现很大一部分原因竟然...
- 一个开箱即用的代码生成器(一个开箱即用的代码生成器是什么)
-
今天给大家推荐一个好用的代码生成器,名为renren-generator,该项目附带前端页面,可以很方便的选择我们所需要生成代码的表。首先我们通过git工具克隆下来代码(地址见文末),导入idea。...
- 低代码建模平台-数据挖掘平台(低代码平台的实现方式)
-
现在来看一下数据连接。·这里是管理数据连接的空间,点击这里可以新增一个数据连接。·输入连接名称,然后输入url,是通过gdbc的方式去连接的数据库,目前是支持mysql、oracle以及国产数据库达梦...
- navicat 17.2.7连接oracle数据库提示加载oracle库失败
-
系统:macOS15.5navicat版本:navicatpremiumlite17.2.7连接oracle测试报错:加载oracle库失败【解决办法】:放达里面找到程序,显示简介里面勾选“使...
- 开源“Windows”ReactOS更新:支持全屏应用
-
IT之家6月17日消息,ReactOS团队昨日(6月16日)在X平台发布系列推文,公布了该系统的最新进展,包括升级Explorer组件,支持全屏应用,从Wine项目引入了...
- SSL 推出采用全模拟内置混音技术的模拟调音台Oracle
-
英国调音台传奇品牌SolidStateLogic宣布推出Oracle——一款采用全模拟内置混音技术的调音台,在紧凑的AWS尺寸机箱内集成了大型调音台的功能。该调音台提供24输入和...
- 47道网络工程师常见面试题,看看有没有你不会的!
-
你们好,我的网工朋友。网络工程师面试的时候,都会被问到什么?这个问题其实很泛,一般来说,你肯定要先看明白岗位需求写的是什么。基本上都是围绕公司需要的业务去问的。但不可否认的是,那些最基础的概念,多少也...
- 汉得信息:发布EBS系统安装启用JWS的高效解决方案
-
e公司讯,从汉得信息获悉,近日,微软官方宣布InternetExplorer桌面应用程序将于2022年6月15日正式停用。目前大部分客户都是使用IE浏览器打开EBS的Form界面,IE停用后,只能使...
- 36.9K star ! 推荐一个酷炫低代码开发平台!功能太强!
-
前言最近在逛github,看看能不能搜罗到一些对自己有帮助的开源软件。不经意间看到一个高star的java开源项目:jeecg-boot。进入在线演示版一看,感叹实在是太牛了!此开源项目不管是给来学习...
- Linux新手入门系列:Linux下jdk安装配置
-
本系列文章是把作者刚接触和学习Linux时候的实操记录分享出来,内容主要包括Linux入门的一些理论概念知识、Web程序、mysql数据库的简单安装部署,希望能够帮到一些初学者,少走一些弯路。注意:L...
- 手把手教你在嵌入式设备中使用SQLite3
-
摘要:数据库是用来存储和管理数据的专用软件,使得管理数据更加安全,方便和高效。数据库对数据的管理的基本单位是表(table),在嵌入式linux中有时候它也需要用到数据库,听起来好难,其实就是几个函数...
- JAVA语言基础(java语言基础知识)
-
一、计算机的基本概念什么是计算机?计算机(Computer)全称:电子计算机,俗称电脑。是一种能够按照程序运行、自动高速处理海量数据的现代化智能电子设备。由硬件和软件组成、没有安装过任何软件的计算机称...
- 再见 Navicat!一款开源的 Web 数据库管理工具!
-
大家好,我是Java陈序员。在日常的开发工作中,常常需要与各种数据库打交道。而为了提高工作效率,常常会使用一些可视化工具进行操作数据库。今天,给大家介绍一款开源的数据库管理工具,无需下载安装软件,基...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- 甲骨文签署多项大型云协议,其一未来可贡献超300亿美元年收入
- 甲骨文获TEMU巨额合同,后者大部分基础设施将迁移至Oracle云
- Spring Boot 自定义数据源设置,这些坑你踩过吗?
- 一个开箱即用的代码生成器(一个开箱即用的代码生成器是什么)
- 低代码建模平台-数据挖掘平台(低代码平台的实现方式)
- navicat 17.2.7连接oracle数据库提示加载oracle库失败
- 开源“Windows”ReactOS更新:支持全屏应用
- SSL 推出采用全模拟内置混音技术的模拟调音台Oracle
- 47道网络工程师常见面试题,看看有没有你不会的!
- 汉得信息:发布EBS系统安装启用JWS的高效解决方案
- 标签列表
-
- oracle位图索引 (74)
- oracle批量插入数据 (65)
- oracle事务隔离级别 (59)
- oracle 空为0 (51)
- oracle主从同步 (55)
- oracle 乐观锁 (51)
- redis 命令 (78)
- php redis (88)
- redis 存储 (66)
- redis 锁 (69)
- 启动 redis (66)
- redis 时间 (56)
- redis 删除 (67)
- redis内存 (57)
- redis并发 (52)
- redis 主从 (69)
- redis 订阅 (51)
- redis 登录 (54)
- redis 面试 (58)
- 阿里 redis (59)
- redis 搭建 (53)
- redis的缓存 (55)
- lua redis (58)
- redis 连接池 (61)
- redis 限流 (51)