Redis 的 GEO 特性將在 Redis 3.2 版本釋出, 這個功能可以將使用者給定的地理位置資訊儲存起來, 並對這些資訊進行操作。
本文將對 Redis 的 GEO 特性進行介紹, 說明這個特性相關命令的使用者, 並在最後說明如何使用這些命令去實現“查詢附近的人”以及“搖一搖”這兩個功能。
版本要求
因為 Redis 目前的穩定版本為 Redis 3.0 , 而 GEO 特性是 Redis 3.2 版本的特性, 所以如果你想要使用這個特性的話, 那麼就需要到 Redis 的 GitHub 頁面去克隆 unstable 分支 然後編譯 unstable 版本的 Redis 原始碼, 這樣才能用到 Redis GEO 特性。
新增位置和獲取位置
為了進行地理位置相關操作, 我們首先需要將具體的地理位置記錄起來, 這一點可以通過執行 GEOADD 命令來完成, 該命令的基本格式如下:
1 |
GEOADD location-set longitude latitude name [longitude latitude name ...] |
GEOADD 命令每次可以新增一個或多個經緯度地理位置。 其中 location-set 為儲存地理位置的集合, 而 longitude 、 latitude 和 name 則分別為地理位置的經度、緯度、名字。
舉個例子, 以下程式碼展示瞭如何通過 GEOADD 命令, 將清遠、廣州、佛山、東莞、深圳等數個廣東省的市新增到位置集合 Guangdong-cities 裡面:
1 2 3 4 5 |
redis> GEOADD Guangdong-cities 113.2099647 23.593675 Qingyuan 1 -- 成功新增一個位置 redis> GEOADD Guangdong-cities 113.2278442 23.1255978 Guangzhou 113.106308 23.0088312 Foshan 113.7943267 22.9761989 Dongguan 114.0538788 22.5551603 Shenzhen 4 -- 成功新增四個位置 |
在將位置記錄到位置集合之後, 我們可以使用 GEOPOS 命令, 輸入位置的名字並取得位置的具體經緯度:
1 |
GEOPOS location-set name [name ...] |
比如說, 如果我們想要獲取清遠、廣州和佛山的經緯度, 那麼可以執行以下程式碼:
1 2 3 4 5 6 7 |
redis> GEOPOS Guangdong-cities Qingyuan Guangzhou Foshan 1) 1) "113.20996731519699" -- 清遠的經度 2) "23.593675019671288" -- 清遠的緯度 2) 1) "113.22784155607224" -- 廣州的經度 2) "23.125598202060807" -- 廣州的緯度 3) 1) "113.10631066560745" -- 佛山的經度 2) "23.008831202413539" -- 佛山的緯度 |
計算兩個位置之間的距離
在擁有了地理資料之後, 我們就可以基於這些資料進行各種各樣的操作。 針對地理位置資訊的其中一個最簡單的操作, 就是計算兩個位置之間的距離。
在 Redis 裡面, 計算兩個位置之間的距離可以通過 GEODIST 命令來實現:
1 |
GEODIST location-set location-x location-y [unit] |
在呼叫這個命令時, 使用者需要給定想要計算差距的地點 location-x 和 location-y , 以及儲存這兩個地點的地理位置集合。
可選引數 unit 用於指定計算距離時的單位, 它的值可以是以下單位的其中一個:
- m 表示單位為米。
- km 表示單位為千米。
- mi 表示單位為英里。
- ft 表示單位為英尺。
如果使用者沒有指定 unit 引數, 那麼 GEODIST 預設使用米為單位。
作為例子, 以下程式碼展示瞭如何計算清遠和廣州之間的距離:
1 2 |
redis> GEODIST Guangdong-cities Qingyuan Guangzhou "52094.433840356309" -- 兩地相聚 52094 米 |
上面的計算結果使用了米來表示清遠和廣州兩地的距離, 不過在表示比較長的距離時, 我們更習慣採用公里(km)作為單位。 通過顯式地給定 km (千米)作為單位, 我們可以讓 GEODIST 顯示兩個地點之間相距的公里數:
1 2 |
redis> GEODIST Guangdong-cities Qingyuan Guangzhou km "52.094433840356309" -- 兩地相聚 52 公里 |
獲取指定範圍內的元素
除了計算兩地的距離之外, 另一個常見的地理位置操作就是找出特定範圍之內的其他存在的地點。 比如找出地點 x 範圍 100 米之內的所有地點, 找出地點 y 範圍 50 公里之內的所有地點等等。
Redis 提供了 GEORADIUS 和 GEORADIUSBYMEMBER 兩個命令來實現查詢特定範圍內地點的功能, 它們的作用一樣, 只是指定中心點的方式不同: GEORADIUS 使用使用者給定的經緯度作為計算範圍時的中心點, 而 GEORADIUSBYMEMBER 則使用儲存在位置集合裡面的某個地點作為中心點。 以下是這兩個命令的基本格式:
1 2 3 |
GEORADIUS location-set longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [ASC|DESC] [COUNT count] GEORADIUSBYMEMBER location-set location radius m|km|ft|mi [WITHCOORD] [WITHDIST] [ASC|DESC] [COUNT count] |
這兩個命令的各個引數的意義如下:
- m|km|ft|mi 指定的是計算範圍時的單位;
- 如果給定了可選的 WITHCOORD , 那麼命令在返回匹配的位置時會將位置的經緯度一併返回;
- 如果給定了可選的 WITHDIST , 那麼命令在返回匹配的位置時會將位置與中心點之間的距離一併返回;
- 在預設情況下, GEORADIUS 和 GEORADIUSBYMEMBER 的結果是未排序的, ASC 可以讓查詢結果根據距離從近到遠排序, 而 DESC 則可以讓查詢結果根據從遠到近排序;
- COUNT 引數指定要返回的結果數量。
作為示例, 我們可以使用 GEORADIUSBYMEMBER 去找出位於廣州 50 公里、 100 公里以及 150 公里以內的城市:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
redis> GEORADIUSBYMEMBER Guangdong-cities Guangzhou 50 km 1) "Foshan" 2) "Guangzhou" redis> GEORADIUSBYMEMBER Guangdong-cities Guangzhou 100 km 1) "Foshan" 2) "Guangzhou" 3) "Dongguan" 4) "Qingyuan" redis> GEORADIUSBYMEMBER Guangdong-cities Guangzhou 150 km 1) "Foshan" 2) "Guangzhou" 3) "Dongguan" 4) "Qingyuan" 5) "Shenzhen" |
示例:查詢附近的人
好的, 在瞭解了 Redis GEO 特性的基本資訊之後, 接下來我們該思考如何使用這些特性去解決實際的問題了。
為了讓使用者可以方便地找到自己附近的其他使用者, 每個社交網站基本上都內建了“查詢附近的人”這一功能, 通過 Redis , 我們也可以實現同樣的功能, 以下是實現該功能的虛擬碼:
1 2 3 4 5 6 7 8 9 10 11 |
def pin(user, longitude, latitude): """ 記錄使用者的地理位置。 """ GEOADD('user-location-set', longitude, latitude, user) def find_nearby(user, n): """ 返回指定使用者附近 n 公里的所有其他使用者。 """ return GEORADIUSBYMEMBER('user-location-set', user, n, unit='km') |
示例:搖一搖
為了增加樂趣性, 我們可以對“查詢附近的人”這一功能進行修改 —— 程式不是返回指定範圍內的所有人, 而是隨機地返回指定範圍內的某個人, 這也就是非常著名的“搖一搖”功能。 以下是實現該功能的虛擬碼:
1 2 3 4 5 6 7 8 9 |
RANDOM_RADIUS = 1 # 隨機查詢的範圍為 1 公里 def find_random(user): # 獲取範圍內的所有其他使用者 get_all_near_users = find_nearby(user, RANDOM_RADIUS) # 將查詢的結果從 Python 列表轉換為 Python 集合 user_set = set(get_all_near_users) # 然後呼叫 pop() 方法,從集合裡面隨機地移除並返回一個元素 return user_set.pop() |
效率優化
現在的 find_random() 函式可以實現“搖一搖”功能, 但它的效率並不高: 因為程式每次執行這個函式的時候都需要重新執行 find_nearby() 函式以查詢使用者附近的位置, 然而大部分使用者的位置並不經常改變, 並且 GEORADIUSBYMEMBER 命令的執行代價並不低, 因此每次執行 find_random() 都重新執行 find_nearby() 是一種非常低效的做法。
為了優化 find_random() 的效率, 我們可以為 find_random() 的結果建立快取: 把每個執行“搖一搖”的使用者的 find_nearby() 結果儲存到一個 Redis 集合裡面, 並設定一個過期時間(比如 5 分鐘), 然後通過對集合使用 SRANDMEMBER 來隨機地獲取使用者。 這樣使用者在指定過期時間內執行的所有“搖一搖”操作都只會引起一次 GEORADIUSBYMEMBER , 這將極大地提高 find_random() 的執行效率。
另外, 如果使用者密集地聚集在一起, 那麼通過使用 GEORADIUSBYMEMBER 命令提供的 COUNT 引數可以有效地減少指定範圍內的使用者數量, 這可以提高 find_nearby() 的效率, 從而提高 find_random() 的效率。
因為篇幅關係, 優化版的 find_random() 的具體實現這裡就不給出了, 有興趣的讀者可以自己嘗試完成這個函式。
結語
好的, 關於 Redis GEO 特性的簡單介紹就到此結束, 希望這篇文章對於大家瞭解 Redis 的 GEO 特效能夠有所幫助, 我也期待著大家能夠和我分享其他 GEO 特性的用法。