天天看點

Redis GEO 特性簡介

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 特性的用法。

繼續閱讀