本文来源:https://www.dazhuanlan.com/2020/02/05/5e3a0a3110649/

背景

前段时间自己在做附近直播相关业务,其中有一个核心的点就是检索用户附近的主播,也是主要召回池。针对业务场景的特殊性,最后决定使用Redis的GEO技术来完成这个功能。主要考虑点在于每天在线直播的主播数量是固定的差不多一万这个量级,使用配置好一点的单机Redis单key存储是没问题的。数据操作主要有两个:一是主播开播的时候写入主播Id的经纬度,二是主播关播的时候删除主播Id元素。这样就维护了一个具有位置信息的在线主播集合提供给线上检索。下面详细介绍一下。

Redis GEO 命令

Redis3.2 版本提供了GEO(地理信息定位)功能,支持存储地理位置信息用来实现诸如附近位置、摇一摇这类依赖于地理位置信息的功能,对于需要实现这些功能的开发者来说是一大福音。GEO功能是Redis的另一位作者Matt Stancliff 借鉴NoSQL数据库 Ardb 实现的,Ardb的作者来自中国,它提供了优秀的GEO功能。

Redis GEO 相关的命令如下:

# 添加一个空间元素,longitude、latitude、member分别是该地理位置的经度、纬度、成员
# 这里的成员就是指代具体的业务数据,比如说用户的ID等
# 需要注意的是Redis的纬度有效范围不是[-90,90]而是[-85,85]
# 如果在添加一个空间元素时,这个元素中的menber已经存在key中,那么GEOADD命令会返回0,相当于更新了这个menber的位置信息
GEOADD key longitude latitude member [longitude latitude member]
# 用于添加城市的坐标信息
geoadd cities:locations 117.12 39.08 tianjin 114.29 38.02 shijiazhuang 118.01 39.38 tangshan 115.29 38.51 baoding # 获取地理位置信息
geopos key member [member ...]
# 获取天津的坐标
geopos cities:locations tianjin # 获取两个坐标之间的距离
# unit代表单位,有4个单位值
- m (meter) 代表米
- km (kilometer)代表千米
- mi (miles)代表英里
- ft (ft)代表尺
geodist key member1 member2 [unit]
# 获取天津和保定之间的距离
GEODIST cities:locations tianjin baoding km # 获取指定位置范围内的地理信息位置集合,此命令可以用于实现附近的人的功能
# georadius和georadiusbymember两个命令的作用是一样的,都是以一个地理位置为中心算出指定半径内的其他地理信息位置,不同的是georadius命令的中心位置给出了具体的经纬度,georadiusbymember只需给出成员即可。其中radiusm|km|ft|mi是必需参数,指定了半径(带单位),这两个命令有很多可选参数,参数含义如下:
# - withcoord:返回结果中包含经纬度。
# - withdist:返回结果中包含离中心节点位置的距离。
# - withhash:返回结果中包含geohash,有关geohash后面介绍。
# - COUNT count:指定返回结果的数量。
# - asc|desc:返回结果按照离中心节点的距离做升序或者降序。
# - store key:将返回结果的地理位置信息保存到指定键。
# - storedist key:将返回结果离中心节点的距离保存到指定键。
georadius key longitude latitude radiusm|km|ft|mi [withcoord] [withdist] [withhash] [COUNT count] [asc|desc] [store key] [storedist key] georadiusbymember key member radiusm|km|ft|mi [withcoord] [withdist] [withhash] [COUNT count] [asc|desc] [store key] [storedist key] # 获取geo hash
# Redis使用geohash将二维经纬度转换为一维字符串,geohash有如下特点:
# - GEO的数据类型为zset,Redis将所有地理位置信息的geohash存放在zset中。
# - 字符串越长,表示的位置更精确,表3-8给出了字符串长度对应的精度,例如geohash长度为9时,精度在2米左右。长度和精度的对应关系,请参考:https://easyreadfs.nosdn.127.net/9F42_CKRFsfc8SUALbHKog==/8796093023252281390
# - 两个字符串越相似,它们之间的距离越近,Redis利用字符串前缀匹配算法实现相关的命令。
# - geohash编码和经纬度是可以相互转换的。
# - Redis正是使用有序集合并结合geohash的特性实现了GEO的若干命令。
geohash key member [member ...] # 删除操作,GEO没有提供删除成员的命令,但是因为GEO的底层实现是zset,所以可以借用zrem命令实现对地理位置信息的删除。
zrem key member

Redis GEO 原理

讲Redis GEO实现之前需要先明白一些关于空间索引的算法GEOHASH的知识。针对索引我们日常所见都是一维的字符,那么如何对三维空间里面的坐标点建立索引呢,直接点就是三维变二维,二维变一维。

地球纬度区间是[-90,90],经度区间是[-180,180]。 将它展开想象成一个矩形。

通过上面的方法将地球的表面转换成二维空间的平面,那接下来就是如何将二维换行成一维了。我们先将平面切割成四个正方形,然后用简单的 01 编码来标识这个四个正方形,最后按照编码的大小将四个正方形连接起来,这样整个平面就转换成了一条Z曲线,变成了一维。我们递归对每个正方形做同样的操作,递归的层次越深,整个平面就逐渐被Z曲线填充。我们的点也会落在每个小正方形里面,小正方形越小,精度就越高。如下图所示:

转成一维以后接下来就如何建立索引了。当我们拿到一个经纬度之后按照如下方式进行编码。

从上面的图可以发现二分的次数越多就越接近经纬度的实际值,和前面提到的不断递归正方形是一个意思。按照上面的方式我们选定一个二分的深度(也就是精度)分别对经纬度进行编码。然后按照以奇数为纬度,偶数为经度组合成一个二进制序列,再将获取到的经纬度组合二进制序列以每5个数为一组,将每一组都进行转换成十进制数字,最后采用Base32对应编码规则进行转换可得到编码,也就是最后的索引。

通过上面几个步骤介绍了一下GeoHash具体的流程、有了上面这个知识点,理解Redis GEO原理就很简单了,Redis使用ZSet的方式存储Geo类型的数据,有序集合里面的member是具体的业务对象,score就是该业务对象的经纬度进行GeoHash编码之后将二级制序列转成52位整数值数据。当我们想要获取某个经纬度附近的元素时候,先根据当前经纬度计算出对应的GeoHash块(52位整数值),在根据半径计算出当前hash块周围的8个hash块,然后在根据score值获取这8个hash块范围内的元素返回。

GEO HASH 延伸

对于一个经纬度,如果我们编码的时候选择对经度二分3次(3位二进制),对维度二分2次(2位二进制),最后组合成一个5位的二级进序列,经过Base32编码得到一个字符。那么这个字符的一共有2^5=32个,这样就将地图划分为32个块。如下图所示

GeoHash将每一个区域画成一块块矩形块,每个矩形块使用一个字符串表示,当我们需要查询附近的点时,通过自己的坐标计算出一个字符串,根据这个字符串定位到我们所在的矩形块,然后返回这个矩形块中的点。然后根据编码的深度来确定精度,或者根据Base32编码之后字符的长度来确定块的所表示的区域大小。

length width height
1 5000km 5000km
2 1250km 625km
3 156km 156km
4 39.1km 19.5km
5 4.89km 4.89km
6 1.22km 0.61km
7 153m 153m
8 38.2m 19.1m
9 4.77m 4.77m
10 1.19m 0.596m
11 149mm 149mm
12 37.2mm 18.6mm

对于这样的编码方式有一定的局限性:在拥有局部保序性的同时,具有突变性。导致一些邻近点真实并不是距离较近的点。

参考

http://geohash.gofreerange.com/

https://halfrost.com/go_spatial_search/

https://www.cnblogs.com/LBSer/p/3310455.html

Redis GEO 功能使用场景的更多相关文章

  1. redis各种数据结构使用场景

    一.redis 数据结构使用场景 原来看过 redisbook 这本书,对 redis 的基本功能都已经熟悉了,从上周开始看 redis 的源码.目前目标是吃透 redis 的数据结构.我们都知道,在 ...

  2. Spring Boot 2 实战:利用Redis的Geo功能实现查找附近的位置

    1. 前言 老板突然要上线一个需求,获取当前位置方圆一公里的业务代理点.明天上线!当接到这个需求的时候我差点吐血,这时间也太紧张了.赶紧去查相关的技术选型.经过一番折腾,终于在晚上十点完成了这个需求. ...

  3. Redis Geo: Redis新增位置查询功能

    转载于:http://www.itxuexiwang.com/a/shujukujishu/redis/2016/0216/144.html 移动互联网增进了人与人之间的联系,其中基于位置信息的服务( ...

  4. 转:Redis Geo: Redis新增位置查询功能

    原文来自于:http://www.infoq.com/cn/news/2015/07/redis-geo 移动互联网增进了人与人之间的联系,其中基于位置信息的服务(Location Based Ser ...

  5. 3.Redis高级功能

    3.Redis高级功能3.1 慢查询分析3.1.1 慢查询的两个配置参数3.1.2 最佳实践3.1.3 单线程架构3.2 Redis Shell3.2.1 redis-cli 详解3.2.2 redi ...

  6. redis数据类型及使用场景

    Redis数据类型  String: Strings 数据结构是简单的key-value类型,value其实不仅是String,也可以是数字. 常用命令:  set,get,decr,incr,mge ...

  7. (3)redis队列功能

    Redis队列功能介绍 List 常用命令: Blpop删除,并获得该列表中的第一元素,或阻塞,直到有一个可用 Brpop删除,并获得该列表中的最后一个元素,或阻塞,直到有一个可用 Brpoplpus ...

  8. Redis数据结构以及应用场景

    1. Redis数据结构以及应用场景 1.1. Memcache VS Redis 1.1.1. 选Memcache理由 系统业务以KV的缓存为主,数据量.并发业务量大,memcache较为合适 me ...

  9. redis GEO地理位置命令介绍

    GEOADD keylongitude latitude member [longitude latitude member ...] Available since 3.2.0. Time comp ...

随机推荐

  1. boostrap栅格系统自适应的布局

    1.栅格系统 ​ Bootstrap是基于移动优先的原则开发的,使用了一系列的媒体查询(media queries)方法,为我们的布局和界面创建自适应的的分界点.这些分界点主要是基于视口宽度的最小值, ...

  2. 还不懂Redis?看完这个故事就明白了!

    我是Redis 你好,我是Redis,一个叫Antirez的男人把我带到了这个世界上. 说起我的诞生,跟关系数据库MySQL还挺有渊源的. 在我还没来到这个世界上的时候,MySQL过的很辛苦,互联网发 ...

  3. Java反序列漏洞

    序列化:WriteObject 反序列化:readObject() Jd-gui.exe 最简单的打开java文件方式 Intellij idea 编辑工具 演示案例: WebGoat_Javaweb ...

  4. Tomcat 第一篇:源码导入 IDEA 编辑器

    1 引言 做 Java 的同学应该都见过上面这只名字叫 Tomcat 的猫,毕竟这只猫在过去和现在都是全球最流行的 Web 容器之一. 很有意思的一件事儿是从我接触这只猫开始,从来不知道它的中文名字是 ...

  5. [LeetCode]Mysql小本本

    常用方法 累加型题目,可以考虑使用笛卡尔积进行自表连接,连接后的表进行where条件进行筛选.group by分组操作. union:需要把两列作一列可以用union,union的两张表查询的字段不一 ...

  6. Jmeter测试工具

    jmeter的简单应用 目录 jmeter的简单应用 1.Jmeter 的基本概念 2.我们 为什么 使用 Jmeter 3.Jmeter的作用 4.Jmeter怎么用 5.安装JAVA环境 6.Jm ...

  7. JDK15真的来了,一起来看看它的新特性

    目录 简介 JDK15的新特性 JEP 385 Deprecate RMI Activation for Removal JEP 371 Hidden Classes JEP 339 Edwards- ...

  8. java虚拟机小贴士之GC分析

    打印日志 通过加入 -XX:+PrintGCDetails 参数则可以打印详细GC信息至控制台.参数-verbose:gc也是可以,但不够详细.通过加入-XX:+PrintGCDateStamps则可 ...

  9. php判断请求方式

    1 /** 2 * 判断是否为get请求 3 * 4 * @return bool 5 */ 6 function is_get():bool 7 { 8 return $_SERVER['REQUE ...

  10. Asp.Net Core SignalR 系列博客

    系列 SignalR+Vue SignalR+Vue 服务端向客户端发送信息 SignalR+Vue+Log4net 实时日志推送 待定...... 源码地址:https://github.com/Q ...