SpringBoot使用GeoHash分頁查詢附近的人:RedisTemplate+GeoHash+Lua
架構設計
開發環境
SpringBoot Redis(version>=3.2)
Redis原生命令實現
一、存入用戶的經緯度
geoadd 用于存儲指定的地理空間位置,可以將一個或多個經度(longitude)、緯度(latitude)、位置名稱(member)添加到指定的 key 中 命令格式:
GEOADD
[ key
] [ longitude
] [ latitude
] [ member
]
模擬五個用戶存入經緯度,redis客戶端執行如下命令:
GEOADD user
116.48105 39.996794 zhangsan
GEOADD user
116.514203 39.905409 lisi
GEOADD user
116.489033 40.007669 wangwu
GEOADD user
116.562108 39.787602 sunliu
GEOADD user
116.334255 40.027400 zhaoqi
通過redis客戶端查看效果:
二、查找距當前用戶由近到遠附近100km用戶
georadiusbymember可以找出位于指定范圍內的元素,georadiusbymember 的中心點是由給定的位置元素決定的 命令格式:
GEORADIUSBYMEMBER key member radius m
| km
| ft
| mi
[ WITHCOORD
] [ WITHDIST
] [ WITHHASH
] [ COUNT count
] [ ASC
| DESC
] [ STORE key
] [ STOREDIST key
]
模擬查找100km里距離lisi由近到遠五個人
georadiusbymember user lisi
100 km asc count
5
命令執行效果如下:
三、實現分頁查詢
每次分頁查詢的請求都計算一次然后拿到程序中在取相應的分頁數據,優缺點:
優點:實現簡單,無需額外的存儲空間 缺點:當用戶量大時,很顯然不僅效率低,而且容易把程序的內存搞溢出
經過查找發現redis的github官網給出了分頁Issues(參考:Will the GEORADIUS support pagination?),解決方案如下:
利用GEORADIUSBYMEMBER 命令中的 STOREDIST 將排好序的數據存入一個Zset集合中,以后分頁查直接從Zset 命令如下:
georadiusbymember user lisi
100 km asc count
5 storedist lisilimitkey
zrem lisilimitkey lisi
zrange lisilimitkey
0 2 withscores
四、代碼實現
完整代碼
https
: / / github
. com
/ shiziyang666
/ public / tree
/ master
/ demo
主要代碼展示
package com. demo ; import com. demo. service. PeopleNearbyService ;
import org. springframework. beans. factory. annotation. Autowired ;
import org. springframework. boot. CommandLineRunner ;
import org. springframework. boot. SpringApplication ;
import org. springframework. boot. autoconfigure. SpringBootApplication ; @SpringBootApplication
public class DemoApplication implements CommandLineRunner { public static void main ( String [ ] args
) { SpringApplication . run ( DemoApplication . class , args
) ; } @Autowired private PeopleNearbyService peopleNearbyService
; @Override public void run ( String . . . args
) throws Exception { peopleNearbyService
. postUserAddress ( "city" , 116.405285 , 39.904989 , "北京" ) ; peopleNearbyService
. postUserAddress ( "city" , 121.47 , 31.23 , "上海" ) ; peopleNearbyService
. postUserAddress ( "city" , 113.27 , 23.13 , "廣州" ) ; peopleNearbyService
. postUserAddress ( "city" , 43.86 , 10.40 , "深圳" ) ; peopleNearbyService
. listNearbyUser ( "city" , "深圳" , 8000 , 4 ) ; peopleNearbyService
. listNearbyUserLimit ( 1 , 2 , "city" , "深圳" , "8000" , "km" , "asc" , "shenzhennewkey" ) ; } }
package com. demo. service ;
public interface PeopleNearbyService { void postUserAddress ( String key
, double precision
, double dimension
, String name
) ; void listNearbyUser ( String key
, String name
, Integer distance
, Integer count
) ; void listNearbyUserLimit ( Integer pageIndex
, Integer pageSize
, String key
, String name
, String distance
, String distanceUnit
, String sort
, String newKey
) ;
}
package com. demo. service. impl ; import com. demo. entity. MemberGpsEntity ;
import com. demo. service. PeopleNearbyService ;
import com. demo. util. GeoHashUtil ;
import org. springframework. beans. factory. annotation. Autowired ;
import org. springframework. data. geo. GeoResults ;
import org. springframework. data. geo. Point ;
import org. springframework. data. redis. connection. RedisGeoCommands ;
import org. springframework. data. redis. core. RedisTemplate ;
import org. springframework. data. redis. core. ZSetOperations ;
import org. springframework. data. redis. core. script. DefaultRedisScript ;
import org. springframework. stereotype. Service ; import java. util. Arrays ;
import java. util. List ;
import java. util. Set ;
import java. util. concurrent. TimeUnit ;
import java. util. stream. Collectors ;
@Service
public class PeopleNearbyServiceImpl implements PeopleNearbyService { @Autowired private RedisTemplate redisTemplate
; @Autowired private GeoHashUtil geoHashUtil
; @Override public void postUserAddress ( String key
, double precision
, double dimension
, String name
) { redisTemplate
. opsForGeo ( ) . add ( key
, new Point ( precision
, dimension
) , name
) ; geoHashUtil
. redisGeoAdd ( key
, precision
, dimension
, name
) ; } @Override public void listNearbyUser ( String key
, String name
, Integer distance
, Integer count
) { GeoResults < RedisGeoCommands. GeoLocation < String > > geoResults
= geoHashUtil
. geoNearByPlace ( key
, name
, distance
, count
) ; System . out
. println ( geoResults
) ; } @Override public void listNearbyUserLimit ( Integer pageIndex
, Integer pageSize
, String key
, String name
, String distance
, String distanceUnit
, String sort
, String newKey
) { Object execute
= execute ( "return redis.call('georadiusbymember',KEYS[1],KEYS[2],KEYS[3],KEYS[4],KEYS[5],'storedist',KEYS[6])" , key
, name
, distance
, distanceUnit
, sort
, newKey
) ; redisTemplate
. expire ( newKey
, 6 , TimeUnit . HOURS
) ; redisTemplate
. opsForGeo ( ) . remove ( newKey
, name
) ; Integer startPage
= ( pageIndex
- 1 ) * pageSize
; Integer endPage
= pageIndex
* pageSize
- 1 ; Set < ZSetOperations. TypedTuple < Object > > aaa
= redisTemplate
. opsForZSet ( ) . rangeWithScores ( newKey
, startPage
, endPage
) ; List < MemberGpsEntity > memberGpsEntityList
= aaa
. stream ( ) . map ( aa
-> { MemberGpsEntity memberGpsEntity
= new MemberGpsEntity ( ) ; memberGpsEntity
. setName ( aa
. getValue ( ) . toString ( ) ) ; memberGpsEntity
. setDistance ( aa
. getScore ( ) ) ; return memberGpsEntity
; } ) . collect ( Collectors . toList ( ) ) ; System . out
. println ( memberGpsEntityList
) ; } private Object execute ( String text
, String . . . str
) { List < String > params
= Arrays . asList ( str
) ; DefaultRedisScript < Long > defaultRedisScript
= new DefaultRedisScript < > ( ) ; defaultRedisScript
. setResultType ( Long . class ) ; defaultRedisScript
. setScriptText ( text
) ; Object execute
= redisTemplate
. execute ( defaultRedisScript
, params
) ; return execute
; }
}
package com. demo. util ; import org. springframework. beans. factory. annotation. Autowired ;
import org. springframework. data. geo. * ;
import org. springframework. data. redis. connection. RedisGeoCommands ;
import org. springframework. data. redis. core. RedisTemplate ;
import org. springframework. stereotype. Component ;
import java. util. List ; @Component
public class GeoHashUtil { @Autowired private RedisTemplate redisTemplate
; public Long redisGeoAdd ( String key
, double precision
, double dimension
, String name
) {
Long addedNum
= redisTemplate
. opsForGeo ( ) . add ( key
, new Point ( precision
, dimension
) , name
) ; return addedNum
; } public List < Point > redisGeoGet ( String key
, List < String > nameList
) { List < Point > points
= redisTemplate
. opsForGeo ( ) . position ( key
, nameList
) ; return points
; } public Distance geoDist ( String key
, String name1
, String name2
) { Distance distance
= redisTemplate
. opsForGeo ( ) . distance ( key
, name1
, name2
, RedisGeoCommands. DistanceUnit . KILOMETERS
) ; return distance
; } public GeoResults < RedisGeoCommands. GeoLocation < String > > redisNearByXY ( String key
, double precision
, double dimension
, Integer distance
, Integer count
) { Circle circle
= new Circle ( new Point ( precision
, dimension
) , new Distance ( distance
, Metrics . KILOMETERS
) ) ; RedisGeoCommands. GeoRadiusCommandArgs args
= RedisGeoCommands. GeoRadiusCommandArgs . newGeoRadiusArgs ( ) . includeDistance ( ) . includeCoordinates ( ) . sortAscending ( ) . limit ( count
) ; GeoResults < RedisGeoCommands. GeoLocation < String > > results
= redisTemplate
. opsForGeo ( ) . radius ( key
, circle
, args
) ; return results
; } public GeoResults < RedisGeoCommands. GeoLocation < String > > geoNearByPlace ( String key
, String name
, Integer distance
, Integer count
) { Distance distances
= new Distance ( distance
, Metrics . KILOMETERS
) ; RedisGeoCommands. GeoRadiusCommandArgs args
= RedisGeoCommands. GeoRadiusCommandArgs . newGeoRadiusArgs ( ) . includeDistance ( ) . includeCoordinates ( ) . sortAscending ( ) . limit ( count
) ; GeoResults < RedisGeoCommands. GeoLocation < String > > results
= redisTemplate
. opsForGeo ( ) . radius ( key
, name
, distances
, args
) ; return results
; } public List < String > geoHash ( String key
, List < String > nameList
) { List < String > results
= redisTemplate
. opsForGeo ( ) . hash ( key
, nameList
) ; return results
; } }
GeoHashUtil的使用詳情請查看:
https
: / / blog
. csdn
. net
/ weixin_41677422
/ article
/ details
/ 108260110
總結
分頁實現思路:將geo集合中的數據按距離由近到遠篩選好后,通過storedist放入一個新的Zset集合,然后通過zset的語法實現分頁。
總結
以上是生活随笔 為你收集整理的SpringBoot使用GeoHash分页查询附近的人:RedisTemplate+GeoHash+Lua 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。