Redis GEO 类型与 API 结合,地理位置优化的绝佳实践
🔭 嗨,您好 👋 我是 vnjohn,在互联网企业担任 Java 开发,CSDN 优质创作者
📖 推荐专栏:Spring、MySQL、Nacos、Java,后续其他专栏会持续优化更新迭代
🌲文章所在专栏:MySQL、Redis、业务设计
🤔 我当前正在学习微服务领域、云原生领域、消息中间件等架构、原理知识
💬 向我询问任何您想要的东西,ID:vnjohn
🔥觉得博主文章写的还 OK,能够帮助到您的,感谢三连支持博客🙏
😄 代词: vnjohn
⚡ 有趣的事实:音乐、跑步、电影、游戏
目录
前言
在企业开发中,例如:附近服务门店/网点查询、附近服务工人派单查询,若没有合理去设计地理位置的这块查询性能提升的功能时,都是会去数据库层面采用函数计算出来,这种方式本来就存在一定的弊端
数据库层面是性能瓶颈,将所有的压力放在数据库中,必然会给系统带来灾难级的响应,例如:当同时访问的用户量递增时,数据库连接池打满 > CPU 飘升 > 系统长时间停留在数据库层面无法及时响应给用户
2、当服务门店/网点数据量越来越大时、服务工人数据越来越庞大时,在使用函数计算筛选出附近的数据,必然会造成数据库的全表扫描 >explain type:ALL
3、当最近的服务门店/网点、服务工人不满足用户的需求对象时,会一直向下拉取下一页的数据,直至筛选到满足自己的服务对象才停止,每一段的筛选都是一次性能极差的 SELECT
故而言之,因为这种问题的出现,不得已而从其他方面去考虑来提升地理位置这块的筛选动作,由数据库「磁盘存储经纬度」改为缓存「内存存储经纬度」来提升重复的查询操作
该文会演示从数据库层面 > 缓存层面,地理位置的优化提升改造
MySQL 数据库
现大部分企业都采用 MySQL 作为数据库存储,所以以 MySQL 8.0 为例,演练在它里面如何采用函数来完成地理位置的计算
表结构
CREATE TABLE `shop` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '门店id', `shop_no` varchar(64) NOT NULL COMMENT '门店编码', `shop_name` varchar(50) NOT NULL COMMENT '门店名称', `status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '启用状态:1-启用、0-', `logo` varchar(255) DEFAULT NULL COMMENT '门店Logo', `introduce` text COMMENT '门店介绍', `longitude` double NOT NULL COMMENT '经度', `latitude` double NOT NULL COMMENT '纬度', `trade_start_time` time DEFAULT NULL COMMENT '营业开始时间', `trade_end_time` time DEFAULT NULL COMMENT '营业结束时间', `contacts` varchar(20) DEFAULT NULL COMMENT '联系人', `telephone` varchar(50) DEFAULT NULL COMMENT '商家联系电话', `province_id` bigint DEFAULT NULL COMMENT '省id', `province` varchar(255) DEFAULT NULL COMMENT '省', `city_id` bigint DEFAULT NULL COMMENT '市id', `city` varchar(255) DEFAULT NULL COMMENT '市', `area_id` bigint DEFAULT NULL COMMENT '区id', `area` varchar(255) DEFAULT NULL COMMENT '区', `address` varchar(255) DEFAULT NULL COMMENT '门店详细地址', `created_time` datetime DEFAULT NULL COMMENT '创建时间', `updated_time` datetime DEFAULT NULL COMMENT '更新时间', `is_deleted` tinyint(1) DEFAULT '0' COMMENT '是否删除', PRIMARY KEY (`id`), UNIQUE KEY `uni_shop_no` (`shop_no`) COMMENT '门店编码唯一索引') ENGINE=InnoDB AUTO_INCREMENT=45 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='商家信息';
首先创建一张商家表「门店/服务网点」涉及到地理位置比较重要的两个字段,longitude > 经度、latitude > 纬度
经度的最大值:180°
纬度的最大值:90°
模拟数据
使用存储函数,模拟生成十万条商家数据
CREATE DEFINER = `root` @`localhost` PROCEDURE `batchInsert` ( IN args INT ) BEGINDECLARE-- 开启事务i INT DEFAULT 1;START TRANSACTION;WHILEi <= args DOINSERT INTO shop ( shop_no, shop_name, `status`, longitude, latitude ) VALUE(ROUND( RAND() * 99999 ),concat( "商家-", i ),1,-- 随机生成经纬度(RAND() * ( 179.077090052913654 - 0.477040512464626 )) + 0.477040512464626,(RAND() * ( 89.9172823750000134 - - 1.8840792500000134 )) + - 1.8840792500000134 );SET i = i + 1;END WHILE;COMMIT;END
call batchInsert(100000);
数据库查询
不加索引
先使用「经纬度」字段不加索引的方式执行 SQL
EXPLAIN SELECT* FROM( SELECT id, ST_DISTANCE_SPHERE ( POINT ( 114.112808, 22.544977 ), POINT ( longitude, latitude )) AS distance FROM shop WHERE `STATUS` = 1 ) temp WHEREROUND( distance / 1000, 2 ) BETWEEN 0 AND 20 ORDER BY distance ASC LIMIT 5
执行计划结果如下:
加索引
alter table shop add index `idx_location` (`longitude`,`latitude`) USING BTREE;
再次执行 SQL,如下:
```sqlEXPLAIN SELECT* FROM( SELECT id, ST_DISTANCE_SPHERE ( POINT ( 114.112808, 22.544977 ), POINT ( longitude, latitude )) AS distance FROM shop WHERE `STATUS` = 1 ) temp WHEREROUND( distance / 1000, 2 ) BETWEEN 0 AND 20 ORDER BY distance ASC LIMIT 5
执行计划结果如下:
直译函数
MySQL 官方直译 ST_DISTANCE_SPHERE 函数说明
语法:ST_Distance_Sphere(g1, g2 [, radius])
说明:
返回球体之间 Point 或 MultiPoint 参数之间的最小球面距离(以米为单位)可选 radius 参数应以米为单位给出
如果两个几何参数都是有效的笛卡尔参数 Point 或 MultiPoint SRID 0 中的值,则返回值是具有所提供半径的球体上两个几何之间的最短距离。如果省略,则默认半径为 6,370,986 米,点 X 和 Y 坐标分别解释为经度和纬度(以度为单位)
如果任何参数的经度或纬度超出范围,则会发生错误:
若经度值不在 (−180, 180] 范围内,则会发生 ER_GEOMETRY_PARAM_LONGITUDE_OUT_OF_RANGE 错误(在 MySQL 8.0.12 ER_LONGITUDE_OUT_OF_RANGE 之前)
2、若纬度值不在 [−90, 90] 范围内,则会发生 ER_GEOMETRY_PARAM_LATITUDE_OUT_OF_RANGE 错误(在 MySQL 8.0.12 ER_LATITUDE_OUT_OF_RANGE 之前)
小结
从以上数据库做地理位置筛选的结果来看,无论是否追加索引,似乎对数据库的查询性能来说,并没有提升
使用数据库做地理位置筛选,基于以下几种情况可以考虑使用该方式进行处理
商家「服务门店/网点」数据量不多
商家「服务门店/网点」模块提供给用户服务的入口较小
Redis 缓存
基于 Redis API 实现地理位置使用 GEO 有两种方式
org.springframework.data.redis.core.RedisTemplate
2、org.redisson.api.RedissonClient
Redis GEO 客户端
该篇节,先告知大家如何应用 Redis 客户端的 GEO 类型,API 会基于客户端的函数进行一次封装,先了解底层开始再到最后的高级 API 实践
查看 Redis 版本
redis-cli -v
连接 Redis 客户端
redis-cli
2、无密码直接登录,有密码通过:auth 明文密码
查看 GEO、ZSet 帮助文档
help @GEO
help @sorted-set
127.0.0.1:6379> help @GEO # GEO 指定的缓存 Key 追加 1~N 条经纬度地理位置信息 GEOADD key [NX|XX] [CH] longitude latitude member [longitude latitude member ...] summary: Add one or more geospatial items in the geospatial index represented using a sorted set since: 3.2.0 # GEO 指定的缓存 Key 两个成员之间的距离 # M|KM|FT|MI:米、公里、英里、英尺 GEODIST key member1 member2 [M|KM|FT|MI] summary: Returns the distance between two members of a geospatial index since: 3.2.0 # GEO 指定缓存 Key 地理位置索引 > 标准地理散列字符串 GEOHASH key member [member ...] summary: Returns members of a geospatial index as standard geohash strings since: 3.2.0 # GEO 指定缓存 Key 地理位置索引 > 成员对应的经纬度 GEOPOS key member [member ...] summary: Returns longitude and latitude of members of a geospatial index since: 3.2.0 # GEO 指定缓存 Key:查询表示地理空间索引的排序集,以传入的经纬度来获取与点的给定最大距离匹配的成员,可按升序、降序排序 GEORADIUS key longitude latitude radius M|KM|FT|MI [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count [ANY]] [ASC|DESC] [STORE key] [STOREDIST key] summary: Query a sorted set representing a geospatial index to fetch members matching a given maximum distance from a point since: 3.2.0 # GEO 指定缓存 Key: 查询表示地理空间索引的排序集,以传入的指定成员经纬度来获取与点的给定最大距离匹配的成员,可按升序、降序排序 GEORADIUSBYMEMBER key member radius M|KM|FT|MI [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count [ANY]] [ASC|DESC] [STORE key] [STOREDIST key] summary: Query a sorted set representing a geospatial index to fetch members matching a given maximum distance from a member since: 3.2.0 # GEO 指定缓存 Key: 查询表示地理空间索引的排序集,以传入的指定成员经纬度来获取与点的给定最大距离匹配的成员,可按升序、降序排序,只支持可读 GEORADIUSBYMEMBER_RO key member radius M|KM|FT|MI [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count [ANY]] [ASC|DESC] summary: A read-only variant for GEORADIUSBYMEMBER since: 3.2.10 # GEO 指定缓存 Key:查询表示地理空间索引的排序集,以传入的经纬度来获取与点的给定最大距离匹配的成员,可按升序、降序排序,只支持可读 GEORADIUS_RO key longitude latitude radius M|KM|FT|MI [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count [ANY]] [ASC|DESC] summary: A read-only variant for GEORADIUS since: 3.2.10 # GEO 指定缓存 Key:查询表示地理空间索引的排序集,以获取「成员或指定经纬度」最大距离匹配的成员,可按升序、降低排序,不支持存储 GEOSEARCH key FROMMEMBER member|FROMLONLAT longitude latitude BYRADIUS radius M|KM|FT|MI|BYBOX width height M|KM|FT|MI [ASC|DESC] [COUNT count [ANY]] [WITHCOORD] [WITHDIST] [WITHHASH] summary: Query a sorted set representing a geospatial index to fetch members inside an area of a box or a circle. since: 6.2.0 # GEO 指定缓存 Key:查询表示地理空间索引的排序集,以「获取成员或指定经纬度」最大距离匹配的成员,可按升序、降低排序,支持存储至 ZSet Key GEOSEARCHSTORE destination source FROMMEMBER member|FROMLONLAT longitude latitude BYRADIUS radius M|KM|FT|MI|BYBOX width height M|KM|FT|MI [ASC|DESC] [COUNT count [ANY]] [STOREDIST] summary: Query a sorted set representing a geospatial index to fetch members inside an area of a box or a circle, and store the result in another key. since: 6.2.0
引入 Spring、Redisson 配置
maven 依赖配置
<properties> <spring.boot.version>2.6.7spring.boot.version> <redisson.version>3.17.5redisson.version>properties><dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-dependenciesartifactId> <version>${spring.boot.version}version> <type>pomtype> <scope>importscope> dependency> <dependency> <groupId>org.redissongroupId> <artifactId>redisson-spring-boot-starterartifactId> <version>${redisson.version}version>dependency> dependencies>dependencyManagement>
Redis 核心配置类,如下:
@Configurationpublic class RedisConfig { @Resource private RedisConnectionFactory factory; @Bean public RedisTemplate<String, Object> redisTemplate() { RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setHashKeySerializer(new StringRedisSerializer()); redisTemplate.setHashValueSerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer()); redisTemplate.setStringSerializer(new StringRedisSerializer()); redisTemplate.setDefaultSerializer(new StringRedisSerializer()); redisTemplate.setConnectionFactory(factory); return redisTemplate; } @Bean public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) { return redisTemplate.opsForHash(); } @Bean public ValueOperations<String, String> valueOperations(RedisTemplate<String, String> redisTemplate) { return redisTemplate.opsForValue(); } @Bean public GeoOperations<String, String> geoOperations(RedisTemplate<String, String> redisTemplate) { return redisTemplate.opsForGeo(); } @Bean public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) { return redisTemplate.opsForList(); } @Bean public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) { return redisTemplate.opsForSet(); } @Bean public ZSetOperations<String, String> zSetOperations(RedisTemplate<String, String> redisTemplate) { return redisTemplate.opsForZSet(); }}
在本文,我们会用到 GeoOperations、ZSetOperations 操作类去调用 API
RedisTemplate API 操作
RedisTemplate 操作工具类,如下:
@Resource private GeoOperations<String, String> geoOperations;@Resource private ZSetOperations<String, String> zSetOperations;// ============================ sorted-set =============================public ZSetOperations.TypedTuple<String> redisTemplateZSetPopMinScore(String key) { return zSetOperations.popMin(key); }// ============================ Geo ============================= public void geoAdd(String key, Double longitude, Double latitude, String member) { Point point = new Point(longitude, latitude); geoOperations.add(key, point, member); } public void geoRemove(String key, String member) { geoOperations.remove(key, member); } public GeoResults<RedisGeoCommands.GeoLocation<String>> geoRadiusWithKilometers(String key, Double longitude,Double latitude, Double distanceNum) { return geoRadiusWithKilometers(key, longitude, latitude, distanceNum, null, Boolean.TRUE); } public GeoResults<RedisGeoCommands.GeoLocation<String>> geoRadiusWithKilometersLimit(String key, Double longitude, Double latitude, Integer limit, Double distanceNum) { return geoRadiusWithKilometers(key, longitude, latitude, distanceNum, limit, Boolean.TRUE); } public GeoResults<RedisGeoCommands.GeoLocation<String>> geoRadiusWithKilometers(String key, Double longitude, Double latitude,Double distanceNum, Integer limit, Boolean ascOrder) { Point point = new Point(longitude, latitude); Distance radius = new Distance(distanceNum, Metrics.KILOMETERS); Circle within = new Circle(point, radius); RedisGeoCommands.GeoRadiusCommandArgs geoRadiusCommandArgs = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeCoordinates().includeDistance(); if (null != limit) { geoRadiusCommandArgs = geoRadiusCommandArgs.limit(limit); } geoRadiusCommandArgs = ascOrder ? geoRadiusCommandArgs.sortAscending() : geoRadiusCommandArgs.sortDescending(); return geoOperations.radius(key, within, geoRadiusCommandArgs); } public GeoResults<RedisGeoCommands.GeoLocation<String>> geoRadiusWithMeters(String key, Double longitude, Double latitude, Double distanceNum) { return geoRadiusWithMeters(key, longitude, latitude, distanceNum, true); } public GeoResults<RedisGeoCommands.GeoLocation<String>> geoRadiusWithMeters(String key, Double longitude, Double latitude, Double distanceNum, Boolean ascOrder) { Point point = new Point(longitude, latitude); Distance radius = new Distance(distanceNum, Metrics.NEUTRAL); Circle within = new Circle(point, radius); RedisGeoCommands.GeoRadiusCommandArgs geoRadiusCommandArgs = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeCoordinates().includeDistance(); geoRadiusCommandArgs = ascOrder ? geoRadiusCommandArgs.sortAscending() : geoRadiusCommandArgs.sortDescending(); return geoOperations.radius(key, within, geoRadiusCommandArgs); }public Long redisTemplateStoreSortedSearchTo(String destName, String key, Double longitude, Double latitude, Double distanceNum, Integer limit, Boolean ascOrder) { Distance distance = new Distance(distanceNum, Metrics.KILOMETERS); RedisGeoCommands.GeoSearchStoreCommandArgs geoSearchStoreCommandArgs = RedisGeoCommands.GeoSearchStoreCommandArgs.newGeoSearchStoreArgs(); geoSearchStoreCommandArgs.limit(limit); geoSearchStoreCommandArgs.sort(ascOrder ? Sort.Direction.ASC : Sort.Direction.DESC); GeoReference geoReference = GeoReference.fromCoordinate(longitude, latitude); Long searchAndStore = geoOperations.searchAndStore(key, destName, geoReference, distance, geoSearchStoreCommandArgs); return searchAndStore; }
geoAdd 方法 -> GEOADD 函数
2、geoRemove 方法 -> ZREM 函数
GEO 存储起来以后放在 Redis 中是以 ZSet 结构进行存储的,所以将 GEO 某个元素删除时,就调用 ZREM 函数进行删除即可
geoRadiusWithKilometers、geoRadiusWithMeters 方法操作的都是相同的函数,只是筛选距离的单位不同,一个是千米、一个是米,它们对应的函数有两个,GEORADIUS — 筛选附近距离的满足元素、GEORADIUS_RO — 筛选附近距离的满足元素,只支持可读
具体的方法执行逻辑可以查看以下方法源码:RedisGeoCommands#GeoRadiusCommandArgs,该方法主要对我们传入的参数进行一次封装,转换为 Redis 中可识别的函数参数可选项
public GeoResults<GeoLocation<byte[]>> geoRadius(byte[] key, Circle within, GeoRadiusCommandArgs args) { List<Object> params = new ArrayList<Object>(); params.add(key); params.add(convert(within.getCenter().getX())); params.add(convert(within.getCenter().getY())); params.add(within.getRadius().getValue()); params.add(getAbbreviation(within.getRadius().getMetric())); RedisCommand<GeoResults<GeoLocation<byte[]>>> command; if (args.getFlags().contains(GeoRadiusCommandArgs.Flag.WITHCOORD)) { command = new RedisCommand<GeoResults<GeoLocation<byte[]>>>("GEORADIUS_RO", postitionDecoder); params.add("WITHCOORD"); } else { MultiDecoder<GeoResults<GeoLocation<byte[]>>> distanceDecoder = new ListMultiDecoder2(new GeoResultsDecoder(within.getRadius().getMetric()), new GeoDistanceDecoder()); command = new RedisCommand<GeoResults<GeoLocation<byte[]>>>("GEORADIUS_RO", distanceDecoder); params.add("WITHDIST"); } if (args.getLimit() != null) { params.add("COUNT"); params.add(args.getLimit()); } if (args.getSortDirection() != null) { params.add(args.getSortDirection().name()); } return read(key, ByteArrayCodec.INSTANCE, command, params.toArray());}
引入 RedisTemplate API 有一些特性,我们在实际应用中可能应用不到,如:
当 GEO 中某个成员不知道它是否存在,当不存在时可以直接新增,存在时不做任何变更,RedisTemplate API 需要操作两次函数:geoRemove、geoAdd,而下面要讲解的 Redisson API 直接可以通过一个函数搞定,好处:减少一次与 Redis 之间的连接,提高操作效率
2、使用 Redisson 客户端,实现「搜索满足距离条件的成员列表」功能时更加的便捷
若 Redisson 版本不对时,会在操作 redisTemplateZSetPopMinScore 方法时,出现如下异常:
java.lang.StackOverflowError: null at org.springframework.data.redis.connection.DefaultedRedisConnection.zPopMin(DefaultedRedisConnection.java:973)
解决办法:将 Redisson 版本降低到 3.15.6
Redisson API 操作
Redisson 操作工具类,如下:
private static final StringCodec REDISSON_CODE_C = new StringCodec();@Resourceprivate RedissonClient redissonClient;// ============================ ZSet Redisson =============================public String redissonZSetPopMinScore(String key) { RScoredSortedSet<Object> scoredSortedSet = redissonClient.getScoredSortedSet(key, REDISSON_CODE_C); return (String) scoredSortedSet.pollFirst();}// ============================ Geo Redisson =============================private RGeo<String> getRGeoClient(String key) { return redissonClient.getGeo(key, REDISSON_CODE_C);}public Boolean redissonGeoAddIfExists(String key, Object member, Double longitude, Double latitude) { RGeo<String> geo = getRGeoClient(key); return geo.addIfExists(new GeoEntry(longitude, latitude, member)) > 0;}public void redissonGeoRemove(String key, List<Long> members) { RGeo<String> geo = getRGeoClient(key); geo.removeAll(members);}public void redissonGeoAdd(String key, Object member, Double longitude, Double latitude) { RGeo<String> geo = getRGeoClient(key); geo.add(new GeoEntry(longitude, latitude, member));}public Map<String, Double> searchWithDistance(String key, Double longitude, Double latitude, Double distanceNum) { return searchWithDistance(key, longitude, latitude, distanceNum, GeoUnit.KILOMETERS, null);}public Map<String, Double> searchWithDistance(String key, Double longitude, Double latitude, Double distanceNum, Integer limit) { return searchWithDistance(key, longitude, latitude, distanceNum, GeoUnit.KILOMETERS, limit);}public Map<String, Double> searchWithDistance(String key, Double longitude, Double latitude, Double distanceNum, GeoUnit geoUnit, Integer limit) { RGeo<String> geo = getRGeoClient(key); GeoSearchArgs args; if (null != limit) { args = GeoSearchArgs.from(longitude, latitude).radius(distanceNum, geoUnit).order(GeoOrder.ASC).count(limit); } else { args = GeoSearchArgs.from(longitude, latitude).radius(distanceNum, geoUnit).order(GeoOrder.ASC); } return geo.searchWithDistance(args);}public Boolean storeSortedSearchTo(String destName, String key, Double longitude, Double latitude, Double distanceNum) { return storeSortedSearchTo(destName, key, longitude, latitude, distanceNum, GeoUnit.KILOMETERS, null);}public Boolean storeSortedSearchTo(String destName, String key, Double longitude, Double latitude, Double distanceNum, GeoUnit geoUnit, Integer limit) { RGeo<String> geo = getRGeoClient(key); GeoSearchArgs args; if (null != limit) { args = GeoSearchArgs.from(longitude, latitude).radius(distanceNum, geoUnit).order(GeoOrder.ASC).count(limit); } else { args = GeoSearchArgs.from(longitude, latitude).radius(distanceNum, geoUnit).order(GeoOrder.ASC); } return geo.storeSortedSearchTo(destName, args) > 0;}
Redisson 中对不同的编码还进行了优化,若知道当前存储或查询的元素属于非字符类型,可以通过以下类型来指定:
字符型:StringCodec,默认使用 UTF-8 编码方式
2、字节数组型:ByteArrayCodec
3、整型:IntegerCodec
4、浮点型:DoubleCodec
…
它们共同的父类为 BaseCodec,除了字符型,其他的编码类型都有实现各自的解码器
redissonGeoAdd 方法 -> GEOADD 函数
2、redissonGeoRemove 方法 -> ZREM 函数
与 RedisTemplate API 一致,GEO 存储起来以后放在 Redis 中是以 ZSet 结构进行存储的,所以将 GEO 某个元素删除时,就调用 ZREM 函数进行删除即可
redissonGeoAddIfExists -> GEOPOS、GEOADD 函数一起组合使用的
可观察该方法的实现:
RedissonGeo#addIfExistsAsync
,内部使用 Redis Lua 脚本实现了这两个函数的组合运用,当 GEOPOS 返回的数据为真时,那么就调用 GEOADD 函数将当前元素存入 GEO Key 中
searchWithDistance 方法,它对应的函数有两个,GEORADIUS — 筛选附近距离的满足元素、GEORADIUS_RO — 筛选附近距离的满足元素,只支持可读
5、storeSortedSearchTo 方法,将筛选出来的内容存储到一个新的 ZSet Key 中
应用场景如下:当用户在某个地点下单以后,需要筛选它附近可派单的工人,可筛选指定人数(只要满足服务距离条件)存储到新的 Key 中,当存储完成以后,即使第一个被派单的工人取消服务了,可以利用 ZSet 作为一个栈的结构,按照最近或最远的方式进行一个一个的弹出来 > Pop,结合 redissonZSetPopMinScore 方法天衣无缝!!
小结
若要使用 RedisTemplate API 中的 redisTemplateStoreSortedSearchTo 方法或者使用 Redisson API 中的 storeSortedSearchTo 方法,Redis 服务端的版本必须高于或等于 6.2.0
这两个方法对应 Redis 中的 GEOSEARCHSTORE 函数,可以使用 help GEOSEARCHSTORE
命令,结合帮助文档运用起来
在如何考虑是否引入一个新的组件,来减少对数据库造成的压力,就需要看地理位置这块筛选的工作数据量有多大了,数据量大的话,宁愿基于内存来完成地理位置筛选,也不要将查询数据压力放在基于磁盘的数据库
引入一个新的组件,必然而然会考虑到引入这个组件会带来哪些问题,那么又要解决好组件给我们的问题了,数据存储到内存中并不可靠,所以在对引入 Redis 组件时,我们要把它的持久化机制考虑进去,结合 Redis 保证地理位置查询性能高效、持久化机制保证数据可靠
Redis 持久化机制类型:AOF、RDB
1、采用 AOF 方式进行持久化,一行一行 Redis 命令会入文件,会导致文件过大,从而造成恢复数据速度会很慢,也会给机器磁盘带来存储压力,好处就是能保证数据基本不丢失
2、采用 RDB 方式进行持久化,会导致一部分数据在瞬时丢失,从而就导致了数据存储不可靠,好处就是恢复速率快
3、结合以上两种方式都有缺点,AOF+RDB 结合作为持久化方式,不仅仅用到了 AOF 数据可靠性也用到了 RDB 恢复数据的效率性
Redis 持久化机制 AOF、RDB、AOF+RDB 方式的详细内容,会在后续有文章进行介绍,敬请期待!!
总结
该篇博文,主要先是进行「地理位置」生产性能问题的全流程演化,从 MySQL -> +索引 -> 不 + 索引,使用了案例 SQL 进行执行计划的分析,从而得出了 MySQL 在特殊场景下不适用于做地理位置的筛选工作「因为它本身基于磁盘的,在大数据量情况下,不能肆意打压瓶颈」;随即采用了 Redis GEO 类型来优化了地理位置的筛选工作,结合 RedisTemplate、Redisson 客户端 API 实战函数进行讲解,从零到一教你如何运用程序结合 Redis GEO 数据类型完成地理位置的优化工程,希望此博文你能够喜欢!
🌟🌟🌟愿你我都能够在寒冬中相互取暖,互相成长,只有不断积累、沉淀自己,后面有机会自然能破冰而行!
博文放在 Redis 专栏里,欢迎订阅,会持续更新!
如果觉得博文不错,关注我 vnjohn,后续会有更多实战、源码、架构干货分享!
推荐专栏:Spring、MySQL,订阅一波不再迷路
大家的「关注❤️ + 点赞👍 + 收藏⭐」就是我创作的最大动力!谢谢大家的支持,我们下文见!
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341