【学习笔记】Redis的geohash数据结构介绍

本文介绍了Redis基于geohash的地理位置功能,包括GEOADD和GEORADIUS命令的使用及实现原理。GEOADD用于添加位置对象到集合,而GEORADIUS则能搜索指定范围内的位置对象。Redis内部使用有序集合存储位置,通过geohash进行高效检索。文章还探讨了如何利用geohash网格筛选元素,以及查询效率。
摘要由CSDN通过智能技术生成

geohash介绍

⾃Redis 3.2开始,Redis基于geohash和有序集合提供了地理位置相关功能。Redis Geo模块包含了以下6个命令:
▶GEOADD: 将给定的位置对象(纬度、经度、名字)添加到指定的key;
▶GEOPOS: 从key⾥⾯返回所有给定位置对象的位置(经度和纬度);
▶GEODIST: 返回两个给定位置之间的距离;
▶GEOHASH: 返回⼀个或多个位置对象的Geohash表⽰;
▶GEORADIUS: 以给定的经纬度为中⼼,返回⽬标集合中与中⼼的距离不超过给定最⼤距离的所有位置对象;
▶GEORADIUSBYMEMBER: 以给定的位置对象为中⼼,返回与其距离不超过给定最⼤距离的所有位置对象。
其中,组合使⽤GEOADD和GEORADIUS可实现“附近的⼈”中“增”和“查”的基本功能。要实现微信中“附近的⼈”功能,可直接使⽤GEORADIUSBYMEMBER命令。其中“给定的位置对象”即为⽤⼾本⼈,搜索的对象为其他⽤⼾。不过本质
上,GEORADIUSBYMEMBER = GEOPOS + GEORADIUS,即先查找⽤⼾位置再通过该位置搜索附近满⾜位置相互距离条件的其他⽤⼾对象。
以下会从源码⻆度⼊⼿对GEOADD和GEORADIUS命令进⾏分析,剖析其算法原理

Redis geo操作中只包含了“增”和“查”的操作,并没有专⻔的“删除”命令。主要是因为Redis内部使⽤有序集合(zset)保存位置对象,可⽤zrem进⾏删除。
在Redis源码geo.c的⽂件注释中,只说明了该⽂件为GEOADD、GEORADIUSGEORADIUSBYMEMBER的实现⽂件(其实在也实现了另三个命令)。从侧⾯看出其他三个命令为辅助命令。

GEOADD

使⽤⽅式
GEOADD key longitude latitude member [longitude latitude member …]
将给定的位置对象(纬度、经度、名字)添加到指定的key。
其中,key为集合名称,member为该经纬度所对应的对象。在实际运⽤中,当所需存储的对象数量过多时,可通过设置多
key(如⼀个省⼀个key)的⽅式对对象集合变相做sharding,避免单集合数量过多。
成功插⼊后的返回值:
(integer) N
其中N为成功插⼊的个数。

通过源码分析可以看出Redis内部使⽤有序集合(zset)保存位置对象,有序集合中每个元素都是⼀个带位置的对象,元素的score值为其经纬度对应的52位的geohash值。
double类型精度为52位;
geohash是以base32的⽅式编码,52bits最⾼可存储10位geohash值,对应地理区域⼤⼩为0.60.6⽶的格⼦。换句话说经Redis geo转换过的位置理论上会有约0.31.414=0.424⽶的误差。(?)

简单总结下GEOADD命令都⼲了啥:
1、参数提取和校验;
2、将⼊参经纬度转换为52位的geohash值(score);
3、调⽤ZADD命令将member及其对应的score存⼊集合key中。

GEORADIUS

使⽤⽅式

GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [ASC|DESC] [COUNT count] [STORE key] [STORedisT key]

以给定的经纬度为中⼼,返回⽬标集合中与中⼼的距离不超过给定最⼤距离的所有位置对象。
范围单位:m | km | ft | mi --> ⽶ | 千⽶ | 英尺 | 英⾥
额外外参数:

  • WITHDIST:在返回位置对象的同时,将位置对象与中⼼之间的距离也⼀并返回。距离的单位和⽤⼾给定的范围单位保持⼀致。
  • WITHCOORD:将位置对象的经度和维度也⼀并返回。
  • WITHHASH:以 52 位有符号整数的形式,返回位置对象经过原始 geohash 编码的有序集合分值。这个选项主要⽤于底层应⽤
    或者调试,实际中的作⽤并不⼤。
  • ASC|DESC:从近到远返回位置对象元素 | 从远到近返回位置对象元素。
  • COUNT count:选取前N个匹配位置对象元素。(不设置则返回所有元素) - STORE key:将返回结果的地理位置信息保存到
    指定key。
  • STORedisT key:将返回结果离中⼼点的距离保存到指定key。

由于 STORE 和 STORedisT 两个选项的存在,GEORADIUS 和 GEORADIUSBYMEMBER 命令在技术上会被标记为写⼊命令,从⽽只会查询(写⼊)主实例,QPS过⾼时容易造成主实例读写压⼒过⼤。为解决这个问题,在 Redis 3.2.10 和 Redis 4.0.0 中,分别新增了 GEORADIUS_RO 和 GEORADIUSBYMEMBER_RO两个只读命令。
不过,在实际开发中笔者发现 在java package Redis.clients.jedis.params.geo 的 GeoRadiusParam 参数类中并不包含STORE 和 STORedisT 两个参数选项,在调⽤georadius时是否真的只查询了主实例,还是进⾏了只读封装。
成功查询后的返回值:
不带WITH限定,返回⼀个member list,如:
[“member1”,“member2”,“member3”]
带WITH限定,member list中每个member也是⼀个嵌套list,如:
[
[“member1”, distance1, [longitude1, latitude1]]
[“member2”, distance2, [longitude2, latitude2]]
]

⼩结
抛开众多可选参数不谈,简单总结下GEORADIUS命令是怎么利⽤geohash获取⽬标位置对象的:
1、参数提取和校验;
2、利⽤中⼼点和输⼊半径计算待查区域范围。这个范围参数包括满⾜条件的最⾼的geohash⽹格等级(精度) 以及 对应的能够覆盖⽬标区域的九宫格位置;(后续会有详细说明)
3、对九宫格进⾏遍历,根据每个geohash⽹格的范围框选出位置对象。进⼀步找出与中⼼点距离⼩于输⼊半径的对象,进⾏返回。
直接描述不太好理解,我们通过如下两张图在对算法进⾏简单的演⽰:
在这里插入图片描述
令左图的中⼼为搜索中⼼,绿⾊圆形区域为⽬标区域,所有点为待搜索的位置对象,红⾊点则为满⾜条件的位置对象。
在实际搜索时,⾸先会根据搜索半径计算geohash⽹格等级(即右图中⽹格⼤⼩等级),并确定九宫格位置(即红⾊九宫格位置信息);再依次查找计算九宫格中的点(蓝点和红点)与中⼼点的距离,最终筛选出距离范围内的点(红点)。

如何通过geohash⽹格的范围框选出元素对象?效率如何?
⾸先在每个geohash⽹格中的geohash值都是连续的,有固定范围。所以只要找出有序集合中,处在该范围的位置对象即可。以下是有序集合的跳表数据结构:
在这里插入图片描述
其拥有类似⼆叉查找树的查询效率,操作平均时间复杂性为O(log(N))。
且最底层的所有元素都以链表的形式按序排列。
所以在查询时,只要找到集合中处在⽬标geohash⽹格中的第⼀个值,后续依次对⽐即可,不⽤多次查找。九宫格不能⼀起查,要⼀个个遍历的原因也在于九宫格各⽹格对应的geohash值不具有连续性。只有连续了,查询效率才会⾼,不然要多做许多距离运算。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值