利用Redis的Geo功能实现查找附近的位置!
1. 前言
老板突然要上线一个需求,获取当前位置方圆一公里的业务代理点。明天上线!当接到这个需求的时候我差点吐血,这时间也太紧张了。赶紧去查相关的技术选型。经过一番折腾,终于在晚上十点完成了这个需求。现在把大致实现的思路总结一下。
2. MySQL 不合适
遇到需求,首先要想到现有的东西能不能满足,成本如何。
MySQL是我首先能够想到的,毕竟大部分数据要持久化到 MySQL 。但是使用 MySQL 需要自行计算 Geohash 。需要使用大量数学几何计算,并且需要学习地理相关知识,门槛较高,短时间内不可能完成需求,而且长期来看这也不是 MySQL 擅长的领域,所以没有考虑它。
Geohash 参考 https://www.cnblogs.com/LBSer...
2. Redis 中的GEO
Redis是我们最为熟悉的 K-V 数据库,它常被拿来作为高性能的缓存数据库来使用,大部分项目都会用到它。从 3.2 版本开始它开始提供了 GEO 能力,用来实现诸如附近位置、计算距离等这类依赖于地理位置信息的功能。 GEO 相关的命令如下:
Redis命令描述GEOHASH返回一个或多个位置元素的 Geohash 表示GEOPOS从key里返回所有给定位置元素的位置(经度和纬度)GEODIST返回两个给定位置之间的距离GEORADIUS以给定的经纬度为中心, 找出某一半径内的元素GEOADD将指定的地理空间位置(纬度、经度、名称)添加到指定的key中GEORADIUSBYMEMBER找出位于指定范围内的元素,中心点是由给定的位置元素决定
Redis会假设地球为完美的球形, 所以可能有一些位置计算偏差,据说<=0.5%,对于有严格地理位置要求的需求来说要经过一些场景测试来检验是否能够满足需求。
2.1 写入地理信息
那么如何实现目标单位半径内的所有元素呢?我们可以将所有的位置的经纬度通过上表中的 GEOADD 将这些地理信息转换为52位的 Geohash 写入 Redis 。
该命令格式:
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),并按照从近到远排列。命令如下:
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"
你可以加上 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中的添加命令转换为下面的代码:
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 批量添加 GeoredisTemplate.boundGeoOps("cities:locs").add(points);
可以结合 Spring Boot 提供的ApplicationRunner接口 来实现初始化。
@Bean public ApplicationRunner cacheActiveAppRunner(RedisTemplate<String, String> redisTemplate) {return args -> {final String GEO_KEY = "cities:locs";// 清理缓存redisTemplate.delete(GEO_KEY);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 批量添加 GeoLocationBoundGeoOperations<String, String> geoOps = redisTemplate.boundGeoOps(GEO_KEY);geoOps.add(points);}; }
地理数据持久化到MySQL,然后同步到Redis中。
3.3 查询附近的特定位置
RedisTemplate 针对 GEORADIUS 命令也有封装:
GeoResults<GeoLocation<M>> radius(K key, Circle within, GeoRadiusCommandArgs args)
Circle 对象是封装覆盖的面积(图1),需要的要素为中心点坐标 Point 对象、半径(radius)、计量单位(metric), 例如:
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);
GeoRadiusCommandArgs 用来封装 GEORADIUS 的一些可选命令参数,参见 章节2.2 中的 WITHCOORD 、 COUNT 、 ASC 等,例如我们需要在返回结果中包含坐标、中心距离、由近到远排序的前5条数据:
RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeDistance().includeCoordinates().sortAscending().limit(limit);
然后执行 radius 方法就会拿到 GeoResults<RedisGeoCommands.GeoLocation<String>> 封装的结果,我们对这个可迭代对象进行解析就可以拿到我们想要的数据:
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 名称 如 tianjin String name = content.getName();// 对应的经纬度坐标Point pos = content.getPoint();// 距离中心点的距离Distance dis = geoLocationGeoResult.getDistance();}); }
3.4 删除元素
有时候我们可能需要删除某个位置元素,但是 Redis 的 Geo 并没有删除成员的命令。不过由于它的底层是 zset ,我们可以借助 zrem 命令进行删除,对应的 Java 代码为:
redisTemplate.boundZSetOps(GEO_STAGE).remove("tianjin");
4. 总结
今天我们使用 Redis 的 Geo 特性实现了常见的附近的地理信息查询需求,简单易上手。其实使用另一个 Nosql 数据库 MongoDB 也可以实现。在数据量比较小的情况下 Redis 已经能很好的满足需要。如果数据量大可使用 MongoDB 来实现。 文中涉及的 DEMO 可关注: 码农小胖哥,公众号回复 redisgeo 获取。
利用Redis的Geo功能实现查找附近的位置!相关推荐
- Spring Boot 2 实战:利用Redis的Geo功能实现查找附近的位置
1. 前言 老板突然要上线一个需求,获取当前位置方圆一公里的业务代理点.明天上线!当接到这个需求的时候我差点吐血,这时间也太紧张了.赶紧去查相关的技术选型.经过一番折腾,终于在晚上十点完成了这个需求. ...
- mysql redis geo_利用Redis的Geo功能实现查找附近的位置
1. 前言 老板突然要上线一个需求,获取当前位置方圆一公里的业务代理点.明天上线!当接到这个需求的时候我差点吐血,这时间也太紧张了.赶紧去查相关的技术选型.经过一番折腾,终于在晚上十点完成了这个需求. ...
- Redis之GEO存储地理位置信息
在外卖软件中的附近的美食店铺.外卖小哥的距离,打车软件附近的车辆,交友软件中附近的小姐姐.我们都可以利用redis的GEO地理位置计算得出. 1.Redis 的 Geo 是在 3.2 版本才有的 2. ...
- 如何利用redis实现秒杀系统
文章目录 题记 利用Watch实现Redis乐观锁 题记 在线思维导图总结:redis大纲 利用Watch实现Redis乐观锁 乐观锁基于CAS(Compare And Swap)思想(比较并替换), ...
- 【案例实战】SpringBoot整合Redis的GEO实现查找附近门店功能
像我们平常美团点外卖的时候,都会看到一个商家距离我们多少米.还有类似QQ附近的人,我们能看到附近的人距离我们有多少米. 那么这些业务是怎么做的呢?是如何实现 基于位置的附近服务系统呢. 在去了解基于位 ...
- SpringBoot 使用 Redis Geo 实现查找附近的位置-附近的人功能
SpringBoot 使用 Redis Geo 实现查找附近的位置 6个操作命令 Redis 命令 描述 GEOADD 增加某个地理位置的坐标 GEOPOS 获取某个地理位置的坐标 GEODIST 获 ...
- php reids的geo功能,Redis GEO相关命令和功能,你造吗?
Redis 是一个高性能的key-value数据库,其最大优点就是,很大程度补偿了memcached这类key/value存储的不足,在部分场合可以对关系数据库起到很好的补充作用.同时Redis还提供 ...
- redis 的geo地理位置及实现附近的人的功能
前言 redis在3.2版本里面新增的一个功能就是对GEO(地理位置)的支持. 理位置大概提供了6个命令,分别为: GEOADD GEODIST GEOHASH GEOPOS GEORADIUS GE ...
- 简单利用Redis实现草稿箱功能
简单利用Redis实现草稿箱功能 我这边图方便直接把接收到的对象存为String,大家也可以用json存储,取的时候在转成json就行了.此处使用jedis. 具体实现的思路很简单,就是利用redis ...
最新文章
- 八、Flume的构架,安装和基本使用
- cmd命令安装composer踩坑
- MySQL ERROR 1071 (42000): Specified key was too long; max key length is 767 bytes
- zTree使用技巧与详解
- typescript赋值
- 字段与属性的总结与比较
- 如何像在SQL中一样使用#39;in#39;和#39;not in#39;过滤Pandas数据帧
- C# 连接sql 2005
- 编程基本功:带着本子却不记录,你以为听懂了记住了,不可能的
- 法语语音教学课件下载
- windows 搭建kms服务器激活_搭建kms服务器,自建KMS激活服务器的两种方法
- USB设备仿真框架设计指南——10.用USB设备模拟器测试USB驱动程序
- FPGA(七) PWM波
- APUE---chap8(进程控制)---8.11(setuid/getuid)
- 与孤独世界的博弈——诺贝尔奖得主约翰·纳什的传奇一生
- IE无法打开网页的常见原因及解决
- [EULAR文摘] 滑膜HIF-1a与类风湿关节炎的关节破坏
- html设置图片切割,HTML+CSS实现合并图片的切割显示以及背景渲染
- 使用Origin画出复杂网络博弈中合作率时间演化图(学术论文)
- L05 Laravel 教程 - 电商实战