Redis核心技术与实战【学习笔记】 - 7.Redis GEO类型 - 面向 LBS 应用的数据类型

本文主要是介绍Redis核心技术与实战【学习笔记】 - 7.Redis GEO类型 - 面向 LBS 应用的数据类型,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

前面,介绍了 Redis 的 5 大基本数据类型:String、List、Hash、Set、Sorted Set,它们可以满足绝大多数的数据存储需求,但是在面对海里数据统计时,它们的内存开销很大。所以对于一些特殊的场景,它们是无法支持的。所以,Redis 还提供了 3 种扩展数据类型,分别是 Bitmap、HyperLogLog、GEO。今天再介绍下 GEO。


1.面向 LBS 应用的 GEO 数据类型

日常生活中,“附近停车场”、打车软件的叫车,这些都离不开基于位置信息服务(LBS)的应用。LBS 应用访问的数据是和人或物关联的一组经纬度信息,且要能查询相邻的经纬度范围,Redis 的 GEO 就非常适合应用在 LBS 服务的场景中。

1.1 GEO 底层结构

在设计一个数据类型的底层结构时,首先要知道,要处理的数据有什么访问特点。所以,需要先搞清楚位置信息到底是怎么存取的。

以叫车服务为例,来分析下 LBS 应用中经纬度的存取特点:

  1. 每辆网约车都有一个编号(如 1001),网约车需要将自己的经纬度信息(如 117.273521 , 39.884737)发给叫车应用。
  2. 用户在叫车的时候,叫车应用会根据用户的经纬度信息( 117.273521 , 39.884740)查找用户附件的车辆,并进行匹配。
  3. 等把位置相近的用户和车辆匹配上以后,叫车应用就会根据车辆的编号,获取车辆的信息,并返回给用户。

可以看到,一辆车(或一个用户)对应一组经纬度,并且随着车(或用户)的位置移动,相应的经纬度也会变化。

这种数据属于一个 key (例如车辆 ID) 对应一个 value(一组经纬度)。当有很多车辆信息需要保存时,就需要有一个集合来保存一系列的 key 和 value。Hash 集合类型可以快速存取一系列 key 和 value,正好可以记录一系列车辆 ID 和经纬度的对应关系,如下所示:
在这里插入图片描述
此外,Hash 类型的 HSET 操作,可以快速的更新车辆变化的经纬度信息。

目前看来,Hash 类型是一个不错的选择。但是,一个 LBS 应用除了记录经纬度信息外,还需要根据用户经纬度信息在车辆的 Hash 集合中进行范围查找。一旦涉及到范围查询,就意味着集合中的数据是有序的,但 Hash 类型是无需的,不能满足要求。

Sorted Set 类型也支持一个 key 对应一个 value 的记录模式,其中,key 就是 Sorted Set 中的元素,而 value 则是元素的权重分数。此外,Sorted Set 可以根据元素的权重分数排序,支持范围查询。这就能满足 LBS 服务中查找相邻位置的需求了。

而 GEO 类型的底层数据结构就是用 Sorted Set 来实现的。咱们还是接着叫车应用例子,用 Sorted Set 来保存车辆的经纬度信息时,Sorted Set 的元素是车辆 ID,元素的权重分数是经纬度信息,如下图所示:
在这里插入图片描述
此时问题是,Sorted Set 元素的权重分数是一个浮点数(float 类型),而一组经纬度包含的精度和纬度两个值,是没法直接保存为一个浮点数。这就要用到 GEO 类型中的 GeoHash 编码了。

1.2 GeoHash 编码方法

Redis 采用了 GeoHash 编码方法,这个方法的基本原理就是“二分区间,区间编码”。当我们要对一组经纬度进行 GeoHash 编码时,要先对经度和纬度分别编码,然后把经纬度各自的编码组合成一个最终编码。

首先,看下经度和纬度的单独编码过程。

  1. 对于一个地理位置来说,经度范围是 [-180, 180]。
  2. GeoHash 编码会把一个经度值编码成一个 N 位的二进制值,我们来对经度范围 [-180, 180] 做 N 次的二分区操作,其中 N 可以自定义。
  3. 在进行第一次区分时,经度范围 [-180, 180] 会被分成两个子区间:[-180, 0) 和 [0, 180]。此时,看一下编码的经度值是落在了左分区还是右分区。 如果是落在做分区,我们就用 0 表示;如果落在右分区,就用 1 表示。这样一来,每做完一次二分区,我们就可以得到 1 位编码值。
  4. 再对经度值所属的分区,再做一次二分区,同时再次查看经度落在了新二分区的做分区还是右分区,按照刚才的规则再做 1 位编码。当做完 N次的二分区后,经度值就可以用一个 N bit 的数来表示了。

举个例子,假设我们要编码的经度值是 117.273521 ,我们用 5 位编码值(也就是 N = 5,做 5 次分区)。
5. 先做第一次二分区操作,把经度区间 [-180, 180] 分成两个子区间:[-180, 0) 和 [0, 180],此时,经度值 117.273521 是属于右分区 [0, 180],所以,我们用 1 表示第一次二分区后的编码值。
6. 再做第二次二分区:把经度值 117.273521 所属分区 [0, 180] 区间,分成 [0, 90) 和 [90, 180]。此时经度值 117.273521 还是属于右分区 [90, 180],编码值仍为 1。
7. 第三次二分区,经度值 117.273521 落在了左分区 [90, 135) 中,所以,第三次分区后的编码值就是 0。
8. 第四次二分区,经度值 117.273521 落在了右分区 [112.5, 135]中,所以第四次编码值就是 1。
9. 第五次二分区,经度值 117.273521 落在了左分区 [112.5, 123.75) 中,所以第五次次编码值就是 0。

最终,做完 5 次分区后,我们把经度值 117.273521 的 GeoHash 编码值为 11010

对维度的编码方式,和对经度一样,只是经度的范围是 [-90, 90],GeoHash 编码的值为 10111,下标展示了对纬度值 39.884737 的编码过程。

分区次数最小维度值二分区中间值最大维度值维度39.884737所在区间维度的GeoHash编码
第一次-90090[0, 90]1
第二次04590[0, 45)0
第三次022.545[22.5, 45]1
第四次22.533.7545[33.75, 45]1
第五次33.7539.37545[39.375, 45)1

我们再把一组经纬度值都编完码后,再把它们组合在一起,组合的规则是:

  • 最终编码值的偶数位上依次是经度的编码值
  • 奇数位上依次是纬度的编码值
  • 其中偶数位从 0 开始,奇数位从 1 开始。

我们把刚刚计算的经纬度(117.273521, 39.884737)的各自编码值 11010 和 10111 ,组合之后:

  • 第 0 位 :经度的第 0 位 ,为 1
  • 第 1 位:纬度度的第 0 位,为 1
  • 第 2 位:经度的第 1 位,为 1
  • 依次类推,就能得到最终的编码值: 1 1 1 0 0 1 1 1 0 1(加粗的为经度编码值,其他是纬度编码值)

用了 GeoHash 编码后,原本无法表示权重的经纬度,就可以用 1110011101 这个值来表示,就可以保存为 Sorted Set 的权重分数了。

其实,使用 GeoHash 编码后,就相当于把整个地理空间划分成一个个放个,每个放个对应了一个 GeoHash 中的一个分区。举个例子。 我们把经度区间 [-180,180] 做一次二分区,把维度区间 [-90,90] 做一次二分区间,就会得到 4 个分区。我们看看经度和纬度范围及对应的 GeoHash 组合编码:

  • 分区一:[-180, 0) 和 [-90, 0),编码 00
  • 分区二:[-180, 0) 和 [0, 90),编码 01
  • 分区三:[0, 180) 和 [-90, 0),编码 10
  • 分区三:[0, 180) 和 [0, 90),编码 11

这 4 个分区对应了 4 个方格,每个方格覆盖了一定范围内的经纬度,分区越多,每个方格能覆盖到的地理位置就越小,也就越精准。我们把所有方格的编码值映射到一维空间内,相邻方格的 GeoHash 编码值基本也是接近的,如下所示:
在这里插入图片描述
所以,我们使用 Sorted Set 范围查询得到的相近编码值,在实际的地理空间上,也是相邻的方格,这就可以实现 LBS 应用搜索附件人或物的功能了。

不过,需要注意的是,有的编码值虽然在大小上接近,但实际对应的方格确距离比较远。例如,我们用 4 魏来做 GeoHash 编码,把经度区间 [-180,180] 和纬度区间 [-90,90] 分成了 4 个分区,一共 16 个分区。编码值为 0111 和 1000 的两个方格就离的比较远,如下所示:
在这里插入图片描述
所以,为了避免查询不准确问题,我们可以同时查询给定经纬度所在的方格周围的 4 个或 8 个方格。

好了,现在我们知道 GEO 类型是把经纬度所在区间编码作为 Sorted Set 中元素的权重分数,把和经纬度相关的车辆 ID 作为 Sorted Set 中元素本身的值保存下来,这样相邻经纬度的查询就可以通过编码值的大小范围来实现了。

1.3 如何操作 GEO 类型

在使用 GEO 类型的时,我们经常会用到两个命令,分别是 GETADD 和 GEORADIUS。

  • GETADD:把一组经纬度和相对应的一个 ID 记录到 GEO 类型集合中;
  • GEORADIUS:会根据输入的经纬度未知,查找这个纬度为中心的一定范围内的其他元素。

假设车辆 ID 是 1001,经纬度位置是 (117.273521, 39.884737),我们可以用一个 GEO 集合保存所有车辆的经纬度,集合 key 是 cars:locations。执行下面的这个命令,就可以把 ID 号为 1001 的车辆的当前经纬度位置存入 GEO 集合中:

GEOADD cars:locations 117.273521 39.884737 1001

当用户想要查看自己附近的网约车是,LBS 应用就可以使用 GEORADIUS 命令。例如,LBS 应用执行下面的命令时,Redis 会根据输入的用户的经纬度信息( 117.273521 , 39.884740),查找这个经纬度为中心的 5 公里内的车辆信息,并返回给 LBS 应用。

GEORADIUS cars:locations 117.273521 39.884740 5 km ASC COUNT 10

当然,你可以修改 “5” 这个参数,来返回更大或更小范围内的车辆信息。此外,还可以进一步限定返回的车辆信息。

  • 比如我们可以使用 ASC 选项,让返回的车辆按照距离这个中心位置从近到源的方式来排序,以边防选择最近的车辆;
  • 还可以使用 COUNT 选项,指定返回的车辆信息的数量。毕竟,5 公里内的车辆可能有很多,如果返回全部信息,会占用比较多的数据带宽,这个选项可以帮助控制返回的数据量,节省带宽。

可以看到,使用 GEO 数据类型可以非常轻松地操作经纬度这种信息。

这篇关于Redis核心技术与实战【学习笔记】 - 7.Redis GEO类型 - 面向 LBS 应用的数据类型的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/664459

相关文章

Python datetime 模块概述及应用场景

《Pythondatetime模块概述及应用场景》Python的datetime模块是标准库中用于处理日期和时间的核心模块,本文给大家介绍Pythondatetime模块概述及应用场景,感兴趣的朋... 目录一、python datetime 模块概述二、datetime 模块核心类解析三、日期时间格式化与

Redis实现分布式锁全解析之从原理到实践过程

《Redis实现分布式锁全解析之从原理到实践过程》:本文主要介绍Redis实现分布式锁全解析之从原理到实践过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、背景介绍二、解决方案(一)使用 SETNX 命令(二)设置锁的过期时间(三)解决锁的误删问题(四)Re

一文带你搞懂Redis Stream的6种消息处理模式

《一文带你搞懂RedisStream的6种消息处理模式》Redis5.0版本引入的Stream数据类型,为Redis生态带来了强大而灵活的消息队列功能,本文将为大家详细介绍RedisStream的6... 目录1. 简单消费模式(Simple Consumption)基本概念核心命令实现示例使用场景优缺点2

Redis中6种缓存更新策略详解

《Redis中6种缓存更新策略详解》Redis作为一款高性能的内存数据库,已经成为缓存层的首选解决方案,然而,使用缓存时最大的挑战在于保证缓存数据与底层数据源的一致性,本文将介绍Redis中6种缓存更... 目录引言策略一:Cache-Aside(旁路缓存)策略工作原理代码示例优缺点分析适用场景策略二:Re

SpringBoot中四种AOP实战应用场景及代码实现

《SpringBoot中四种AOP实战应用场景及代码实现》面向切面编程(AOP)是Spring框架的核心功能之一,它通过预编译和运行期动态代理实现程序功能的统一维护,在SpringBoot应用中,AO... 目录引言场景一:日志记录与性能监控业务需求实现方案使用示例扩展:MDC实现请求跟踪场景二:权限控制与

redis中使用lua脚本的原理与基本使用详解

《redis中使用lua脚本的原理与基本使用详解》在Redis中使用Lua脚本可以实现原子性操作、减少网络开销以及提高执行效率,下面小编就来和大家详细介绍一下在redis中使用lua脚本的原理... 目录Redis 执行 Lua 脚本的原理基本使用方法使用EVAL命令执行 Lua 脚本使用EVALSHA命令

Redis 热 key 和大 key 问题小结

《Redis热key和大key问题小结》:本文主要介绍Redis热key和大key问题小结,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录一、什么是 Redis 热 key?热 key(Hot Key)定义: 热 key 常见表现:热 key 的风险:二、

C#使用StackExchange.Redis实现分布式锁的两种方式介绍

《C#使用StackExchange.Redis实现分布式锁的两种方式介绍》分布式锁在集群的架构中发挥着重要的作用,:本文主要介绍C#使用StackExchange.Redis实现分布式锁的... 目录自定义分布式锁获取锁释放锁自动续期StackExchange.Redis分布式锁获取锁释放锁自动续期分布式

Java学习手册之Filter和Listener使用方法

《Java学习手册之Filter和Listener使用方法》:本文主要介绍Java学习手册之Filter和Listener使用方法的相关资料,Filter是一种拦截器,可以在请求到达Servl... 目录一、Filter(过滤器)1. Filter 的工作原理2. Filter 的配置与使用二、Listen

C语言中位操作的实际应用举例

《C语言中位操作的实际应用举例》:本文主要介绍C语言中位操作的实际应用,总结了位操作的使用场景,并指出了需要注意的问题,如可读性、平台依赖性和溢出风险,文中通过代码介绍的非常详细,需要的朋友可以参... 目录1. 嵌入式系统与硬件寄存器操作2. 网络协议解析3. 图像处理与颜色编码4. 高效处理布尔标志集合