Redis的GEO
Redis 在 3.2 版本以后增加了地理位置 GEO 模块,意味着我们可以使用 Redis 来实现摩拜单车「附近的 Mobike」、美团和饿了么「附近的餐馆」这样的功能了。
用数据库来算附近的人
地图元素的位置数据使用二维的经纬度表示,经度范围 (-180, 180],纬度范围 (-90,90],纬度正负以赤道为界,北正南负,经度正负以本初子午线 (英国格林尼治天文台) 为
界,东正西负。比如掘金办公室在望京 SOHO,它的经纬度坐标是 (116.48105,39.996794),都是正数,因为中国位于东北半球。
当两个元素的距离不是很远时,可以直接使用勾股定理就能算得元素之间的距离。我们平时使用的「附近的人」的功能,元素距离都不是很大,勾股定理算距离足矣。不过需要注
意的是,经纬度坐标的密度不一样 (经度总共 360 度,纬度总共 180 度),勾股定律计算平方差时之后再求和时,需要按一定的系数比加权求和。
现在,如果要计算「附近的人」,也就是给定一个元素的坐标,然后计算这个坐标附近的其它元素,按照距离进行排序,该如何下手?
如果现在元素的经纬度坐标使用关系数据库 (元素 id, 经度 x, 纬度 y) 存储,你该如何计算?首先,你不可能通过遍历来计算所有的元素和目标元素的距离然后再进行排序,这个计
算量太大了,性能指标肯定无法满足。一般的方法都是通过矩形区域来限定元素的数量,然后对区域内的元素进行全量距离计算再排序。这样可以明显减少计算量。如何划分矩形区域
呢?可以指定一个半径 r,使用一条 SQL 就可以圈出来。当用户对筛出来的结果不满意,那就扩大半径继续筛选。
select id from positions where x0-r < x < x0+r and y0-r < y < y0+r
为了满足高性能的矩形区域算法,数据表需要在经纬度坐标加上双向复合索引 (x, y),这样可以最大优化查询性能。
但是数据库查询性能毕竟有限,如果「附近的人」查询请求非常多,在高并发场合,这可能并不是一个很好的方案。
GeoHash 算法
业界比较通用的地理位置距离排序算法是 GeoHash 算法,Redis 也使用 GeoHash 算法。GeoHash 算法将二维的经纬度数据映射到一维的整数,这样所有的元素都将在挂载到一
条线上,距离靠近的二维坐标映射到一维后的点之间距离也会很接近。当我们想要计算「附近的人时」,首先将目标位置映射到这条线上,然后在这个一维的线上获取附近的点就行了。
那这个映射算法具体是怎样的呢?它将整个地球看成一个二维平面,然后划分成了一系列正方形的方格,就好比围棋棋盘。所有的地图元素坐标都将放置于唯一的方格中。方格越
小,坐标越精确。然后对这些方格进行整数编码,越是靠近的方格编码越是接近。那如何编码呢?一个最简单的方案就是切蛋糕法。设想一个正方形的蛋糕摆在你面前,二刀下去均分
分成四块小正方形,这四个小正方形可以分别标记为 00,01,10,11 四个二进制整数。然后对每一个小正方形继续用二刀法切割一下,这时每个小小正方形就可以使用 4bit 的二进制整数
予以表示。然后继续切下去,正方形就会越来越小,二进制整数也会越来越长,精确度就会越来越高。
上面的例子中使用的是二刀法,真实算法中还会有很多其它刀法,最终编码出来的整数数字也都不一样。
编码之后,每个地图元素的坐标都将变成一个整数,通过这个整数可以还原出元素的坐标,整数越长,还原出来的坐标值的损失程度就越小。对于「附近的人」这个功能而言,损
失的一点精确度可以忽略不计。
GeoHash 算法会继续对这个整数做一次 base32 编码 (0-9,a-z 去掉 a,i,l,o 四个字母) 变成一个字符串。在 Redis 里面,经纬度使用 52 位的整数进行编码,放进了 zset 里面,zset
的 value 是元素的 key,score 是 GeoHash 的 52 位整数值。zset 的 score 虽然是浮点数,但是对于 52 位的整数值,它可以无损存储。
在使用 Redis 进行 Geo 查询时,我们要时刻想到它的内部结构实际上只是一个zset(skiplist)。通过 zset 的 score 排序就可以得到坐标附近的其它元素 (实际情况要复杂一
些,不过这样理解足够了),通过将 score 还原成坐标值就可以得到元素的原始坐标。
Redis 的 Geo 指令基本使用
命令演示如下:
root@d4cad7fb69c2:/data# redis-cli 127.0.0.1:6379> GEOADD geos 1 1 a (integer) 1 127.0.0.1:6379> GEOADD geos 2 2 b (integer) 1 127.0.0.1:6379> GEOPOS geos a 1) 1) "0.99999994039535522" 2) "0.99999945914297683" 127.0.0.1:6379> GEODIST geos a b "157270.0561" 127.0.0.1:6379> GEODIST geos a b m "157270.0561" 127.0.0.1:6379> GEODIST geos a b km "157.2701" 127.0.0.1:6379> geoadd key longitude latitude member [longitude latitude member 127.0.0.1:6379> geoadd key longitude latitude member [longitude latitude member 127.0.0.1:6379> geoadd key longitude latitude member [longitude latitude member 127.0.0.1:6379> geoadd key longitude latitude member [longitude latitude member 127.0.0.1:6379> geoadd key longitude latitude member [longitude latitude member 127.0.0.1:6379> geoadd geos 1 1 1,1 (integer) 1 127.0.0.1:6379> geoadd geos 1 2 1,2 (integer) 1 127.0.0.1:6379> geoadd geos 1 3 1,3 (integer) 1 127.0.0.1:6379> geoadd geos 2 3 2,3 (integer) 1 127.0.0.1:6379> geoadd geos 2 2 2,2 (integer) 1 127.0.0.1:6379> geoadd geos 2 1 2,1 (integer) 1 127.0.0.1:6379> geoadd geos 3 1 3,1 (integer) 1 127.0.0.1:6379> geoadd geos 3 2 3,2 (integer) 1 127.0.0.1:6379> geoadd geos 3 3 3,3 (integer) 1 127.0.0.1:6379> geoadd geos 5 5 5,5 (integer) 1127.0.0.1:6379> GEORADIUSBYMEMBER geos 2,2 180 km 1) "1,1" 2) "a" 3) "2,1" 4) "1,2" 5) "2,2" 6) "b" 7) "3,1" 8) "3,2" 9) "1,3" 10) "2,3" 11) "3,3" 127.0.0.1:6379> GEORADIUSBYMEMBER geos 2,2 120 km 1) "1,2" 2) "2,2" 3) "b" 4) "2,3" 5) "2,1" 6) "3,2" 127.0.0.1:6379> GEORADIUS geos 1.5 1.5 100km (error) ERR wrong number of arguments for 'georadius' command 127.0.0.1:6379> GEORADIUS geos 1.5 1.5 100 km 1) "1,2" 2) "2,2" 3) "b" 4) "1,1" 5) "a" 6) "2,1" 127.0.0.1:6379> geohash geos 3,3 1) "s0d1h60s300" 127.0.0.1:6379> GEORADIUSBYMEMBER geos 2,2 120 km count 3 desc 1) "2,1" 2) "2,3" 3) "1,2" 127.0.0.1:6379>
原文地址:https://www.cnblogs.com/dalianpai/p/12751772.html
- 初学java之StringBuffer类的常用方法
- 初学java之大数处理
- hdu---1024Max Sum Plus Plus(动态规划)
- Go语言异步服务器框架原理和实现
- nyoj------布线问题(kruscal+求最小值)
- nyoj-----127星际之门(一)
- nyoj------20吝啬的国度
- HDUOJ-------2493Timer(数学 2008北京现场赛H题)
- go sync.Mutex 设计思想与演化过程 (一)
- HDUOJ--------A simple stone game(尼姆博弈扩展)(2008北京现场赛A题)
- HDUOJ----2485 Destroying the bus stations(2008北京现场赛A题)
- Go语言实践:从新手入门到上线真实的小型服务所遇到的那些坑
- Node.js真的无所不能?那些不适用的应用领域分析
- hdu-----2491Priest John's Busiest Day(2008 北京现场赛G)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- LinkedBlockingQueue源码学习
- 三歪吐血总结了各个中间件是如何实现持久化的
- ThreadPoolExecutor源码学习
- Docker六脉神剑(四) 使用Docker-Compose进行服务编排搭建lnmp环境
- 干的想喝水,一篇文章带你读懂硬盘工作原理!
- 微信小程序开发实战(11):滚动组件(picker)
- Docker六脉神剑 (五) Docker Swarm集群搭建及基础服务部署
- 思科模拟器GNS3将路由器变成交换机的方法
- docker安装nginx并配置https
- Docker Swarm集群部署lnmp+redis
- Maven快速入门
- TomCat安装及快速部署
- SpringCloud+MyBatis分页处理(前后端分离)
- 手把手教你搭建SpringCloud项目
- SpringCloud的@Value注解及GitLab配置使用