redis-geo经纬度查询-从进到远的分页
背景:
最近接到一个需求是实现一个附近的人功能,目前几个主流数据库mysql、mongo、redis都有类似的功能,其原理都是建立空间索引再通过空间函数转化坐标点去查询,考虑到查询效率和数据不需要长期持久化,这里选择redis,底层由zset实现。
简单应用:
这里使用Spring自带的spring-data-redis库
- 添加
/**
*
* @param key zsetKey
* @param member 元素
* @param lng 经度
* @param lat 纬度
* @return 影响数量
*/
@Override
public Long geoAdd(String key, Object member, double lng, double lat) {
Long add = defaultRedisTemplate.opsForGeo().add(key, new Point(lng, lat), member);
return add;
}
-
删除
因为redis-geo基于zset实现,所以直接使用zremove即可
public long zRemove(String key, Object member) {
return defaultRedisTemplate.opsForZSet().remove(key, member);
}
-
点-半径查询
Circle 类有两个字段
private final Point center; // 圆心 private final Distance radius;// 半径
/**
*
* @param key
* @param circle 查询圆
* @param limit 查询条数
* @return
*/
@Override
public List<GeoResult<Object>> geoRadiusWithDist(String key, Circle circle, Long limit) {
GeoResults results= defaultRedisTemplate.opsForGeo().radius(key,circle, RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeDistance().limit(limit));
return results.getContent();
}
分页快照查询:
上面介绍了基于点的半径查询,但实际中应用中可能数据量较大,不建议一次性返回,需要分页查询,下面介绍一种方法:
原理:先通过半径查询再缓存到一个zset中,再通过查询zset使用offset实现分页查询。参考命令:https://redis.io/commands/georadius/
由于目前使用的redisTemplate不支持redis的GEORADIUS命令带STOREDIST参数,所以基于lua脚本实现方案如下
-
缓存快照
/** * 查询并缓存快照 * @param scriptKeys 脚本key * @param scriptParams 脚本参数 * @return 影响条数 举例命令:GEORADIUS geo-key 100 20 10000 km COUNT 100 STOREDIST testZset1 解释: ARGV[1]:geo的key:geo-key ARGV[2]:经度:100 ARGV[3]:纬度:20 ARGV[4]:距离值:10000 ARGV[5]:距离单位:km KEYS[1]:GEORADIUS命令自带COUNT参数(指定查询参数):COUNT ARGV[6]:取数量:100 KEYS[2]:GEORADIUS命令自带STOREDIST参数(将结果缓存到指定zset中):STOREDIST ARGV[7]:指定的zset名 */ public Long geoRadiusAndStoreDist(List<String> scriptKeys, List<String> scriptParams) { String script = "return redis.call('GEORADIUS',ARGV[1],ARGV[2],ARGV[3],ARGV[4],ARGV[5],KEYS[1],ARGV[6],KEYS[2],ARGV[7])"; return (Long) defaultRedisTemplate.execute(new DefaultRedisScript<>(script, Long.class), scriptParams); }
总结:
本文主要介绍了使用redis存储geo地理位置信息及基于spring-redis-data组件的查询方案,并且延伸了一种可分页查询的方案,主要解决两个方面的问题:
1、一次地理位置查询返回结果数量过多响应慢
2、将数据缓存为快照,可以保证多次使用offset分页查询不会有重复数据,且降低redis服务压力