如果現在要開發一個功能:
要為一款交友App實現查找附近的人,並按距離進行排序。
讓你來開發這個功能,你會如何實現?
MySQL 不合適
你可能想到,把用戶用戶的經緯度坐標使用MySQL等關系數據庫(用戶id,經度x,緯度y)存儲,但是該如何計算距離和排序呢?
不可能通過遍歷來計算所有的用戶和目標用戶的距離,然后再進行排序,因為這個計算量太大了,性能指標肯定無法滿足。
GeoHash的編碼方法
為了能高效地對經緯度進行比較,Redis 采用了業界廣泛使用的 GeoHash 編碼方法,這個方法的基本原理是“二分區間,區間編碼”。
關於 GeoHash 參考 https://www.cnblogs.com/LBSer/p/3310455.html。
簡單來說,GeoHash 能夠將二維的經緯度轉換為字符串,然后位置就能夠直接進行比較和范圍查詢了。
Redis 中 Geo 的使用
命令 | 說明 | 可用版本 | 時間復雜度 |
GEOADD | 添加位置的經緯度 | >= 3.2.0 | O(logN) |
GEOPOS | 返回位置的經緯度 | >= 3.2.0 | O(logN) |
GEODIST | 返回兩個位置的距離 | >= 3.2.0 | O(logN) |
GEORADIUS | 返回與指定位置距離距離不大於指定值的位置的經緯度 | >= 3.2.0 | O(N+logM) |
GEORADIUSBYMEMBER | 這個命令和 GEORADIUS 命令一樣 |
>= 3.2.0 | O(logN+M) |
GEOHASH | 返回位置的 GeoHash 值 | >= 3.2.0 | O(logN) |
示例
假設用戶ID是33,經緯度位置是(116.054579, 39.030452),我們可以用一個 GEO 集合保存所有用戶的經緯度,集合 key 是 users:locations。執行下面的這個命令,就可以把ID號為33的用戶的當前經緯度位置存入GEO集合中:
GEOADD users:locations 116.034579 39.030452 33
當用戶想要尋找自己附近的人時,就可以使用 GEORADIUS 命令。
例如,執行下面的命令,Redis 會根據輸入的用戶的經緯度信息(116.054579, 39.030452),查找以這個經緯度為中心的5公里內的用戶信息。
GEORADIUS users:locations 116.054579 39.030452 5 km ASC COUNT 10
總結
在一個地圖應用中,車的數據、餐館的數據、人的數據可能會有百萬千萬條,如果使用 Redis 的 Geo 數據結構,它們將全部放在一個 Sorted Set 集合中。在 Redis 的集群環境中,集合可能會從一個節點遷移到另一個節點,如果單個 key 的數據過大,會對集群的遷移工作造成較大的影響,在集群環境中單個 key 對應的數據量不宜超過 1M,否則會導致集群遷移出現卡頓現象,影響線上服務的正常運行。
所以,這里建議 Geo 的數據使用單獨的 Redis 實例部署,不使用集群環境。
如果數據量過億甚至更大,就需要對 Geo 數據進行拆分,按國家拆分、按省拆分,按市拆分,在人口特大城市甚至可以按區拆分。這樣就可以顯著降低單個 Sorted Set 集合的大小。
參考資料