Redis系列9:Geo 類型賦能億級地圖位置計算
Redis系列1:深刻理解高性能Redis的本質
Redis系列2:數據持久化提高可用性
Redis系列3:高可用之主從架構
Redis系列4:高可用之Sentinel(哨兵模式)
Redis系列5:深入分析Cluster 集群模式
追求性能極致:Redis6.0的多執行緒模型
追求性能極致:客戶端快取帶來的革命
Redis系列8:Bitmap實現億萬級數據計算
1 前言
我們在第一篇 深刻理解高性能Redis的本質 的時候就介紹過Redis的幾種基本數據結構,它是基於不同業務場景而設計的:
- 動態字元串(REDIS_STRING):整數(REDIS_ENCODING_INT)、字元串(REDIS_ENCODING_RAW)
- 雙端列表(REDIS_ENCODING_LINKEDLIST)
- 壓縮列表(REDIS_ENCODING_ZIPLIST)
- 跳躍表(REDIS_ENCODING_SKIPLIST)
- 哈希表(REDIS_HASH)
- 整數集合(REDIS_ENCODING_INTSET)
除了這些常見數據類型,還有一些不常用的數據類型,如 BitMap、Geo、HyperLogLog 等等,他們在各自的方向為不同的類型的數據統計給出解決方案。
上一篇我們說了點陣圖(BitMap)計算,可以應用於任何大數據場景下的二值計算,比如 是否登錄、是否在線、是否簽到、用戶性別狀態、IP黑名單、是否VIP用戶統計 等等場景。
這一篇我們來介紹下Geo,分析它在 坐標記錄、位置計算、距離計算上的能力,以及在地圖業務中的應用場景。
2 了解一下 Location Based Services
Location Based Services,記作 LBS,基於用戶的地理位置數據定位展開的服務,廣泛應用與地圖類(百度地圖、高德地圖)、電商團購類(美團、餓了么)軟體。它常見的使用場景有:
- 計算用戶的精準的地理坐標位置
- 統計用戶定點坐標一定範圍內的其他地理位置,並計算出距離
- 對一定範圍內的地理位置進行排序,並由近到遠篩選
有沒有感覺很熟悉,當然了,在我們的身邊到處都是這樣的應用場景。
3 Geo所支援的能力
Redis 的 GEO 特性在 Redis 3.2 版本就有了, 這個功能主要是用於存儲用戶地理位置資訊,並對這些資訊進行操作。
GEO 的數據結構總共有六個命令,我們一個個來介紹 :
- geoadd
- geopos
- geodist
- georadius
- georadiusbymember
- gethash
3.1 GEOADD 添加經緯資訊
Redis 提供了 GEOADD key longitude latitude member 命令,將一組經緯度資訊和對應的所屬對象的資訊 記錄到 GEO 類型的集合中,指令如下
GEOADD key longitude latitude member [longitude latitude member ...]
longitude latitude member
分別指給定的空間元素:維度、精度、名稱
,這些數據會以有序集合的形式存儲在給定的鍵裡面。
我們舉個例子,如果你在地圖上查找美食,那應該會出現一堆餐飲店鋪和坐標位置,那他們的空間資訊存儲可能是這樣的。
redis> GEOADD food:location 115.775632 39.483256 "東北餃子館" 114.081569 39.692756 "蘭州拉麵"
(integer) 2
3.2 GEOPOS 獲取給定位置的經緯
提供對應的鍵和位置名稱,返回相應的經緯度資訊。
GEOPOS key member [member ...]
按照上面的例子,我要獲取對應的美食店位置坐標資訊如下:
redis> GEOPOS food:location 東北餃子館 蘭州拉麵 NonExisting
"115.775632 39.483256"
"114.081569 39.692756"
3.3 GEODIST 返回給定兩個位置距離
很多時候,我們要導航去一個地方就會用到這類需求。打開百度或者高德地圖,起始位置就定位用戶當前位置,目的地定位為搜索到的地址,比如上面的 東北餃子館。
這時候地圖軟體需要計算出兩個坐標之間的舉例,來推薦用戶是飛機高鐵、開車、還是步行。那麼獲取給定兩個位置之間的距離就變得非常重要,GEODIST就是用來解決這個問題的。
GEODIST key member1 member2 [unit]
上述指令可以返回兩個給定位置之間的距離,unit是距離單位,可選項,默認為m,枚舉如下:
- m:表示單位為米
- km:表示單位為千米
- mi:表示單位為英里
- ft:表示單位為英尺
需要注意的是如果兩個位置之間的其中一個不存在, 那麼會返回空值。下面程式碼計算出 東北餃子館 和 蘭州拉麵 店鋪之間的距離,大概是6.1公里。
redis> GEODIST food:location 東北餃子館 蘭州拉麵
"6184.15156"
3.4 GEORADIUS 獲取給定經緯度的固定距離內的位置資訊
很多種應用場景是我登錄了外賣APP,也確定了我自己所在的位置(即已確知經緯),需要獲取一定距離範圍內(比如10公里),所有的餐飲店。
這時候就使用到了 GEO 提供的 GEORADIUS指令了:根據輸入的經緯度,查找以這個經緯度為中心的一定距離內的其他位置資訊。
GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [ASC|DESC] [COUNT count]
key longitude latitude
: 是前置條件,給定的經緯度資訊,以及我要搜索的keyradius
:距離半徑,指的搜索的範圍m|km|ft|mi
: 為給定的距離單位,有 米、千米、英尺、英里 4種[WITHCOORD] [WITHDIST] [WITHHASH]
: 為返回的資訊類型- WITHDIST: 在返回位置元素的同時, 將位置元素與中心之間的距離也一併返回。距離的單位和用戶給定的範圍單位保持一致。
- WITHCOORD: 將位置元素的經度和維度也一併返回。
- WITHHASH: 以 52 位有符號整數的形式, 返回位置元素經過原始 geohash 編碼的有序集合分值。這個選項主要用於底層應用或者調試, 實際中的作用並不大。
ASC|DESC
:可選參數,按照距離升序或者降序排列,即 由近到遠(asc) 還是 由遠到近(desc)COUNT count
:取數數量,避免獲取到太多的資訊,返回太多資訊
所以如果需要獲取 距離本人位置10公里半徑內由近到遠的美食店排序,按km單位計算,返回值帶上距離資訊,並只取前100個的資訊,程式碼如下:
redis> GEORADIUS food:location 115.791331 39.5120003 10 km WITHDIST ASC COUNT 100
"東北餃子館" 3.3421
"蘭州拉麵" 9.4571
下圖的綠色區域在固定半徑(紅圈)中搜索到了特定的幾個目標位置:1、2、5、9、10。
3.5 GEORADIUSBYMEMBER 按照位置名稱獲取
與 GEORADIUS
的區別是 GEORADIUSBYMEMBER
的中心點是由給定的位置元素決定的, 而不是像 GEORADIUS 那樣,通過傳入經度和緯度來決定中心點。
所以如下,已知蘭州拉麵和東北餃子館的距離是6.1公里,根據蘭州拉麵獲取10公里範圍內的距離的美食店,可以獲取到東北餃子館和自己的位置:
redis> GEORADIUSBYMEMBER food:location "蘭州拉麵" 100 km WITHDIST
"東北餃子館" 6.09127
"蘭州拉麵" 0
3.6 ZREM 刪除關閉的店鋪
redis> ZREM food:location "蘭州拉麵"
(integer) 1
4 總結
- GEO 使用了 Sorted Set 集合類型,並通過 GeoHash 編碼方法實現了經緯度到 Sorted Set 中元素權重分數的轉換,涵蓋兩個關鍵能力就是就是對二維地圖做區間劃分,以及對區間進行編碼。
- 具體可應用的場景如下:
- 計算用戶的精準的地理坐標位置
- 統計用戶定點坐標一定範圍內的其他地理位置,並計算出距離
- 對一定範圍內的地理位置進行排序,並由近到遠篩選
- 真實的地圖數據存儲,場景比這複雜的多,數據量會達到驚人的巨量,所以還是會使用分治原理進行拆分,細化到一個市甚至一個區,來提高存儲和檢索的效率。