如何“搖一搖”——Redis實現(xiàn)“附近的人”的方法及其他

1、GEOHash——如何“搖一搖”

1-1、為什么需要GEOHash

現(xiàn)在很多APP都有“搖一搖”橄登、“附近的人”、網(wǎng)約車離我有多遠等類似的功能讥此。

那就不可避免需要進行一系列的地理坐標轉換為距離的計算示绊。

獲取地理坐標不難,只需要用戶授權就能輕松獲取到很精確的經緯度暂论;

計算距離也不難:兩點之間坐標差的平方相加,再開方即可(簡書的markdown太差了拌禾,公式也不支持)取胎。

問題在于,用戶數(shù)量劇增以后(百萬、千萬闻蛀、億……)匪傍,每次進行全量距離計算本身就變成了一個巨大的負擔,大量乘方觉痛、開方這樣的浮點運算役衡,不管多么強壯的算力都會被壓垮。

1-2薪棒、什么是GEOHash

GEOHash是解決這一問題的一種算法手蝎。

GEOHash的核心思想是將二維的經緯度轉換成一維的字符串,這樣無論使用何種方式存儲用戶的位置數(shù)據(jù)俐芯,都可以很方便地建立索引棵介,大大簡化計算。

GEOHash有三個主要特點:

  1. 字符串越長吧史,表示的范圍越精確邮辽。編碼長度為8時,精度在19米左右贸营,而當編碼長度為9時吨述,精度在2米左右。

  2. 字符串相似的表示距離相近钞脂,利用字符串的前綴匹配揣云,可以查詢附近的地理位置

  3. GEOHash編碼后的字符串芳肌,可以反向解碼出原來的經緯度灵再。

1-3、GEOHash編碼的簡易原理

GEOHash的編碼過程并不復雜亿笤,首先使用分形的思想對經緯度做處理(二維空間轉換為分形維)翎迁,轉為二進制字符串;然后偶數(shù)位放經度净薛,奇數(shù)位放緯度汪榔,得到一個新的二進制字符串;最后對該二進制字符串做Base32編碼處理即可(或者使用Base64)肃拜。

但是“為什么”要這樣編碼痴腌,就涉及到計算機圖形學的一些思想(GEOHash應用了Peano曲線),本篇側重應用燃领,原理的具體說明此處略去士聪。

1-4、GEOHash的邊緣問題

GEOHash編碼時猛蔽,實際上就是把所有區(qū)域分割成大小相同的矩形塊(分得越細剥悟,字符串就越長灵寺,精度越精確)。

但是不管分割得怎么細区岗,都是一個矩形區(qū)域略板,會出現(xiàn)區(qū)域內靠近邊緣的某個點,與另一片區(qū)域中靠近邊緣的點離得更近慈缔,但是被誤判的情況叮称。

畢竟同一片區(qū)域內的hash碼是相同的,不管離得多遠藐鹤,都會被判定為離得最近瓤檐。

解決方法是在查詢時,除了使用定位點的GeoHash碼進行匹配外教藻,還要使用周圍8個區(qū)域的GeoHash編碼一并匹配距帅。

2、Redis中GEOHash的應用

Redis 從3.2版本開始括堤,追加了GEO數(shù)據(jù)類型用于存儲用戶信息碌秸,并且提供了各種用于地理計算的方法。

(BTW悄窃,對Redis GEO貢獻最大的是其實是國內的開發(fā)者讥电。也是因為我們對這個功能的需求最大)

下面是相關的全部命令列表:

GEOADD:增加地理位置的坐標(支持批量操作)

時間復雜度O(log(N))

GEOADD key longitude latitude member [longitude latitude member ...]

  • key標識一個地理位置的集合。
  • longitude是經度轧抗,latitude是緯度恩敌。
  • member是該地理位置的名稱(自定義)。
應用舉例:

geoadd cities 121.47 31.23 shanghai 

geoadd cities 116.40 39.90 beijing 118.78 32.07 nanjing
GEOPOS:獲取地理位置的坐標(支持批量操作)

時間復雜度O(log(N))

GEOPOS key member [member ...]

GEODIST:獲取兩個地理位置的距離

時間復雜度O(log(N))

GEODIST key member1 member2 [m|km|ft|mi]

單位可以指定為以下四種類型:

  • m:米横媚,距離單位默認為米纠炮,不傳遞該參數(shù)則單位為米
  • km:公里
  • mi:英里
  • ft:英尺
應用舉例:
geodist cities beijing shanghai
GEORADIUS:根據(jù)給定地理位置坐標獲取指定范圍內的地理位置集合

時間復雜度O(N+log(M)),N為指定半徑范圍內的元素個數(shù)灯蝴,M為要返回的個數(shù)

GEORADIUS key longitude latitude radius [m|km|ft|mi] [WITHCOORD] [WITHDIST] [ASC|DESC] [WITHHASH] [COUNT count]

  • longitude是經度恢口,latitude是緯度。
  • radius表示范圍距離穷躁。
  • 距離單位可以為m|km|ft|mi
  • WITHCOORD:傳入WITHCOORD參數(shù)耕肩,則返回結果會帶上匹配位置的經緯度。
  • WITHDIST:傳入WITHDIST參數(shù)问潭,則返回結果會帶上匹配位置與給定地理位置的距離猿诸。
  • ASC|DESC:默認結果是未排序的,傳入ASC為從近到遠排序狡忙,傳入DESC為從遠到近排序梳虽。
  • WITHHASH:傳入WITHHASH參數(shù),則返回結果會帶上匹配位置的hash值灾茁。
  • COUNT count:傳入COUNT參數(shù)窜觉,可以返回指定數(shù)量的結果是复。
GEORADIUSBYMEMBER:根據(jù)給定地理位置獲取指定范圍內的地理位置集合

時間復雜度O(N+log(M)),N為指定半徑范圍內的元素個數(shù)竖螃,M為要返回的個數(shù)

GEORADIUSBYMEMBER key member radius [m|km|ft|mi] [WITHCOORD] [WITHDIST] [ASC|DESC] [WITHHASH] [COUNT count]

GEORADIUS 命令的參數(shù)是坐標,而本命令的參數(shù)是地理位置名稱逗余,其他無區(qū)別特咆。

應用舉例:
georadiusbymember cities shanghai 50 km
GEOHASH:獲取地理位置的GEOHash值(支持批量操作)

時間復雜度O(log(N))

GEOHASH key member [member ...]

3、關于Redis中GEO的一些說明

redis使用的GEOHash編碼長度為26位录粱,可以精確到0.59m的精度腻格。

Redis中的GEO核心主要是兩點:

  1. 使用GEOHash保存地理位置的坐標
  2. 使用有序集合(ZSET)保存地理位置的集合

內部的實際存儲使用的是ZSET,進一步說明如下:

  • GEO命令中的key就是ZSET的名字

  • Redis沒有提供地理位置的刪除命令啥繁〔酥埃可以利用ZSET的ZREM命令進行刪除: zrem cities shanghai

  • 執(zhí)行GEOADD命令時,會先按照標準的GEOHash計算方法計算hash值旗闽,然后將地理位置名稱(member)作為集合的member酬核,將GEOHash值作為score,執(zhí)行ZADD命令插入到ZSET中

  • GEORADIUS和GEORADIUSBYMEMBER适室。內部實現(xiàn)實際上就是先執(zhí)行ZRANGE命令嫡意,然后判斷結果是否符合命令中指定的距離即可。另外就是這兩個命令已經自行解決了GEOHash的“邊緣問題”捣辆,會自動關聯(lián)周圍的八片區(qū)域蔬螟,無需額外處理。

4汽畴、Redis以外的GEOHash實現(xiàn)

4-1旧巾、MySQL

MySQL從 5.7.4 labs 版本開始增加了對于空間索引的支持,通過R樹實現(xiàn)忍些。

如果不具備升級MySQL的條件鲁猩,就需要增加類似 geo_hash 的字段,追加地理位置時手動計算hash碼坐昙,搜索附近的人時手動解決GEOHash的邊緣問題绳匀,從算力和復雜度上來說都不能令人滿意。

4-2炸客、MongoDB + 2d索引

MongoDB主要是通過它的兩種地理空間索引 2dsphere 和 2d來實現(xiàn)地理位置的操作疾棵。

兩種索引的底層依然是基于Geohash,但與通用的Geohash還有一些不同痹仙。

2dsphere 索引僅支持球形表面的幾何形狀查詢是尔。

2d 索引支持平面幾何形狀和一些球形查詢。雖然2d 索引支持某些球形查詢开仰,但 2d 索引對這些球形查詢時拟枚,可能會出錯薪铜。所以球形查詢盡量選擇 2dsphere索引。

盡管兩種索引的方式不同恩溅,但只要坐標跨度不太大隔箍,計算出的距離誤差幾乎可以忽略不計。

對經緯度數(shù)據(jù)創(chuàng)建2d索引(db.coll.createIndex)以后脚乡,只要使用geoNear命令就可以實現(xiàn)附近的人的功能了蜒滩。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市奶稠,隨后出現(xiàn)的幾起案子俯艰,更是在濱河造成了極大的恐慌,老刑警劉巖锌订,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件竹握,死亡現(xiàn)場離奇詭異,居然都是意外死亡辆飘,警方通過查閱死者的電腦和手機啦辐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來劈猪,“玉大人昧甘,你說我怎么就攤上這事≌降茫” “怎么了充边?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長常侦。 經常有香客問我浇冰,道長,這世上最難降的妖魔是什么聋亡? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任肘习,我火速辦了婚禮,結果婚禮上坡倔,老公的妹妹穿的比我還像新娘漂佩。我一直安慰自己,他們只是感情好罪塔,可當我...
    茶點故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布投蝉。 她就那樣靜靜地躺著,像睡著了一般征堪。 火紅的嫁衣襯著肌膚如雪瘩缆。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天佃蚜,我揣著相機與錄音庸娱,去河邊找鬼着绊。 笑死,一個胖子當著我的面吹牛熟尉,可吹牛的內容都是我干的归露。 我是一名探鬼主播,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼斤儿,長吁一口氣:“原來是場噩夢啊……” “哼靶擦!你這毒婦竟也來了?” 一聲冷哼從身側響起雇毫,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎踩蔚,沒想到半個月后棚放,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡馅闽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年飘蚯,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片福也。...
    茶點故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡局骤,死狀恐怖,靈堂內的尸體忽然破棺而出暴凑,到底是詐尸還是另有隱情峦甩,我是刑警寧澤,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布现喳,位于F島的核電站凯傲,受9級特大地震影響,放射性物質發(fā)生泄漏嗦篱。R本人自食惡果不足惜冰单,卻給世界環(huán)境...
    茶點故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望灸促。 院中可真熱鬧诫欠,春花似錦、人聲如沸浴栽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽吃度。三九已至甩挫,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間椿每,已是汗流浹背伊者。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工英遭, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人亦渗。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓挖诸,卻偏偏與公主長得像,于是被迫代替她去往敵國和親法精。 傳聞我的和親對象是個殘疾皇子多律,可洞房花燭夜當晚...
    茶點故事閱讀 45,077評論 2 355