Spring boot 2 real combat: using redis’s geo function to find nearby locations


Spring boot 2 real combat: using redis's geo function to find nearby locations

1. Preface

The boss suddenly wants to go online for a demand to obtain the business agent point with the current location of one kilometer. Online tomorrow! When I received this demand, I almost vomited blood, and the time was too tight. Hurry to check the relevant technology selection. After a lot of twists and turns, the demand was finally completed at 10 pm. Now, let’s summarize the idea of general implementation.

Spring boot 2 real combat: using redis's geo function to find nearby locations

2. MySQL is not suitable

When meeting the demand, we should first think about whether the existing things can be satisfied and how the cost is.

MySQLIt’s the first thing I can think of. After all, most of the data needs to be persistent until theMySQL。 But usingMySQLIt needs to be calculated by yourselfGeohash。 It needs to use a lot of mathematical and geometric calculations, and needs to learn geography related knowledge. The threshold is high, and it is impossible to complete the requirements in a short time, and it is not easy to do so in the long runMySQLGood at the field, so did not consider it.

Geohashreference resources https://www.cnblogs.com/LBSer…

2. Geo in redis

RedisIt’s the one we’re most familiar withK-VDatabase, which is often used as a high-performance cache database to use, most projects will use it. from3.2Version starts, it starts to provideGEOAbility, which is used to realize the functions such as nearby location, calculating distance and so on.GEOThe related commands are as follows:

Redis command describe
GEOHASH Returns the location of one or more location elementsGeohashexpress
GEOPOS Returns the position (longitude and latitude) of all the given positioning elements from the key
GEODIST Returns the distance between two given positions
GEORADIUS Taking the given latitude and longitude as the center, find out the elements within a certain radius
GEOADD Adds the specified geospatial location (latitude, longitude, name) to the specified key
GEORADIUSBYMEMBER Find out the elements in the specified range, the center point is determined by the given location element

Redis will assume that the earth is a perfect sphere, so there may be some deviation in position calculation, which is said to be < = 0.5%. For the demand with strict geographical location requirements, it needs to go through some scenario tests to check whether it can meet the demand.

2.1 writing geographic information

So how to achieve all the elements within the target unit radius? We can pass the longitude and latitude of all positions through the above tableGEOADDConvert these geographic information into 52 bitGeohashwrite inRedis

The command format is as follows:

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

Corresponding examples:

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

It means that the longitude is117.12Latitude is39.08The location oftianjinAnd longitude114.29Latitude is38.02The location ofshijiazhuangjoinkeybycities:locsOfsorted setIn the collection. You can add one to more than one location. Then we can calculate the geographical position with the help of other commands.

The effective longitude is from – 180 degrees to 180 degrees. The effective latitude ranges from – 85.05112878 degrees to 85.05112878 degrees. When the coordinate position exceeds the specified range, the command will return an error.

2.2 areas within the radius of statistical units

We can rely onGEORADIUSTo find all the elements within a certain radius with given latitude and longitude.

The command format is as follows:

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

This command is better than thatGEOADDMore complicated:

  • radiusRadius length, required. hindermkmftmi, is the length unit option, choose one of four.
  • WITHCOORDThe longitude and dimension of the location element are also returned, which is not required.
  • WITHDISTWhen the position element is returned, the distance between the position element and the center point is also returned. The unit of distance is consistent with the query unit, which is not required.
  • WITHHASHThe 52 bit precision of the return positionGeohashValue, not required. I seldom use this one anyway. Maybe others are lower levelLBSApplication services need this.
  • COUNTReturns the number of qualified position elements, which is not required. For example, return to the top 10 to avoid performance problems caused by too many matching results.
  • ASC|DESCSorting method is optional. Unsorted is returned by default, but most of us need to sort. Refer to the center position and use it from near to farASCFrom far to nearDESC

For example, we arecities:locs(115.03, 38.44) as the center200kmThe results include the name of the city, the corresponding coordinates and the distance from the center (km), and are arranged from near to far. The order is as follows:

redis> georadius cities:locs 115.03 38.44 200 km WITHCOORD WITHDIST ASC
1) 1) "shijiazhuang"
   2) "79.7653"
   3) 1) "114.29000169038772583"
      2) "38.01999994251037407"
2) 1) "tianjin"
   2) "186.6937"
   3) 1) "117.02000230550765991"
      2) "39.0800000535766543"

You can addCOUNT 1To find the nearest location.

3. Based on redis Geo

Now that the general principles and ideas have been finished, the next step is practical operation. combinationSpring BootWhat should we do?

3.1 development environment

Need to haveGEOCharacteristicRedisVersion, here I use theRedis 4。 In addition, we use the clientspring-boot-starter-data-redis。 We’ll use it hereRedisTemplateObject.

3.2 batch adding location information

In the first step, we need to initialize the location data to theRedisIn the middle. staySpring Data RedisA position coordinate in(lng,lat)Can be encapsulated intoorg.springframework.data.geo.PointObject. Then specify a name to form a locationGeoInformation.RedisTemplateThe method of batch adding location information is provided. We can make itSection 2.1The add command in is converted to the following code:

Map<String, Point> points = new HashMap<>();
   points.put("tianjin", new Point(117.12, 39.08));
   points.put("shijiazhuang", new Point(114.29, 38.02));
   //Redistemplate batch adding Geo

It can be initialized by combining with the applicationrunner interface provided by spring boot.

public ApplicationRunner cacheActiveAppRunner(RedisTemplate<String, String> redisTemplate) {

    return args -> {
        final String GEO_KEY = "cities:locs";

        //Clean up cache
        Map<String, Point> points = new HashMap<>();
        points.put("tianjin", new Point(117.12, 39.08));
        points.put("shijiazhuang", new Point(114.29, 38.02));
        //Redistemplate batch adding geolocation
        BoundGeoOperations<String, String> geoOps = redisTemplate.boundGeoOps(GEO_KEY);

The geographic data is persisted to MySQL and then synchronized to redis.

3.3 query specific location nearby

RedisTemplatein the light ofGEORADIUSThe command also has encapsulation:

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

CircleThe object is the area covered by the package (Figure 1), and the required elements are the coordinates of the center pointPointObject, radius, and metric, for example:

Point point = new Point(115.03, 38.44);

Metric metric = RedisGeoCommands.DistanceUnit.KILOMETERS;
Distance distance = new Distance(200, metric);

Circle circle = new Circle(point, distance);

GeoRadiusCommandArgsUsed to encapsulateGEORADIUSFor some optional command parameters, seeChapter 2.2InWITHCOORDCOUNTASCFor example, we need to include the first five data of coordinates, center distance and sorting from near to far in the returned results

RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands

Then executeradiusThe method will be availableGeoResults<RedisGeoCommands.GeoLocation<String>>As a result of encapsulation, we can get the data we want by parsing the iterative object

GeoResults<RedisGeoCommands.GeoLocation<String>> radius = redisTemplate.opsForGeo()
        .radius(GEO_STAGE, circle, args);

if (radius != null) {
    List<StageDTO> stageDTOS = new ArrayList<>();
    radius.forEach(geoLocationGeoResult -> {
        RedisGeoCommands.GeoLocation<String> content = geoLocationGeoResult.getContent();
        //Member name, such as Tianjin 
        String name = content.getName();
        //Corresponding longitude and latitude coordinates
        Point pos = content.getPoint();
        //Distance from center point
        Distance dis = geoLocationGeoResult.getDistance();

3.4 deleting elements

Sometimes we may need to delete a location element, butRedisOfGeoThere is no command to delete members. But because the bottom layer iszsetWe can usezremCommand to delete, the correspondingJavaThe code is:


4. Summary

Today we useRedisOfGeoFeatures to achieve the common nearby geographic information query requirements, simple and easy to use. Actually, use another oneNosqldatabaseMongoDBIt can also be achieved. In the case of small amount of dataRedisHas been able to meet the needs of a good. If the amount of data is large, it can be usedMongoDBTo achieve. The problems involved in this paper are as followsDEMOFocus on:Manong little fat brotherReply to official accountredisgeoobtain.

Pay attention to the official account: Felordcn for more information

Personal blog: https://felord.cn

Recommended Today

Implementation example of go operation etcd

etcdIt is an open-source, distributed key value pair data storage system, which provides shared configuration, service registration and discovery. This paper mainly introduces the installation and use of etcd. Etcdetcd introduction etcdIt is an open source and highly available distributed key value storage system developed with go language, which can be used to configure sharing […]