当前位置: 首页 > news >正文

【实战篇】GEO是什么?还可以定义新的数据类型吗?

背景

之前,我们学习了 Redis 的 5 大基本数据类型:String、List、Hash、Set 和 Sorted Set,它们可以满足大多数的数据存储需求,但是在面对海量数据统计时,它们的内存开销很大,而且对于一些特殊的场景,它们是无法支持的。所以,Redis 还提供了 3 种扩展数据类型,分别是 Bitmap、HyperLogLog 和 GEO。前两种我们已经重点介绍过了,今天,再具体讲一讲 GEO。

面向 LBS 应用的 GEO 数据类型

在日常生活中,我们越来越依赖搜索“附近的餐馆”、在打车软件上叫车,这些都离不开基于位置信息服务(Location-Based Service,LBS)的应用。LBS 应用访问的数据是和人或物关联的一组经纬度信息,而且要能查询相邻的经纬度范围,GEO 就非常适合应用在 LBS 服务的场景中,我们来看一下它的底层结构。

GEO 的底层结构

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

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

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

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

Hash

这种数据记录模式属于一个 key(例如车 ID)对应一个 value(一组经纬度)。当有很多车辆信息要保存时,就需要有一个集合来保存一系列的 key 和 value。Hash 集合类型可以快速存取一系列的 key 和 value,正好可以用来记录一系列车辆 ID 和经纬度的对应关系,所以,我们可以把不同车辆的 ID 和它们对应的经纬度信息存在 Hash 集合中,如下图所示:
在这里插入图片描述
同时,Hash 类型的 HSET 操作命令,会根据 key 来设置相应的 value 值,所以,我们可以用它来快速地更新车辆变化的经纬度信息。

到这里,Hash 类型看起来是一个不错的选择。但问题是,对于一个 LBS 应用来说,除了记录经纬度信息,还需要根据用户的经纬度信息在车辆的 Hash 集合中进行范围查询。一旦涉及到范围查询,就意味着集合中的元素需要有序,但 Hash 类型的元素是无序的,显然不能满足我们的要求。

Sortted Set

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

实际上,GEO 类型的底层数据结构就是用 Sorted Set 来实现的。咱们还是借着叫车应用的例子来加深下理解。

用 Sorted Set 来保存车辆的经纬度信息时,Sorted Set 的元素是车辆 ID,元素的权重分数是经纬度信息,如下图所示:在这里插入图片描述
这时问题来了,Sorted Set 元素的权重分数是一个浮点数(float 类型),而一组经纬度包含的是经度和纬度两个值,是没法直接保存为一个浮点数的,那具体该怎么进行保存呢?

这就要用到 GEO 类型中的 GeoHash 编码了。

GeoHash 的编码方法

为了能高效地对经纬度进行比较,Redis 采用了业界广泛使用的 GeoHash 编码方法,这个方法的基本原理就是“二分区间,区间编码”。

当我们要对一组经纬度进行 GeoHash 编码时,我们要先对经度和纬度分别编码,然后再把经纬度各自的编码组合成一个最终编码。

对于一个地理位置信息来说,它的经度范围是[-180,180]。GeoHash 编码会把一个经度值编码成一个 N 位的二进制值,我们来对经度范围[-180,180]做 N 次的二分区操作,其中 N 可以自定义。

在进行第一次二分区时,经度范围[-180,180]会被分成两个子区间:[-180,0) 和[0,180](我称之为左、右分区)。此时,我们可以查看一下要编码的经度值落在了左分区还是右分区。如果是落在左分区,我们就用 0 表示;如果落在右分区,就用 1 表示。这样一来,每做完一次二分区,我们就可以得到 1 位编码值。

然后,我们再对经度值所属的分区再做一次二分区,同时再次查看经度值落在了二分区后的左分区还是右分区,按照刚才的规则再做 1 位编码。当做完 N 次的二分区后,经度值就可以用一个 N bit 的数来表示了。
在这里插入图片描述
在这里插入图片描述
当一组经纬度值都编完码后,我们再把它们的各自编码值组合在一起,组合的规则是:最终编码值的偶数位上依次是经度的编码值,奇数位上依次是纬度的编码值,其中,偶数位从 0 开始,奇数位从 1 开始。

我们刚刚计算的经纬度(116.37,39.86)的各自编码值是 11010 和 10111,组合之后,第 0 位是经度的第 0 位 1,第 1 位是纬度的第 0 位 1,第 2 位是经度的第 1 位 1,第 3 位是纬度的第 1 位 0,以此类推,就能得到最终编码值 1110011101,如下图所示:
在这里插入图片描述
用了 GeoHash 编码后,原来无法用一个权重分数表示的一组经纬度(116.37,39.86)就可以用 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 个分区,对应了 16 个方格。编码值为 0111 和 1000 的两个方格就离得比较远,如下图所示:
在这里插入图片描述
所以,为了避免查询不准确问题,我们可以同时查询给定经纬度所在的方格周围的 4 个或 8 个方格。

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

如何操作 GEO 类型?

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

  • GEOADD 命令:用于把一组经纬度信息和相对应的一个 ID 记录到 GEO 类型集合中;
  • GEORADIUS 命令:会根据输入的经纬度位置,查找以这个经纬度为中心的一定范围内的其他元素。当然,我们可以自己定义这个范围。

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

GEOADD cars:locations 116.034579 39.030452 33

当用户想要寻找自己附近的网约车时,LBS 应用就可以使用 GEORADIUS 命令。例如,LBS 应用执行下面的命令时,Redis 会根据输入的用户的经纬度信息(116.054579,39.030452 ),查找以这个经纬度为中心的 5 公里内的车辆信息,并返回给 LBS 应用。当然, 你可以修改“5”这个参数,来返回更大或更小范围内的车辆信息。

GEORADIUS cars:locations 116.054579 39.030452 5 km ASC COUNT 10

另外,我们还可以进一步限定返回的车辆信息。

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

如何自定义数据类型?

为了实现自定义数据类型,首先,我们需要了解 Redis 的基本对象结构 RedisObject,因为 Redis 键值对中的每一个值都是用 RedisObject 保存的。

RedisObject 包括元数据和指针。其中,元数据的一个功能就是用来区分不同的数据类型,指针用来指向具体的数据类型的值。所以,要想开发新数据类型,我们就先来了解下 RedisObject 的元数据和指针。

Redis 的基本对象结构

RedisObject 的内部组成包括了 type、encoding、lru 和 refcount 4 个元数据,以及 1 个*ptr指针。

  • type:表示值的类型,涵盖了我们前面学习的五大基本类型;
  • encoding:是值的编码方式,用来表示 Redis 中实现各个基本类型的底层数据结构,例如 SDS、压缩列表、哈希表、跳表等;
  • lru:记录了这个对象最后一次被访问的时间,用于淘汰过期的键值对;
  • refcount:记录了对象的引用计数;
  • ptr:是指向数据的指针。
    在这里插入图片描述
    RedisObject 结构借助
    ptr指针,就可以指向不同的数据类型,例如,ptr指向一个 SDS 或一个跳表,就表示键值对中的值是 String 类型或 Sorted Set 类型。所以,我们在定义了新的数据类型后,也只要在 RedisObject 中设置好新类型的 type 和 encoding,再用ptr指向新类型的实现,就行了。

开发一个新的数据类型

了解了 RedisObject 结构后,定义一个新的数据类型也就不难了。首先,我们需要为新数据类型定义好它的底层结构、type 和 encoding 属性值,然后再实现新数据类型的创建、释放函数和基本命令。
在这里插入图片描述
这里不再缀诉,有兴趣的可以自己去研究一下。

小结

这篇文章,我们学习了 Redis 的扩展数据类型 GEO。GEO 可以记录经纬度形式的地理位置信息,被广泛地应用在 LBS 服务中。GEO 本身并没有设计新的底层数据结构,而是直接使用了 Sorted Set 集合类型。

GEO 类型使用 GeoHash 编码方法实现了经纬度到 Sorted Set 中元素权重分数的转换,这其中的两个关键机制就是对二维地图做区间划分,以及对区间进行编码。一组经纬度落在某个区间后,就用区间的编码值来表示,并把编码值作为 Sorted Set 元素的权重分数。这样一来,我们就可以把经纬度保存到 Sorted Set 中,利用 Sorted Set 提供的“按权重进行有序范围查找”的特性,实现 LBS 服务中频繁使用的“搜索附近”的需求。

GEO 属于 Redis 提供的扩展数据类型。扩展数据类型有两种实现途径:一种是基于现有的数据类型,通过数据编码或是实现新的操作的方式,来实现扩展数据类型,例如基于 Sorted Set 和 GeoHash 编码实现 GEO,以及基于 String 和位操作实现 Bitmap;另一种就是开发自定义的数据类型,具体的操作是增加新数据类型的定义,实现创建和释放函数,实现新数据类型支持的命令操作。

相关文章:

【实战篇】GEO是什么?还可以定义新的数据类型吗?

背景 之前,我们学习了 Redis 的 5 大基本数据类型:String、List、Hash、Set 和 Sorted Set,它们可以满足大多数的数据存储需求,但是在面对海量数据统计时,它们的内存开销很大,而且对于一些特殊的场景&…...

SpringBoot最佳实践之 - 项目中统一记录正常和异常日志

1. 前言 此篇博客是本人在实际项目开发工作中的一些总结和感悟。是在特定需求背景下,针对项目中统一记录日志(包括正常和错误日志)需求的实现方式之一,并不是普适的记录日志的解决方案。所以阅读本篇博客的朋友,可以参考此篇博客中记录日志的…...

【Flutter】状态管理:高级状态管理 (Riverpod, BLoC)

当项目变得更加复杂时,简单的状态管理方式(如 setState() 或 Provider)可能不足以有效地处理应用中状态的变化和业务逻辑的管理。在这种情况下,高级状态管理框架,如 Riverpod 和 BLoC,可以提供更强大的工具…...

OAK相机的RGB-D彩色相机去畸变做对齐

▌低畸变标准镜头的OAK相机RGB-D对齐的方法 OAK相机内置的RGB-D管道会自动将深度图和RGB图对齐。其思想是将深度图像中的每个像素与彩色图像中对应的相应像素对齐。产生的RGB-D图像可以用于OAK内置的图像识别模型将识别到的2D物体自动映射到三维空间中去,或者产生的…...

smartctl硬盘检查工具

一、smartctl工具简介   Smartmontools是一种硬盘检测工具,通过控制和管理硬盘的SMART(Self Monitoring Analysis and Reporting Technology),自动检测分析及报告技术)技术来实现的,SMART技术可以对硬盘的磁头单元、盘片电机驱动系统、硬盘…...

清空MySQL数据表

要清空 MySQL 数据表,您可以使用 TRUNCATE 或 DELETE 命令 使用 TRUNCATE 命令 TRUNCATE 命令用于删除表中的所有数据,并重置自增 ID(如果存在): TRUNCATE TABLE table_name;将 table_name 替换为您要清空的表的名称…...

2024年妈杯MathorCup大数据竞赛A题超详细解题思路

2024年妈杯大数据竞赛初赛整体难度约为0.6个国赛。A题为台风中心路径相关问题,为评价预测问题;B题为库存和销量的预测优化问题。B题难度稍大于A题,可以根据自己队伍情况进行选择。26日早六点之前发布AB两题相关解题代码论文。 下面为大家带来…...

Kafka系列之:Kafka集群磁盘条带划分和Kafka集群磁盘扩容详细方案

Kafka系列之:Kafka集群磁盘条带划分和Kafka集群磁盘扩容详细方案 一、lsblk命令二、Kafka节点磁盘条带化方案一三、Kafka节点磁盘条带化方案二四、理解逻辑区块LE五、查看kafka节点磁盘条带划分情况六、Kafka节点磁盘扩容一、lsblk命令 lsblk命令用于列出块设备的信息,包括磁…...

【LeetCode】修炼之路-0007- Reverse Integer (整数反转)【python】

题目 Reverse Integer Given a signed 32-bit integer x, return x with its digits reversed. If reversing x causes the value to go outside the signed 32-bit integer range [-231, 231 - 1], then return 0. Assume the environment does not allow you to store 64-b…...

【Flutter】页面布局:线性布局(Row 和 Column)

在 Flutter 中,布局(Layout)是应用开发的核心之一。通过布局组件,开发者可以定义应用中的控件如何在屏幕上排列。Row 和 Column 是 Flutter 中最常用的两种线性布局方式,用于水平和垂直排列子组件。在本教程中&#xf…...

C语言巨难题:执行操作可获得的最大总奖励 I(C语言版)

1.题目: 给你一个整数数组 rewardValues,长度为 n,代表奖励的值。 最初,你的总奖励 x 为 0,所有下标都是 未标记 的。你可以执行以下操作 任意次 : 从区间 [0, n - 1] 中选择一个 未标记 的下标 i。如果…...

【力扣】GO解决子序列相关问题

文章目录 一、引言二、动态规划方法论深度提炼子序列问题的通用解法模式 三、通用方法论应用示例:最长递增子序列(LeetCode题目300)Go 语言代码实现 四、最长连续递增序列(LeetCode题目674)Go 语言代码实现 五、最长重…...

Ubuntu20.04安装VM tools并实现主机和虚拟机之间文件夹共享

1、Ubuntu20.04安装VM tools 参考这个,很详细:Ubuntu 20.04 安装 VMwareTools 教程 2、实现主机与VMware虚拟机共享文件夹 设置共享文件夹参考:windows和虚拟机互传文件的三种方式 挂载操作参考:主机与VMware虚拟机共享文件夹&…...

Linux 学习笔记(十七)—— 文件系统

终极目标:理解 inode 和 软硬连接; 文件系统:Ext2; 文件 文件内容 文件属性; ——> 磁盘上存储的文件 存储的文件内容 存储的文件属性; Linux系统中:文件内容使用数据块存储,文件属性使用inode(固定…...

【计算机网络 - 基础问题】每日 3 题(五十八)

✍个人博客:https://blog.csdn.net/Newin2020?typeblog 📣专栏地址:http://t.csdnimg.cn/fYaBd 📚专栏简介:在这个专栏中,我将会分享 C 面试中常见的面试题给大家~ ❤️如果有收获的话,欢迎点赞…...

Netty入门基础:IO模型中BIO\NIO概念及区别【附演示代码】

文章目录 😀BIO💢实战demo 🌈NIO🏍Buffer核心属性核心方法 🎗Channel🎈Selector核心方法 🧨实战demo 🎨粘包与半包 😀BIO 传统IO模型,同步阻塞,每…...

vue2 使用环境变量

一. 在根目录下创建.env.xxx文件 .env 基础系统变量,无论何种环境,都可使用其中配置的值,其他环境中的变量会覆盖.env中的同名变量。 .env.development 开发环境 .env.production 生产环境 .env.staging 测试环境 二. 内容格式 vue2 使用是以…...

数据预处理

继续提取代码片段: 12. **导入iris数据集并查看前5行数据**: python from sklearn.datasets import load_iris iris load_iris() X iris.data print(iris数据集的维度为:, X.shape) print(iris数据集的前5行数据为:\n, X[:5]) …...

django宠物领养管理系统-计算机毕业设计源码26858

目录 1 绪论 1.1 选题背景与意义 1.2国内外研究现状 1.3论文结构与章节安排 2系统分析 2.1 可行性分析 2.2 系统流程分析 2.2.1 数据流程 3.3.2 业务流程 2.3 系统功能分析 2.3.1 功能性分析 2.3.2 非功能性分析 2.4 系统用例分析 2.5本章小结 3 系统总体设计 3…...

使用TeamViewer远程局域网内的两台电脑

有个场景,有人还不知道TV可以局域网操作,记录一下。 主要就是修改设置,将取消激活改为接受 然后输入受控端的ip即可...

GUI简介、Swing的常用组件、java程序的运行过程、class文件、JAR、runable_jar、双括号初始化

GUI简介 GUI:图形用户界面,在计算机中采用图形的方式显示用户界面 java的GUI开发 AWT:java最早推出的GUI编程开发包,界面风格跟随操作系统SWT:eclipse就是java使用SWT开发的Swing:在AWT的基础上扩充了功能…...

@Autowired和@Resource和getBean()区别

今天遇到一个对我来说很奇葩的错误,我想在Service中注入bean,我这里使用了Autowired和Resource都不能注入,导致初始化失败,使用了getBean()方法就可以注入。从来没有遇到过这个问题。后来我查询了一下,才明白了原理。我…...

Merlion笔记(四):添加一个新的预测模型

文章目录 1 模型配置类2 模型类3 运行模型:一个简单的例子4 可视化5 定量评估6 定义一个基于预测器的异常检测器 本文提供了一个示例,展示如何向 Merlion 添加一个新的预测模型,遵循 CONTRIBUTING.md 中的说明。建议在阅读本篇文章之前,先查…...

【论文阅读】ESRGAN

学习资料 论文题目:增强型超分辨率生成对抗网络(ESRGAN: Enhanced Super-Resolution Generative Adversarial Networks)论文地址:[1809.00219] ESRGAN:增强型超分辨率生成对抗网络代码:xinntao / ESRGAN&am…...

电脑异常情况总结

文章目录 笔记本无症状息屏黑屏 笔记本无症状息屏黑屏 🍎 问题描述: 息屏导致黑屏;依次操作计算机--》右键--》管理--》事件查看器--》Windows日志--》系统;从息屏到异常黑屏之间出现了很多错误,如下:事件…...

[项目详解][boost搜索引擎#1] 概述 | 去标签 | 数据清洗 | scp

目录 一、前言 二、项目的相关背景 三、搜索引擎的宏观原理 四、搜索引擎技术栈和项目环境 五、正排索引 VS 倒排索引--原理 正排索引 分词 倒排索引 六、编写数据去除标签和数据清洗模块 Parser 1.数据准备 parser 编码 1.枚举文件 EnumFile 2.去标签ParseHtml(…...

PL/I语言的起源?有C语言,有B语言和A语言吗?为什么shell脚本最开始可能有#!/bin/bash字样?为什么不支持嵌套注释?

PL/I语言的起源 在20世纪50~60年代,当时主流的编程语言是COBOL/FORTRAN/ALGOL等,IBM想要设计一门通用的编程语言,已有的编程语言无法实现此要求,故想要设计一门新语言,即是PL/I. PL/I是Programming Language/One的缩写…...

gin入门教程(3):创建第一个 HTTP 服务器

首先设置golang github代理,可解决拉取git包的时候,无法拉取的问题: export GOPROXYhttps://goproxy.io再查看自己的go版本: go version我这里的版本是:go1.23.2 linux/arm64 准备工作做好之后就可以进行开发了 3.…...

Vue+ECharts+iView实现大数据可视化大屏模板

Vue数据可视化 三个大屏模板 样式还是比较全的 包括世界地图、中国地图、canvas转盘等 项目演示: 视频: vue大数据可视化大屏模板...

el-table 表格设置必填项

el-table 表格设置必填项 要在 el-table 中集成 el-form 来设置必填项,并进行表单验证,可以使用 Element UI 提供的表单验证功能。下面是一个详细的示例,展示了如何在 el-table 中使用 el-form 来设置必填项,并进行验证。 示例代…...

h5网站开发软件下载/站长之家官网登录入口

问题 Push failed: fatal: Authentication failed for httpspycharm invalid authentication data couldn’t kickstart handshaking 解决...

系部网站开发计划/全面落实疫情防控优化措施

走进异步编程的世界 - 在 GUI 中执行异步操作 【博主】反骨仔  【原文地址】http://www.cnblogs.com/liqingwen/p/5877042.html 序 这是继《开始接触 async/await 异步编程》、《走进异步编程的世界 - 剖析异步方法》后的第三篇。主要介绍在 WinForm 中如何执行异步操作。 目…...

网页设计作业样例/seo基础教程

光敏电阻器(photoresistor)又叫光感电阻,它的电阻值会根据光的强度变化而变化。 现在我要把它接入树莓派Pico,观察电阻值随光强度的变化情况,本试验参考的文章:https://peppe8o.com/how-to-use-a-photoresi…...

网络编程有哪些/百度搜索优化

焦红伟,博士,副教授,硕士生导师,河南省高校青年骨干教师。2015年毕业于西安电子科技大学,获理学博士学位。主要从事最优化算法及应用、系统优化、智能优化算法及应用等方面的研究工作。近年来对非凸规划问题的全局优化…...

怎么做动态的实时更新的网站/免费平台

1. 基本语法test condition或[ condition ]使用括号方式时,条件与括号之间要加空格。2. 测试文件状态例:测试文件 scores.txe 是否可执行。 [rootlocalhost ~]# [ -x scores.txt ] [rootlocalhost ~]# echo $? 1 条件测试可以使用以下条件表达…...

沧州网站设计哪家好/做营销策划的公司

1.什么是HttpClient Http 是Hyper-Text Transfer Protocol简写,迄今为止互联网应用最广泛的协议。网络服务、互联网应用、网络计算需求的增长,持续推动http协议应用范围不断扩展。 java.net包提供http方式访问资源的最基本功能,httpClient在其…...