Redis原理及实践之GeoHash

2. 地理位置距离排序算法(GeoHash)

  • GeoHash算法思想
    GeoHash算法将二维的经纬度数据映射到一维的整数,这样所有的元素都将挂载到一条线上,距离靠近的二维坐标映射到一维后的点之间距离会很接近。当我们想要计算附近的人时,首先将目标位置映射到这条线上,然后在这条一维的线上获取附近的点就ok了。
    那这个映射算法具体是怎样的呢?它将整个地球看成一个二维平面,然后划分成了一系列正方形的方格,就好比围棋棋盘。所有的地图元素坐标都将放置于唯一的方格中。方格越小,坐标越精确。然后对这些方格进行整数编码,越是靠近的方格编码越是接近。那如何编码呢?一个最简单的方案就是切蛋糕法。设想一个正方形的蛋糕摆在你面前,二刀下去均分分成四块小正方形,这四个小正方形可以分别标记为 00,01,10,11 四个二进制整数。然后对每一个小正方形继续用二刀法切割一下,这时每个小小正方形就可以使用 4bit 的二进制整数予以表示。然后继续切下去,正方形就会越来越小,二进制整数也会越来越长,精确度就会越来越高。

    image

    二分切割法

    编码之后,每个地图元素的坐标都将变成一个整数,通过这个整数可以还原出元素的坐标,整数越长,还原出来的坐标值的损失程度就越小。对于「附近的人」这个功能而言,损失的一点精确度可以忽略不计。

GeoHash算法会对上述编码的整数继续做一次base32编码(0 ~ 9,a ~ z)变成一个字符串。Redis中经纬度使用52位的整数进行编码,放进zset中,zset的value元素是key,score是GeoHash的52位整数值。在使用Redis进行Geo查询时,其内部对应的操作其实只是zset(skiplist)的操作。通过zset的score进行排序就可以得到坐标附近的其它元素,通过将score还原成坐标值就可以得到元素的原始坐标

总之,Redis中处理这些地理位置坐标点的思想是: 二维平面坐标点 --> 一维整数编码值 --> zset(score为编码值) --> zrangebyrank(获取score相近的元素)、zrangebyscore --> 通过score(整数编码值)反解坐标点 --> 附近点的地理位置坐标。

  • 推荐一篇牛逼的讲解高效多维空间点索引算法文章:https://halfrost.com/go_spatial_search/

  • Redis的Geo指令基本使用
    Redis Geo指令只有6个,使用时务必再次想起,它只是一个普通的zset结构。

    • geoadd : (纬度、经度、名称)三元组
    127.0.0.1:6379> geoadd company 116.48105 39.996794 juejin(integer) 1127.0.0.1:6379> geoadd company 116.514203 39.905409 ireader(integer) 1127.0.0.1:6379> geoadd company 116.489033 40.007669 meituan(integer) 1127.0.0.1:6379> geoadd company 116.562108 39.787602 jd 116.334255 40.027400           xiaomi(integer) 2...
    
    • geodist : 计算两个元素之间的距离
    // 例如,计算集合company中juejin和ireader两个元素之间的距离,单位为km// 其中,距离单位可以是m、km、ml、ft,分别代表米、千米、英里和尺127.0.0.1:6379> geodist company juejin ireader km"10.5501"127.0.0.1:6379> geodist company juejin meituan km"1.3878" 127.0.0.1:6379> geodist company juejin jd km "24.2739" 127.0.0.1:6379> geodist company juejin xiaomi km "12.9606" 127.0.0.1:6379> geodist company juejin juejin km  "0.0000"...
    
    • geopos : 获取集合中任意元素的经纬度坐标,可以一次获取多个
    // 获取company集合中juejin元素的经纬度坐标127.0.0.1:6379> geopos company juejin1) 1) "116.48104995489120483"   2) "39.99679348858259686"127.0.0.1:6379> geopos company ireader1) 1) "116.5142020583152771"   2) "39.90540918662494363"127.0.0.1:6379> geopos company juejin ireader  // 同时获取多个元素的经纬度坐标1) 1) "116.48104995489120483"   2) "39.99679348858259686"2) 1) "116.5142020583152771"   2) "39.90540918662494363"...
    

    注意:GeoHash对二维经纬度坐标进行一维映射是有损的,通过映射再还原回的经纬度坐标和原始输入的经纬度坐标存在一定的误差。

    • geohash : 获取元素经纬度坐标经过geohash算法生成的base32编码值
    127.0.0.1:6379> geohash company ireader1) "wx4g52e1ce0"127.0.0.1:6379> geohash company juejin1) "wx4gd94yjn0"
    
    • georadiusbymember : 查询指定元素附近的其它元素
    # 根据元素查找附近的元素# 范围 20 公里以内最多 3 个元素按距离正排,它不会排除自身127.0.0.1:6379> georadiusbymember company ireader 20 km count 3 asc1) "ireader"2) "juejin"3) "meituan"# 范围 20 公里以内最多 3 个元素按距离倒排127.0.0.1:6379> georadiusbymember company ireader 20 km count 3 desc1) "jd"2) "meituan"3) "juejin"# 三个可选参数 withcoord withdist withhash 用来携带附加参数# withdist 很有用,它可以用来显示距离127.0.0.1:6379> georadiusbymember company ireader 20 km withcoord withdist   withhash count 3 asc1) 1) "ireader"   2) "0.0000"   3) (integer) 4069886008361398   4) 1) "116.5142020583152771"      2) "39.90540918662494363"2) 1) "juejin"   2) "10.5501"   3) (integer) 4069887154388167   4) 1) "116.48104995489120483"      2) "39.99679348858259686"3) 1) "meituan"   2) "11.5748"   3) (integer) 4069887179083478   4) 1) "116.48903220891952515"      2) "40.00766997707732031"...
    
    • georadius
    # 根据坐标点查找附近位置的元素127.0.0.1:6379> georadius company 116.514202 39.905409 20 km withdist count 3 asc1) 1) "ireader"   2) "0.0000"2) 1) "juejin"   2) "10.5501"3) 1) "meituan"   2) "11.5748"...
    

Redis Geo使用时的注意事项
在一个地图应用中,车的数据、餐馆的数据、人的数据可能会有百万千万条,如果使用 Redis 的 Geo 数据结构,它们将全部放在一个 zset 集合中。在 Redis 的集群环境中,集合可能会从一个节点迁移到另一个节点,如果单个 key 的数据过大,会对集群的迁移工作造成较大的影响,在集群环境中单个 key 对应的数据量不宜超过 1M,否则会导致集群迁移出现卡顿现象,影响线上服务的正常运行。
所以,这里建议 Geo 的数据使用单独的 Redis 实例部署,不使用集群环境。
如果数据量过亿甚至更大,就需要对 Geo 数据进行拆分,按国家拆分、按省拆分,按市拆分,在人口特大城市甚至可以按区拆分。这样就可以显著降低单个 zset 集合的大小。(注意:zset集合大小,进行合适地切分)

一张图搞懂GeoHash与Z阶曲线的关系:

image

image.png

GeoHash算法的优缺点:

  • 优点
    • GeoHash利用Z阶曲线进行编码,Z阶曲线可以将二维或更多维的所有点都转换成一阶曲线。
    • 地理位置坐标点通过编码转化成一维值,利用BST、B树、SkipList等,均可进行范围搜索。因此利用GeoHash算法查找邻近点比较快。
  • 缺点
    • 在拥有局部保序性的同时,具有突变性。导致一些邻近点真实并不是距离较近的点。

https://blog.csdn.net/liyantianmin/article/details/82668701

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,012评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,589评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,819评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,652评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,954评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,381评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,687评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,404评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,082评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,355评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,880评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,249评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,864评论 3 232
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,007评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,760评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,394评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,281评论 2 259

推荐阅读更多精彩内容