Spring Boot 2 實戰:利用Redis的Geo功能實現查找附近的位置

7b80ba0cbd754730ae84c3079eaf8808

前言

老闆忽然要上線一個需求,獲取當前位置方圓一千米的業務代理點。明天上線!當接到這個需求的時候我差點吐血,這時間也太緊張了。趕忙去查相關的技術選項。通過一番折騰,終於在晚上十點完成了這個需求。如今把大體實現的思路總結一下。git

49ac2a7d18da4b39bb16b3d43b25aced

1. MySQL 不合適

遇到需求,首先要想到現有的東西能不能知足,成本如何。redis

MySQL是我首先可以想到的,畢竟大部分數據要持久化到MySQL。可是使用MySQL須要自行計算Geohash。須要使用大量數學幾何計算,而且須要學習地理相關知識,門檻較高,短期內不可能完成需求,並且長期來看這也不是MySQL擅長的領域,因此沒有考慮它。spring

2. Redis 中的GEO

Redis是咱們最爲熟悉的K-V數據庫,它常被拿來做爲高性能的緩存數據庫來使用,大部分項目都會用到它。從3.2版本開始它開始提供了GEO能力,用來實現諸如附近位置、計算距離等這類依賴於地理位置信息的功能。GEO相關的命令以下:sql

72992c6b39814507afef994cbf851dae

Redis會假設地球爲完美的球形, 因此可能有一些位置計算誤差,聽說<=0.5%,對於有嚴格地理位置要求的需求來講要通過一些場景測試來檢驗是否可以知足需求。數據庫

2.1 寫入地理信息緩存

那麼如何實現目標單位半徑內的全部元素呢?咱們能夠將全部的位置的經緯度經過上表中的GEOADD將這些地理信息轉換爲52位的Geohash寫入Rediside

該命令格式:spring-boot

geoadd key longitude latitude member [longitude latitude member ...]

對應例子:性能

redis> geoadd cities:locs 117.12 39.08 tianjin 114.29 38.02  shijiazhuang 
(integer) 2

意思是將經度爲117.12緯度爲39.08的地點tianjin和經度爲114.29緯度爲38.02的地點shijiazhuang加入key爲cities:locs的 sorted set集合中。能夠添加一到多個位置。而後咱們就能夠藉助於其餘命令來進行地理位置的計算了。學習

有效的精度從-180度到180度。有效的緯度從-85.05112878度到85.05112878度。當座標位置超出上述指定範圍時,該命令將會返回一個錯誤。

2.2 統計單位半徑內的地區

咱們能夠藉助於GEORADIUS來找出以給定經緯度,某一半徑內的全部元素。

該命令格式:

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

這個命令比GEOADD要複雜一些:

  • radius 半徑長度,必選項。後面的m、km、ft、mi、是長度單位選項,四選一。

  • WITHCOORD 將位置元素的經度和維度也一併返回,非必選。

  • WITHDIST 在返回位置元素的同時, 將位置元素與中心點的距離也一併返回。 距離的單位和查詢單位一致,非必選。

  • WITHHASH 返回位置的52位精度的Geohash值,非必選。這個我反正不多用,可能其它一些偏向底層的LBS應用服務須要這個。

  • COUNT 返回符合條件的位置元素的數量,非必選。好比返回前10個,以免出現符合的結果太多而出現性能問題。

  • ASC|DESC 排序方式,非必選。默認狀況下返回未排序,可是大多數咱們須要進行排序。參照中心位置,從近到遠使用ASC ,從遠到近使用DESC

例如,咱們在 cities:locs 中查找以(115.03,38.44)爲中心,方圓200km的城市,結果包含城市名稱、對應的座標和距離中心點的距離(km),並按照從近到遠排列。命令以下:

0dda2af628fb4a3698ed1c284abdcd49

你能夠加上 COUNT 1來查找最近的一個位置。

3. 基於Redis GEO實戰

大體的原理思路說完了,接下來就是實操了。結合Spring Boot應用咱們應該如何作?

3.1 開發環境

須要具備GEO特性的Redis版本,這裏我使用的是Redis 4 。另外咱們客戶端使用 spring-boot-starter-data-redis 。這裏咱們會使用到 RedisTemplate對象。

3.2 批量添加位置信息

第一步,咱們須要將位置數據初始化到Redis中。在Spring Data Redis中一個位置座標(lng,lat) 能夠封裝到org.springframework.data.geo.Point對象中。而後指定一個名稱,就組成了一個位置Geo信息。RedisTemplate提供了批量添加位置信息的方法。咱們能夠將章節2.1中的添加命令轉換爲下面的代碼:

9ba117f1deec470e9a64da3bf30b6310

能夠結合Spring Boot 提供的ApplicationRunner接口來實現初始化。

00b6194210964972855fd24895ce0b82

地理數據持久化到MySQL,而後同步到Redis中。

3.3 查詢附近的特定位置

RedisTemplate 針對GEORADIUS命令也有封裝:

GeoResults<GeoLocation<M>> radius(K key, Circle within, GeoRadiusCommandArgs args)

Circle對象是封裝覆蓋的面積(圖1),須要的要素爲中心點座標Point對象、半徑(radius)、計量單位(metric), 例如:

dc1b8a57565447649007ba241771cab3

GeoRadiusCommandArgs用來封裝GEORADIUS的一些可選命令參數,參見章節2.2中的WITHCOORD、COUNT、ASC等,例如咱們須要在返回結果中包含座標、中心距離、由近到遠排序的前5條數據:

2823ebdcd3294ee68ad5bdbd86ac3275

而後執行 radius方法就會拿到GeoResults<RedisGeoCommands.GeoLocation<String>>封裝的結果,咱們對這個可迭代對象進行解析就能夠拿到咱們想要的數據:

a56f84b05bbb46f99730fb5faa781c66

3.4 刪除元素

有時候咱們可能須要刪除某個位置元素,可是RedisGeo並無刪除成員的命令。不過因爲它的底層是zset,咱們能夠藉助zrem命令進行刪除,對應的Java代碼爲:

redisTemplate.boundZSetOps(GEO_STAGE).remove("tianjin");

4. 總結

今天咱們使用RedisGeo特性實現了常見的附近的地理信息查詢需求,簡單易上手。其實使用另外一個Nosql數據庫MongoDB也能夠實現。在數據量比較小的狀況下Redis已經能很好的知足須要。若是數據量大可以使用MongoDB來實現。

相關文章
相關標籤/搜索