1. 前言

老板突然要上线一个需求,获取当前位置方圆一公里的业务代理点。明天上线!当接到这个需求的时候我差点吐血,这时间也太紧张了。赶紧去查相关的技术选型。经过一番折腾,终于在晚上十点完成了这个需求。现在把大致实现的思路总结一下。

2. MySQL 不合适

遇到需求,首先要想到现有的东西能不能满足,成本如何。

MySQL是我首先能够想到的,毕竟大部分数据要持久化到MySQL。但是使用MySQL需要自行计算Geohash。需要使用大量数学几何计算,并且需要学习地理相关知识,门槛较高,短时间内不可能完成需求,而且长期来看这也不是MySQL擅长的领域,所以没有考虑它。

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 points = new HashMap<>();

points.put("tianjin", new Point(117.12, 39.08));

points.put("shijiazhuang", new Point(114.29, 38.02));

// RedisTemplate 批量添加 Geo

redisTemplate.boundGeoOps("cities:locs").add(points);

复制代码

@Bean

public ApplicationRunner cacheActiveAppRunner(RedisTemplate redisTemplate){

return args -> {

final String GEO_KEY = "cities:locs";

// 清理缓存

redisTemplate.delete(GEO_KEY);

Map points = new HashMap<>();

points.put("tianjin", new Point(117.12, 39.08));

points.put("shijiazhuang", new Point(114.29, 38.02));

// RedisTemplate 批量添加 GeoLocation

BoundGeoOperations geoOps = redisTemplate.boundGeoOps(GEO_KEY);

geoOps.add(points);

};

}

复制代码

地理数据持久化到MySQL,然后同步到Redis中。

3.3 查询附近的特定位置

RedisTemplate 针对GEORADIUS命令也有封装:

GeoResults> 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>封装的结果,我们对这个可迭代对象进行解析就可以拿到我们想要的数据:

GeoResults> radius = redisTemplate.opsForGeo()

.radius(GEO_STAGE, circle, args);

if (radius != null) {

List stageDTOS = new ArrayList<>();

radius.forEach(geoLocationGeoResult -> {

RedisGeoCommands.GeoLocation 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来实现。

mysql查询周边商铺_利用Redis的Geo功能实现查找附近的店铺相关推荐

  1. mysql查询周边商铺_基于Mysql5.7实现查找附近的店铺

    我们新开发了一个电商平台,需要实现附近的店铺功能,经过预研,觉得没有必要采用mongodb的地理位置查询功能,因为涉及数据同步,还有联合索引的问题.直接用MySQL5.7内置的距离计算功能就可以满足大 ...

  2. mysql redis geo_利用Redis的Geo功能实现查找附近的位置

    1. 前言 老板突然要上线一个需求,获取当前位置方圆一公里的业务代理点.明天上线!当接到这个需求的时候我差点吐血,这时间也太紧张了.赶紧去查相关的技术选型.经过一番折腾,终于在晚上十点完成了这个需求. ...

  3. Spring Boot 2 实战:利用Redis的Geo功能实现查找附近的位置

    1. 前言 老板突然要上线一个需求,获取当前位置方圆一公里的业务代理点.明天上线!当接到这个需求的时候我差点吐血,这时间也太紧张了.赶紧去查相关的技术选型.经过一番折腾,终于在晚上十点完成了这个需求. ...

  4. 利用Redis的Geo功能实现查找附近的位置!

    1. 前言 老板突然要上线一个需求,获取当前位置方圆一公里的业务代理点.明天上线!当接到这个需求的时候我差点吐血,这时间也太紧张了.赶紧去查相关的技术选型.经过一番折腾,终于在晚上十点完成了这个需求. ...

  5. mysql查询无限下级_示例php+mysql查询实现无限下级分类树输出

    本文实例讲述了php+mysql查询实现无限下级分类树输出.分享给大家供大家参考,具体如下: 这里介绍的php结合mysql查询无限下级树输出,其实就是无限分类.给各位整理了几个php无限分类的例子. ...

  6. mysql 查询时间转换_数据库查询时日期的转换

    首先简单说明一下,laravel框架中查询并打印sql语句的办法,不管任何时候由于sql语句报错时,都可以先打印一下,分析一下是什么原因造成的错误 ①引入laravel框架DB类useIllumina ...

  7. mysql查询会话池_用户会话,数据控件和AM池

    mysql查询会话池 最近,有人问我有关应用程序模块池的有趣问题. 众所周知,AM池包含用户会话引用的应用程序模块实例,这允许会话在后续请求时从池中获取完全相同的AM实例. 如果应用程序中有多个根应用 ...

  8. amoeba实现mysql主从读写分离_利用Amoeba实现MySQL主从复制和读写分离

    在实际生产环境中,如果对数据库的读和写都在同一个数据库服务器中操作,无论是在安全性.高可用性,还是高并发等各个方面都是完全不能满足实际需求的,因此,一般来说都是通过主从复制(Master-Slave) ...

  9. php mysql查询中文乱码_解决php mysql查询插入中文乱码问题_PHP教程

    解决php mysql查询插入中文乱码问题 一.中文问题其实就是经统一编码了否则就会乱码 1.数据库与php页面编码统一 2.数据库数据表字段与页面编码统一 如果做到上面两种就不存在中文乱码问题了,那 ...

  10. mysql 查询存储过程 速度_查询mysql过程

    MySql 使用explain分析查询 今天写了个慢到哭的查询,想用explain分析下执行计划,后来发现explain也是有局限性的: EXPLAIN不会告诉你关于触发器.存储过程的信息或用户自定义 ...

最新文章

  1. JEECG支付宝服务窗开发培训视频
  2. [导入]ZT笑到内伤:史上最雷,最爆寒的电影字幕
  3. android模糊后面视频,在安卓手机上怎么制作中间是横视频上下是模糊效果的竖视频?手机视频短片制作...
  4. Windows文件夹、文件源代码对比工具--WinMerge
  5. Tomcat 7 'javax.el.ELException' 的解决方式(failed to parse the expression [${xxx}])
  6. linux内核与用户空间的九种通信机制
  7. Hbuilderx编辑器介绍(00)
  8. LeetCode(1108)——IP 地址无效化(JavaScript)
  9. 手机怎么安装py thon_Python计数器– Py​​thon集合计数器
  10. 181103每日一句
  11. python网易云歌词爬虫_用python爬取网易云音乐歌曲的歌词
  12. step13. ubuntu18.04下载安装配置Hive(转)
  13. jquery中的ajax写法
  14. 广告投放媒体发展简史
  15. Golang 生成钱包地址
  16. 雪花算法:分布式唯一 ID 生成利器
  17. python 命令行解析模块_Python命令行解析模块详解
  18. Android 组件化方案 JIMU 体验
  19. W25Q64Flash芯片STM32操作
  20. HTMl综合各大网站谈谈Meta标签和meta property=og标签含义

热门文章

  1. 发展型机器人:由人类婴儿启发的机器人. 2.6 本章总结
  2. Struts2表单验证的xml配置
  3. swift UI专项训练19 TextView 多行文本
  4. 文件上传时判断是否为图片
  5. C语言中字符串存储方法
  6. 我的vs2010扩展备忘.jpg
  7. 50款漂亮的免费网页PSD模板下载(第三季)
  8. 创建,删除和移动文件夹以及文件夹列表
  9. 8.企业应用架构模式 --- 通盘考虑
  10. 5.数据结构 --- 数组和广义表