穩住,這波告訴你如何用java+redis 實現搜索附近人功能

近期在安排一款交友軟件的APP,現在有一個功能需要實現搜索附近的人。

後來發現用redis 的GEO功能實現非常簡。

先說一下設計思路,每個用戶在登陸的時候都會添加一下經緯度,這個是APP端獲取的,

然後設置一下這個經緯度到mysql數據庫中,最後把經緯度同步到redis數據庫中。

穩住,這波告訴你如何用java+redis 實現搜索附近人功能

我們先來了解一下 redis GEO功能。

geoadd:增加某個地理位置的座標。

GEOADD key longitude latitude member [longitude latitude member ...]

將給定的空間元素(緯度、經度、名字)添加到指定的鍵裡面。

這些數據會以有序集合的形式被儲存在鍵裡面, 從而使得像 GEORADIUS和 GEORADIUSBYMEMBER 這樣的命令可以在之後通過位置查詢取得這些元素。

GEOADD 命令以標準的 x,y 格式接受參數, 所以用戶必須先輸入經度, 然後再輸入緯度。

GEOADD 能夠記錄的座標是有限的: 非常接近兩極的區域是無法被索引的。

精確的座標限制由 EPSG:900913 / EPSG:3785 / OSGEO:41001 等座標系統定義, 具體如下:

有效的經度介於 -180 度至 180 度之間。

有效的緯度介於 -85.05112878 度至 85.05112878 度之間。

當用戶嘗試輸入一個超出範圍的經度或者緯度時, GEOADD 命令將返回一個錯誤。

可用版本:

>= 3.2.0

時間複雜度:

每添加一個元素的複雜度為 O(log(N)) , 其中 N 為鍵裡面包含的位置元素數量。

返回值:

新添加到鍵裡面的空間元素數量,已經存在,再添加的話會更新當前數據,但是返回值不會包含。

<code>redis> GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania"
(integer) 2

redis> GEODIST Sicily Palermo Catania
"166274.15156960039"

redis> GEORADIUS Sicily 15 37 100 km
1) "Catania"

redis> GEORADIUS Sicily 15 37 200 km
1) "Palermo"
2) "Catania"/<code>

上面的是redis官方文檔給出的操作。

在實際運用中最好把key值作為用戶id,這樣比較容易檢索。

geopos:獲取某個地理位置的座標。

GEOPOS key member [member ...]

從鍵裡面返回所有給定位置元素的位置(經度和緯度)。

因為 GEOPOS 命令接受可變數量的位置元素作為輸入, 所以即使用戶只給定了一個位置元素, 命令也會返回數組回覆。

可用版本:

>= 3.2.0

時間複雜度:

獲取每個位置元素的複雜度為 O(log(N)) , 其中 N 為鍵裡面包含的位置元素數量。

返回值:

GEOPOS 命令返回一個數組, 數組中的每個項都由兩個元素組成: 第一個元素為給定位置元素的經度, 而第二個元素則為給定位置元素的緯度。

當給定的位置元素不存在時, 對應的數組項為空值。

<code>redis> GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 

redis> GEOPOS Sicily Palermo
1) 1) "13.361389338970184"
2) "38.115556395496299"
2) 1) "15.087267458438873"
2) "37.50266842333162"
3) (nil)/<code>

geodist:獲取兩個地理位置的距離。

<code>GEODIST key member1 member2 [unit]/<code>

返回兩個給定位置之間的距離。

如果兩個位置之間的其中一個不存在, 那麼命令返回空值。

指定單位的參數 unit 必須是以下單位的其中一個:

m 表示單位為米。

km 表示單位為千米。

mi 表示單位為英里。

ft 表示單位為英尺。

如果用戶沒有顯式地指定單位參數, 那麼 GEODIST 默認使用米作為單位。

GEODIST 命令在計算距離時會假設地球為完美的球形, 在極限情況下, 這一假設最大會造成 0.5% 的誤差。

可用版本:

>= 3.2.0

複雜度:

O(log(N))

返回值:

計算出的距離會以雙精度浮點數的形式被返回。

如果給定的位置元素不存在, 那麼命令返回空值。

<code>redis> GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 

redis> GEODIST Sicily Palermo Catania
"166274.15156960039"

redis> GEODIST Sicily Palermo Catania km
"166.27415156960038"

redis> GEODIST Sicily Palermo Catania mi
"103.31822459492736"

redis> GEODIST Sicily Foo Bar
(nil)/<code>

georadius:根據給定地理位置座標獲取指定範圍內的地理位置集合。

以給定的經緯度為中心, 返回鍵包含的位置元素當中, 與中心的距離不超過給定最大距離的所有位置元素。

範圍可以使用以下其中一個單位:

m 表示單位為米。

km 表示單位為千米。

mi 表示單位為英里。

ft 表示單位為英尺。

在給定以下可選項時, 命令會返回額外的信息:

WITHDIST : 在返回位置元素的同時, 將位置元素與中心之間的距離也一併返回。 距離的單位和用戶給定的範圍單位保持一致。

WITHCOORD : 將位置元素的經度和維度也一併返回。

WITHHASH : 以 52 位有符號整數的形式, 返回位置元素經過原始 geohash 編碼的有序集合分值。 這個選項主要用於底層應用或者調試, 實際中的作用並不大。

穩住,這波告訴你如何用java+redis 實現搜索附近人功能

命令默認返回未排序的位置元素。 通過以下兩個參數, 用戶可以指定被返回位置元素的排序方式:

ASC : 根據中心的位置, 按照從近到遠的方式返回位置元素。

DESC : 根據中心的位置, 按照從遠到近的方式返回位置元素。

在默認情況下, GEORADIUS 命令會返回所有匹配的位置元素。

雖然用戶可以使用 COUNT <count> 選項去獲取前 N 個匹配元素, 但是因為命令在內部可能會需要對所有被匹配的元素進行處理, 所以在對一個非常大的區域進行搜索時, 即使只使用 COUNT 選項去獲取少量元素, 命令的執行速度也可能會非常慢。/<count>

但是從另一方面來說, 使用 COUNT 選項去減少需要返回的元素數量, 對於減少帶寬來說仍然是非常有用的。

可用版本:

>= 3.2.0

時間複雜度:

O(N+log(M)), 其中 N 為指定半徑範圍內的位置元素數量, 而 M 則是被返回位置元素的數量。

返回值:

GEORADIUS 命令返回一個數組, 具體來說:

在沒有給定任何 WITH 選項的情況下, 命令只會返回一個像 ["New York","Milan","Paris"] 這樣的線性(linear)列表。

在指定了 WITHCOORD 、 WITHDIST 、 WITHHASH 等選項的情況下, 命令返回一個二層嵌套數組, 內層的每個子數組就表示一個元素。

在返回嵌套數組時, 子數組的第一個元素總是位置元素的名字。 至於額外的信息, 則會作為子數組的後續元素,

按照以下順序被返回:

以浮點數格式返回的中心與位置元素之間的距離, 單位與用戶指定範圍時的單位一致。

geohash 整數。

由兩個元素組成的座標,分別為經度和緯度。

舉個例子, GEORADIUS Sicily 15 37 200 km withcoord withdist 這樣的命令返回的每個子數組都是類似以下格式的:

<code>["Palermo","190.4424",["13.361389338970184","38.115556395496299"]]/<code>
<code>redis> GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania"
(integer) 2

redis> GEORADIUS Sicily 15 37 200 km WITHDIST
1) 1) "Palermo"
2) "190.4424"
2) 1) "Catania"
2) "56.4413"

redis> GEORADIUS Sicily 15 37 200 km WITHCOORD
1) 1) "Palermo"
2) 1) "13.361389338970184"
2) "38.115556395496299"
2) 1) "Catania"
2) 1) "15.087267458438873"
2) "37.50266842333162"

redis> GEORADIUS Sicily 15 37 200 km WITHDIST WITHCOORD
1) 1) "Palermo"
2) "190.4424"
3) 1) "13.361389338970184"
2) "38.115556395496299"

2) 1) "Catania"
2) "56.4413"
3) 1) "15.087267458438873"
2) "37.50266842333162"/<code>

這個方法用的最多一般也就是用這個方法,傳入 經緯度,得到周圍的人的id和距離。

georadiusbymember:根據給定地理位置獲取指定範圍內的地理位置集合。

這個命令和 GEORADIUS 命令一樣, 都可以找出位於指定範圍內的元素, 但是 GEORADIUSBYMEMBER 的中心點是由給定的位置元素決定的, 而不是像 GEORADIUS 那樣, 使用輸入的經度和緯度來決定中心點。

穩住,這波告訴你如何用java+redis 實現搜索附近人功能

關於 GEORADIUSBYMEMBER 命令的更多信息, 請參考 GEORADIUS 命令的文檔。

可用版本:

<code>>= 3.2.0/<code>

時間複雜度:

<code>O(log(N)+M), 其中 N 為指定範圍之內的元素數量, 而 M 則是被返回的元素數量。/<code>

返回值:

一個數組, 數組中的每個項表示一個範圍之內的位置元素。

<code>redis> GEOADD Sicily 13.583333 37.316667 "Agrigento"
(integer) 1

redis> GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania"
(integer) 2

redis> GEORADIUSBYMEMBER Sicily Agrigento 100 km
1) "Agrigento"
2) "Palermo"/<code>

geohash:獲取某個地理位置的geohash值。

<code>GEOHASH key member [member ...]/<code>

返回一個或多個位置元素的 Geohash 表示。

可用版本:

>= 3.2.0

時間複雜度:

尋找每個位置元素的複雜度為 O(log(N)) , 其中 N 為給定鍵包含的位置元素數量。

返回值:

一個數組, 數組的每個項都是一個 geohash 。

命令返回的 geohash 的位置與用戶給定的位置元素的位置一一對應。

<code>redis> GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania"
(integer) 2

redis> GEOHASH Sicily Palermo Catania
1) "sqc8b49rny0"
2) "sqdtr74hyu0"/<code>

上面的都是一些理論知識,接下來用java代碼實際操作一下:

<code>public class RedisUtil {
\t private static JedisPool jedisPool = null;
\t // Redis服務器IP
\t private static String ADDR = "192.168.1.254";
\t // Redis的端口號
\t private static int PORT = 6380;
\t // 訪問密碼

\t private static String AUTH = "111111";

\t /**
\t * 初始化Redis連接池
\t */
\t static {
\t try {
\t JedisPoolConfig config = new JedisPoolConfig();
\t // 連接耗盡時是否阻塞, false報異常,ture阻塞直到超時, 默認true
\t config.setBlockWhenExhausted(true);
\t // 設置的逐出策略類名, 默認DefaultEvictionPolicy(當連接超過最大空閒時間,或連接數超過最大空閒連接數)
\t config.setEvictionPolicyClassName("org.apache.commons.pool2.impl.DefaultEvictionPolicy");
\t // 是否啟用pool的jmx管理功能, 默認true
\t config.setJmxEnabled(true);
\t // 最大空閒連接數, 默認8個 控制一個pool最多有多少個狀態為idle(空閒的)的jedis實例。
\t config.setMaxIdle(8);
\t // 最大連接數, 默認8個
\t config.setMaxTotal(200);
\t // 表示當borrow(引入)一個jedis實例時,最大的等待時間,如果超過等待時間,則直接拋出JedisConnectionException;
\t config.setMaxWaitMillis(1000 * 100);
\t // 在borrow一個jedis實例時,是否提前進行validate操作;如果為true,則得到的jedis實例均是可用的;
\t config.setTestOnBorrow(true);
\t jedisPool = new JedisPool(config, ADDR, PORT, 3000, AUTH);
\t } catch (Exception e) {
\t e.printStackTrace();
\t }
\t }
\t /**
\t * 獲取Jedis實例
\t *
\t * @return
\t */

\t public synchronized static Jedis getJedis() {
\t try {
\t if (jedisPool != null) {
\t Jedis resource = jedisPool.getResource();
\t return resource;
\t } else {
\t return null;
\t }
\t } catch (Exception e) {
\t e.printStackTrace();
\t return null;
\t }
\t }

\t /**
\t * 釋放jedis資源
\t *
\t * @param jedis
\t */
\t public static void close(final Jedis jedis) {
\t if (jedis != null) {
\t jedis.close();
\t }
\t }

\t public static void main(String[] args) {
\t Jedis jedis = RedisUtil.getJedis();

\t //添加經緯度
\t Coordinate coordinate=new Coordinate();
\t coordinate.setLatitude(31.244803); //維度
\t coordinate.setLongitude(121.483671); //經度
\t coordinate.setKey("1"); //可以作為用戶表的id
\t
\t
\t //添加經緯度
\t Coordinate coordinate1=new Coordinate();
\t coordinate1.setLatitude(31.245321); //維度
\t coordinate1.setLongitude(121.485015); //經度
\t coordinate1.setKey("2"); //可以作為用戶表的id
\t
\t //添加經緯度
\t Coordinate coordinate2=new Coordinate();
\t coordinate2.setLatitude(31.245456); //維度
\t coordinate2.setLongitude(121.485285); //經度
\t coordinate2.setKey("3"); //可以作為用戶表的id

\t
\t addReo(coordinate);
\t addReo(coordinate1);
\t addReo(coordinate2);
\t RedisUtil.close(jedis);
\t }

\t /**
\t * 添加座標
\t * key 經度 維度 距離
\t * return m 表示單位為米*/
\t public static Long addReo(Coordinate coordinate) {
\t Jedis jedis = null;
\t try {
\t jedis = jedisPool.getResource();
\t //第一個參數可以理解為表名
\t return jedis.geoadd("test",coordinate.getLongitude(),coordinate.getLatitude(),coordinate.getKey());
\t } catch (Exception e) {
\t System.out.println(e.getMessage());
\t } finally {
\t if (null != jedis)
\t jedis.close();
\t }
\t return null;
\t }
\t /**
\t * 查詢附近人
\t * key 經度 維度 距離
\t * return GeoRadiusResponse*/
\t public static List<georadiusresponse> geoQuery(Coordinate coordinate) {
\t Jedis jedis = null;
\t try {
\t jedis = jedisPool.getResource();
\t //200F GeoUnit.KM表示km
\t return jedis.georadius("test",coordinate.getLongitude(),coordinate.getLatitude(),200F,GeoUnit.KM, GeoRadiusParam.geoRadiusParam().withDist());
\t } catch (Exception e) {
\t System.out.println(e.getMessage());
\t } finally {
\t if (null != jedis)
\t jedis.close();
\t }
\t return null;
\t }

}/<georadiusresponse>/<code>

執行完main方法之後我們可以看到添加了三條數據;

穩住,這波告訴你如何用java+redis 實現搜索附近人功能

最後再寫一個查詢方法測試下:

<code> public static void main(String[] args) {
\t Jedis jedis = RedisUtil.getJedis();

\t //添加經緯度
\t Coordinate coordinate=new Coordinate();
\t coordinate.setLatitude(31.244803); //維度
\t coordinate.setLongitude(121.483671); //經度

\t coordinate.setKey("1"); //用戶表的id 以當前用戶作為查詢條件,查詢他周圍的人數
\t List<georadiusresponse> list=geoQuery(coordinate);
\t for(GeoRadiusResponse geo:list){
\t \tSystem.out.println(geo.getMemberByString()); //主鍵 有主鍵了個人信息就很簡單了
\t \tSystem.out.println(geo.getDistance()); //距離多少米
\t }
\t
\t RedisUtil.close(jedis);
\t }/<georadiusresponse>/<code>

輸出語句:

1 2.0E-4 2 0.1404 3 0.1698

這裡主要要導入兩個包:

穩住,這波告訴你如何用java+redis 實現搜索附近人功能

以上就是穩住,這波告訴你如何用java+redis 實現搜索附近人功能,下面展示了部分資料,希望也能幫助到大家,對編程感興趣的朋友,如果能幫到你請點贊、點贊、點贊:

整理的 pdf 文檔:

穩住,這波告訴你如何用java+redis 實現搜索附近人功能

穩住,這波告訴你如何用java+redis 實現搜索附近人功能

穩住,這波告訴你如何用java+redis 實現搜索附近人功能

穩住,這波告訴你如何用java+redis 實現搜索附近人功能

源碼分析專題部分課程:

穩住,這波告訴你如何用java+redis 實現搜索附近人功能

穩住,這波告訴你如何用java+redis 實現搜索附近人功能

獲取方式

點贊,收藏並轉發文章後點擊小編頭像或暱稱,關注後私信回覆:【11】 即可

舉手之勞,非常感謝!!!


分享到:


相關文章: