分布式空间索引了解与扩展
目录
一、空间索引快速理解
(一)区域编码
(二)区域编码检索
(三)Geohash 编码
(四)RTree及其变体
二、业内方案选取
三、分布式空间索引架构
(一)PG数据变更通知服务
(二)空间索引管理服务
(三)空间索引SDK
参考文章
一、空间索引快速理解
假设用户A使用某社交应用希望查看附近的人,以扩展社交圈或寻找志趣相投的人。
假设用户B使用某共享单车应用希望看到当前附近可以骑行的车,以快速查找并进行骑行计划。
这两类场景都需要通过地理位置服务来实现“附近的X”功能。
可以通过限定“附近”的范围来减少检索空间。一般可以将所有的检索空间划分为多个区域并做好编号,然后以区域编号为 key 做好索引。以查找附近的人为例,可以先快速查询到自己所属的区域,然后再将该区域中所有人的位置取出,计算和每一个人的距离就可以了。
(一)区域编码
对于一个完整的二维空间,可以通过二分的思想进行均匀划分。具体方法是在水平方向和垂直方向上分别进行二分,形成四个均匀划分的子空间。对这四个子空间进行编号,可以使用两个比特位。在水平方向上,用 0 表示左边的区域,用 1 表示右边的区域;在垂直方向上,用 0 表示下面的区域,用 1 表示上面的区域。按照顺时针的顺序,从左下角开始编号为 00、01、11 和 10。
通过这样的划分和编号方式,整个二维空间就被划分为多个具有唯一标识的区域。通过继续沿用区域划分的思路,可以将每个区域再分为四块,形成更细致的划分。整个空间会被划分成16块区域,对应的编号也会增加两位。例如,编号为01的区域被划分成4小块,分别为0100、0101、0110、0111。这就是区域编码的基本思路。
(二)区域编码检索
有了区域编码方式后,查询附近的人可以利用区域编码的一维特性,将二维空间的两个维度用一维编码表示。
具体的查询步骤如下:
- 区域编码存储: 将区域编码作为 key 存储在有序数组中。有序数组的排序方式按照区域编码的大小进行排列。
- 二分查找: 使用二分查找技术在有序数组中快速定位自己所属区域的编码。这种方式适用于静态的区域信息,如果区域动态增加,也可以考虑使用二叉检索树或跳表等数据结构进行索引。
- 区域查询: 查询到自己所属区域的编码后,从索引中获取所有属于该区域的用户信息。这一步可以通过有序数组、二叉检索树、跳表等结构进行高效查询。
- 计算距离和排序: 对获取的用户信息进行距离计算,计算每个用户与自己的距离。最后,根据距离排序,展现给用户。
但找到的“附近的人”实际上是同一区域的人,并不一定是离自己最近的人。对于边缘区域的用户,可能在邻接区域里,因此在实际应用中需要权衡查询效率和精确度。如图中的蓝色圆点相比绿色圆点反而是更远的。
为了更精确地找到附近的人,可以建立一个更大的候选集合,将目标区域周围的邻接区域的用户都加入,然后进行距离计算和排序。
操作步骤如下:
-
确定查询半径: 根据期望的查询半径,以当前区域为中心向周围扩散。例如,如果查询半径是一个区域边长的一半,可以涵盖目标区域周围一圈的邻接区域。
-
扩展候选集: 将扩展后的邻接区域中的用户都加入候选集。在示例中,如果考虑目标区域周围的8个邻接区域,可以确保不遗漏任何可能的附近用户。
-
距离计算和排序: 对候选集中的用户进行距离计算,计算每个用户与目标用户的距离。最后,根据距离排序,得到最终的查询结果。
虽然这样的操作会增加计算量,但可以提供更精确的解。如果希望降低计算量,可以考虑提高区域划分的粒度,使得在相同查询半径下需要检索的用户数量减少。这样可以在保持查询效果的情况下降低计算复杂度。
回到区域编码方案本身:要快速寻找目标区域周围的邻接区域的编码,可以利用区域编码的奇偶位拆分水平编码和垂直编码。
通过对这两块编码进行操作,可以得到不同方向上邻接的8个区域的编码。具体操作步骤如下:
-
分解编码: 对于一个区域编码,例如0110,可以分解成水平编码(01)和垂直编码(10)。
-
获取邻接区域编码: 对于当前区域,分别对水平编码和垂直编码进行加1或减1的操作,得到不同方向上邻接的8个区域的编码。例如,如果水平编码是01,那么右边区域的水平编码就是10,垂直编码相同。
通过这样的操作,可以快速获取目标区域周围邻接区域的编码,进而进行候选集的扩展和附近用户的查询。这种方法利用了区域编码的特性,实现了高效的邻接区域查找。
(三)Geohash 编码
在实际工作中,用户的地理位置坐标通常用经纬度表示,而地球可以看作一个大的二维空间,经度和纬度就是这个空间的水平和垂直切分方向。地球的纬度区间是[-90,90],经度是[-180,180]。
如果给出的用户纬度(垂直方向)坐标是 39.983429,经度(水平方向)坐标是 116.490273,那求这个用户所属的区域编码的过程,就可以总结为 3 步:
- 在纬度方向上,第一次二分,39.983429 在[0,90]之间,[0,90]属于空间的上半边,因此我们得到编码 1。然后在[0,90]这个空间上,第二次二分,39.983429 在[0,45]之间,[0,45]属于区间的下半边,因此得到编码 0。两次划分之后得到的编码就是 10。
- 在经度方向上,第一次二分,116.490273 在[0,180]之间,[0,180]属于空间的右半边,因此得到编码 1。然后在[0,180]这个空间上,第二次二分,116.490273 在[90,180]之间,[90,180]还是属于区间的右半边,因此得到的编码还是 1。两次划分之后得到的编码就是 11。
- 纬度的编码和经度的编码交叉组合起来,先是经度,再是纬度。这样就构成了区域编码为 1110。
实际上,如果区域划分的粒度非常细,就要持续、多次二分。而每多二分一次,我们就需要增加一个比特位来表示编码。
如果经度和纬度各二分 15 次的话,那我们就需要 30 个比特位来表示一个位置的编码。那上面例子中的编码就会是 11100 11101 00100 01111 00110 11110。
这样得到的编码会特别长,为了简化编码表示,可以以 5 个比特位为一个单位,把长编码转为 base32 编码,最终得到的就是 wx4g6y。这种将经纬度坐标转换为字符串的编码方式,就叫作 Geohash 编码。大多数应用都会使用 Geohash 编码进行地理位置的表示,以及在很多系统中,比如,Redis、MySQL 以及 Elastic Search 中,也都支持 Geohash 数据的存储和查询。
(四)RTree及其变体
另一种空间索引方法是按照数据进行划分,通常是对空间对象的外接矩形(MBR)进行多级划分。这种方法使用一棵多叉树来保存MBR和空间对象,其中每一级作为树的一层。只有叶子节点存储实际的空间对象,而非叶子节点存储更大范围的MBR。这类空间索引的典型代表是RTree及其变体。
假设我们有一个城市地图上的空间数据集,其中包含了许多建筑物(空间对象)。希望使用RTree来组织这些建筑物的位置信息,以便能够高效地进行范围查询和邻近查询。
-
建立RTree: 开始时,RTree的根节点包含整个城市的外接矩形。每个节点都有一个关联的MBR,而叶子节点包含实际的建筑物信息。
-
插入建筑物: 当新的建筑物被插入时,RTree会调整相应的节点,确保树的平衡。插入操作可能导致节点的分裂,以容纳新的MBR。
-
范围查询: 假设我们要查询城市中某个区域内的所有建筑物。RTree会从根节点开始,逐层检查节点的MBR,仅选择与查询区域相交的节点。这样就能有效剪枝,减少搜索空间,直到到达叶子节点。然后,我们可以检查叶子节点中实际的建筑物数据,找到符合查询条件的建筑物。
-
邻近查询: 假设我们想找到某个特定建筑物附近的其他建筑物。我们首先找到包含该建筑物的叶子节点,然后沿着树遍历到根节点,选择可能与目标建筑物相交或接近的节点。这样,我们可以有效地缩小搜索范围,找到附近的建筑物。
-
删除建筑物: 如果需要删除某个建筑物,RTree会调整相关节点,确保树的平衡。删除操作可能导致节点的合并或调整。
通过RTree,我们能够以高效的方式组织、查询和更新城市地图上的建筑物数据,提供了对多维空间数据的灵活支持。这是一个简化的案例。
二、业内方案选取
空间数据的存储和空间关系计算需要借助空间索引来实现,常见的空间索引有三个方案,本地内存(比如基于RTree)、空间数据库(典型的如PostgreSQL+PostGIS)、分布式KV(比如基于Geohash的RedisGEO)。
方案 | 特点 | 优点 | 缺点 | 典型应用场景 |
---|---|---|---|---|
本地内存索引 (RTree) | 基于树状结构,适用于本地内存中的数据组织和查询 | 空间范围查询和邻近查询性能好 | 不适用于大规模数据存储和分布式计算 | 中小规模数据集的本地应用 |
空间数据库 (PostgreSQL+PostGIS) | 基于关系型数据库系统,提供空间扩展(PostGIS) | 支持复杂的空间查询和分析,数据持久化 | 部署和维护相对复杂,对小规模应用可能过重 | 大规模、复杂的空间数据应用 |
分布式KV (基于Geohash的RedisGEO) | 基于键值存储系统,使用Geohash编码 | 高效的地理位置查询,适用于分布式环境 | 不适用于复杂的空间查询和分析 | 实时位置服务,分布式系统 |
选择合适的空间索引方案通常取决于应用的需求和规模。如果是小规模、单机的应用,RTree等本地内存索引可能足够;对于复杂查询和大规模数据集,空间数据库是一个不错的选择;而分布式KV适用于需要分布式处理和实时查询的场景。
三、分布式空间索引架构
只提供一个简单的架构思考(实际中一定不能单独只用一种,这样实现上很受限,最好是结合着实际情况进行整合设计),具体应用请根据实际内容进行设置处理。基于读写分离的CQRS(Command Query Responsibility Segregation)架构,其中查询端和管理端之间通过消息传输实现松耦合。
(一)PG数据变更通知服务
服务类型: 自研的PostgreSQL版本DTS服务。
职责: 负责订阅PG数据源的变更并进行变更通知。
功能: 提供实时的数据变更订阅功能,使得其他系统能够获取PG数据库的最新状态。
(二)空间索引管理服务
职责: 管理空间索引,包括多集群、多分片、多节点的管理。
功能:
- 增删集群、分片、节点。
- 数据分发、一致性保障。
- 处理围栏变更、主从切换、zk状态更新等操作。
- 提供可视化管理页面和基于Prometheus的监控报警平台。
(三)空间索引SDK
职责: 客户端路由和空间索引节点服务发现。
功能:
- 利用Lettuce非阻塞响应式开源库处理客户端请求。
- 提供空间索引节点的服务发现,确保客户端能够准确路由请求。
- 基于公司的ZK平台提供额外能力。
整体架构的特点包括CQRS的使用,读写分离,消息传输的松耦合,以及对PG数据库和空间索引的有效管理和通知机制。这样的设计有助于系统的可维护性、伸缩性和性能优化。
参考文章
1.13 | 空间检索(上):如何用Geohash实现“查找附近的人”功能?-极客时间
2.RTree算法及介绍_c# rtree-CSDN博客
3.开源方案距离计算问题:https://github.com/tidwall/tile38/issues/222
4.RTree的距离计算算法:Для просмотра статьи разгадайте капчу
相关文章:

分布式空间索引了解与扩展
目录 一、空间索引快速理解 (一)区域编码 (二)区域编码检索 (三)Geohash 编码 (四)RTree及其变体 二、业内方案选取 三、分布式空间索引架构 (一)PG数…...

Set和Map的应用场景
Set: 1.成员不能重复 2.只有键值,没有键名,有点类似数组 3.可以遍历,方法 add,delete,has Map: 1.本质上是键值对的集合,类似集合; 2.可以遍历,方法很多,可以干跟各种数据格式转换 Set和…...

小白级教程,10秒开服《幻兽帕鲁》
在帕鲁的世界,你可以选择与神奇的生物「帕鲁」一同享受悠闲的生活,也可以投身于与偷猎者进行生死搏斗的冒险。帕鲁可以进行战斗、繁殖、协助你做农活,也可以为你在工厂工作。你也可以将它们进行售卖,或肢解后食用。 前言 马上过年…...

IDEA 构建开发环境
本博客主要讲解了如何创建一个Maven构建Java项目。(本文是创建一个用Maven构建项目的方式,所以需要对Maven有一定的了解) IDEA 构建开发环境 一、创建一个空工程二、构建一个普通的Maven模块 一、创建一个空工程 创建一个空的工程 * 设置整…...

归并排序----C语言数据结构
目录 引言 1.归并排序的实现----c2.归并排序的复杂度分析时间复杂度空间复杂度 引言 归并排序(Merge Sort) 是一种基于分治法的排序算法,它的基本思想是将原始数组划分成较小的数组,然后递归地对这些小数组进行排序,最后将排好序…...

【网站项目】065健康综合咨询问诊平台
🙊作者简介:拥有多年开发工作经验,分享技术代码帮助学生学习,独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。🌹赠送计算机毕业设计600个选题excel文件,帮助大学选题。赠送开题报告模板ÿ…...

Adobe Camera Raw forMac/win:掌控原始之美的秘密武器
Adobe Camera Raw,这款由Adobe开发的插件,已经成为摄影师和设计师们的必备工具。对于那些追求完美、渴望探索更多创意可能性的专业人士来说,它不仅仅是一个插件,更是一个能够释放无尽创造力的平台。 在数字摄影时代,R…...

OpenHarmony—开发及引用静态共享包(API 9)
HAR(Harmony Archive)是静态共享包,可以包含代码、C库、资源和配置文件。通过HAR可以实现多个模块或多个工程共享ArkUI组件、资源等相关代码。HAR不同于HAP,不能独立安装运行在设备上,只能作为应用模块的依赖项被引用。 接下来&a…...

测试面试题常见题
文章目录 功能测试一个完整的测试计划应该包含哪些内容一个完整的测试用例包含哪些内容?什么时候需要发测试报告?一份测试报告应该包含哪些内容?一个完整的缺陷报告应该包含哪些内容?简述等价类划分法并举例针对具体场景的测试用例…...

代码随想录算法训练营第六天 - 哈希表part02
454.四数之和II 核心思想:利用字典的key,value 4个数组两两分组,nums1nums2 的两两元素之和 及 计数 先存入字典中,然后对nums3和nums4的进行元素相加 然后对比字典中是否有对应的key,有就countvalue class Solution…...

【Javaweb程序设计】【C00165】基于SSM的高考志愿辅助填报系统(论文+PPT)
基于SSM的高考志愿辅助填报系统(论文PPT) 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于ssm的高考志愿辅助填报系统 本系统分为前台系统模块、后台管理员模块以及后台学生模块 前台系统模块:当游客打开系统的网址后&…...

海外云手机为什么吸引用户?
近年来,随着全球化的飞速发展,海外云手机逐渐成为各行各业关注的焦点。那么,究竟是什么让海外云手机如此吸引用户呢?本文将深入探讨海外云手机的三大吸引力,揭示海外云手机的优势所在。 1. 高效的社交媒体运营 海外云…...

将`List<String>`转换为`List<Long>`
将List<String>转换为List<Long> 大家好,我是免费搭建查券返利机器人赚佣金就用微赚淘客系统3.0的小编,也是冬天不穿秋裤,天冷也要风度的程序猿!在Java中,将List<String>转换为List<Long>可以…...

【Unity3D小功能】Unity3D中Text使用超链接并绑定点击事件
推荐阅读 CSDN主页GitHub开源地址Unity3D插件分享简书地址我的个人博客 大家好,我是佛系工程师☆恬静的小魔龙☆,不定时更新Unity开发技巧,觉得有用记得一键三连哦。 一、前言 在开发中遇到了要给Text加超链接的需求,研究了实现…...

MyBatis-Plus CRUD 接口
Service CRUD 接口 public String services() {Boolean re false;/**Service CRUD 接口**//**Save 返回boolean **///1、插入一条数据Person person1 new Person();person1.setEmail("123qq.com");person1.setSex("男");//person1.setUser_id(0);//影响…...

在JVM中,Java对象是如何创建、存储和访问的?
在Java虚拟机(JVM)中,Java对象的创建、存储和访问是Java程序运行的核心部分。这个过程涉及到内存管理、对象模型以及运行时数据区域的概念。 1. Java对象的创建: a. 类加载: 在Java程序运行时,类加载器负…...

C++类和对象之进击篇
目录 1.类的6个默认成员函数2.构造函数2.1概念2.2特性 3.析构函数3.1概念3.2特性 4.拷贝构造函数4.1 概念4.2特征 5.赋值运算符重载5.1运算符重载5.2赋值运算符重载5.3前置和后置重载 6.日期类的实现7.const成员8.取地址及const取地址操作符重载 1.类的6个默认成员函数 如果一…...

ElementUI 组件:Container 布局容器
ElementUI安装与使用指南 Container 布局容器 点击下载learnelementuispringboot项目源码 效果图 el-container.vue(Container 布局容器)页面效果图 项目里el-container.vue代码 <script> import PagePath from "/components/PagePat…...

小米商城服务治理之客户端熔断器(Google SRE客户端熔断器)
目录 前言 一、什么是Google SRE熔断器 二、Google SRE 熔断器的工作流程: 三、客户端熔断器 (google SRE 熔断器) golang GRPC 实现 四、客户端熔断器 (google SRE 熔断器) golang GRPC单元测试 大家可以关注个人博客:xingxing – Web Developer …...

Springboot 校验工具类
校验工具类 这个实现逻辑很简单,就是调用string的正则表达式 我这里的代码要导入糊涂工具包 <dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.7.17</version> </dependency>import…...

编程笔记 html5cssjs 069 JavaScrip Undefined数据类型
编程笔记 html5&css&js 069 JavaScrip Undefined数据类型 一、undefined数据类型二、类型运算小结 在JavaScript中,undefined 是一种基本数据类型,它表示一个变量已经声明但未定义(即没有赋值)或者一个对象属性不存在。 一…...

MySQL 处理JSON字符串
目录 前言 JSON值的部分更新 创建JSON值 JSON 值的规范化、合并和自动包装 合并JSON值 搜索和修改JSON值 JSON路径 JSON值的比较和排序 JSON值的聚合 前言 现在很多数据会以json格式存储,如果你还在用like查询json字符串,那你就OUT了࿰…...

python爬虫-多线程-数据库——WB用户
数据库database的包: Python操作Mysql数据库-CSDN博客 效果: 控制台输出: 数据库记录: 全部代码: import json import os import threading import tracebackimport requests import urllib.request from utils im…...

有向图查询所有环,非递归
图: 有向图查询所有环,非递归: import java.util.*;public class CycleTest {private final int V; // 顶点数private final List<List<Integer>> adjList; // 邻接表public CycleTest(int vertices) {this.V vertices;this.…...

SpringBoot 使用WebSocket功能
实现步骤: 1.导入WebSocket坐标。 在pom.xml中增加依赖项: <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId> </dependency>2.编写WebSocket配…...

HTML5的新特性
目录 一,新增语义化标签 二,新增的多媒体标签 三,新增input表单 四,新增的表单属性 一,新增语义化标签 二,新增的多媒体标签 1,音频:<audio>.。。用MP3 <audio src…...

Filter过滤器学习使用
验证token 对外API过滤器 public class APIFilter implements Filter {private static Logger logger LogManager.getLogger(APIFilter.class);private ICachedTokenService tokenService;public APIFilter(ICachedTokenService tokenService) {super();this.tokenService …...

关于修改数据库服务器时间导致达梦数据库集群裂开
故障原因: 因为每天数据库服务器时间都不一致,想要给数据库服务器配置个NTP服务器。结果导致达梦数据库裂库。后面查看了达梦系统管理员手册了解了达梦集群的机制,知道数据库服务器时间需要先关闭数据库服务之后才可以修改数据库服务器时间。…...

自定义包的设计与实现
这是一个 CPacket 类,用于解析包含固定格式的数据。该类的成员变量包括固定包头 sHead、包长度 nLength、控制命令 sCmd、包数据 strData 和和校验 sSum。 构造函数: CPacket():默认构造函数,初始化成员变量。 CPacket(const B…...

时机成熟了
时机成熟了。 有一个老乡群,一到年底就各种人找车、车找人的消息。这些消息如果能直接爬取到一个小的网页里面去,则可以极大地便利大家做检索。如何把非结构化的内容转成结构化的 json,在以前是一个难题,但是有了 ChatGPT&#x…...