实践:了解Redis Geo范围查询,获取当前位置最近的经纬度点

前言

近期有个获取车辆所处道路的需求,车辆行驶的范围在一个城市的市区内,针对一个城市的道路经纬度节点的数据量会比较大(就济南市而言,目前数据量在20万左右),数据的准确性以及检索效率是首要考虑的问题。

推荐阅读

Redis Geo

经过一系列的调研后,由于数据的量级也还可以,决定采用Redis Geo来解决这个问题。

Redis3.2+版本开始对Geo的支持进行了增强,提供了可以根据给定经纬度点位置作为中心点,在指定范围内进行检索距离最近的经纬度点。

美团外卖、饿了么等APP上根据手机位置定位范围中(1km内)的商家,类似于这种的需求也可以使用Redis Geo来实现。

yuqiyu@hengyu ~> redis-cli
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> geoadd road:nodes:370100 117.1087416 36.7148919 point1 
(integer) 1
127.0.0.1:6379> geoadd road:nodes:370100 117.1087006 36.7152294 point2 
(integer) 1
127.0.0.1:6379> keys *
1) "road:nodes:370100"

# 查询一条经纬度
127.0.0.1:6379> georadius road:nodes:370100 117.1089668 36.7151653 100 m withdist withcoord count 1
1) 1) "point2"
   2) "24.5815"
   3) 1) "117.10870295763015747"
      2) "36.7152294132502206"

# 查询两条经纬度
127.0.0.1:6379> georadius road:nodes:370100 117.1089668 36.7151653 100 m withdist withcoord count 2
1) 1) "point2"
   2) "24.5815"
   3) 1) "117.10870295763015747"
      2) "36.7152294132502206"
2) 1) "point1"
   2) "36.4573"
   3) 1) "117.10874050855636597"
      2) "36.71489229533602838"

geoadd 命令

geoadd key longitude latitude member [longitude latitude member ...]
  • key:geo集合的唯一键
  • longitude:新增GPS位置的经度
  • latitude:新增GPS位置的纬度
  • member:该GPS位置的唯一标识

georadius 命令

georadius key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]
  • key:geo集合的唯一键
  • longitude:待检索的GPS经度
  • latutude:待检索的GPS纬度
  • radius:检索的范围,单位可选择:米(m)、千米(km)、英里(mi)、英尺(ft)
  • withcoord:将匹配的经纬度输出
  • withdist:将匹配经纬度的距离输出
  • count:输出匹配的数量
  • asc|desc:根据距离排序,asc:由近到远,desc:由远到近

georadius指令会将给定的经纬度作为检索的中心点,在指定范围内进行检索匹配的经纬度点的位置。

检索实现

在实践的过程中,使用了两种方式来进行测试,发现在检索的效率上有着轻微的差异,下面通过代码实践来进行比对。

Spring Data 方式检索

spring-boot-starter-data-redisSpringBoot提供用于操作Redis的依赖,内部集成的是lettuce,下面是通过RedisTemplate的方式来检索范围内的点的代码实现。

/**
 * Spring Data方式测试Redis Geo
 *
 * @author 恒宇少年
 */
@SpringBootTest
@Slf4j
public class SpringDataRedisGeoTest {
    /**
     * Redis Geo Key
     */
    private static final String GEO_KEY = "road:nodes:370100";
    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 检索geo集合内的最近位置
     */
    @Test
    public void searchPoint() {
        double longitude = 117.1089668;
        double latitude = 36.7151653;
        Point centerPoint = new Point(longitude, latitude);
        Distance distance = new Distance(100, RedisGeoCommands.DistanceUnit.METERS);
        Circle circle = new Circle(centerPoint, distance);
        RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands
                .GeoRadiusCommandArgs
                .newGeoRadiusArgs()
                .includeDistance()
                .includeCoordinates()
                .sortAscending()
                .limit(1);
        GeoResults<RedisGeoCommands.GeoLocation<String>> radius = redisTemplate.boundGeoOps(GEO_KEY).radius(circle, args);
        for (GeoResult<RedisGeoCommands.GeoLocation<String>> result : radius) {
            RedisGeoCommands.GeoLocation<String> content = result.getContent();
            log.info("检索的结果,唯一标识:{},位置:{},距离:{}.",
                    content.getName(), content.getPoint(), result.getDistance());
        }
    }
}

Redission方式检索

Redisson内部自定义封装了操作Redis的逻辑,对Redis Geo也做了支持,经过测试发现,Redisson方式要比Spring Data方式检索的效率高。

以10万条数据为例,Spring Data方式检索需要300ms左右,而Redisson方式检索仅需要90ms左右。

/**
 * Redisson方式测试Redis Geo
 *
 * @author 恒宇少年
 */
@SpringBootTest
@Slf4j
public class RedissonRedisGeoTest {
    private static final String GEO_KEY = "road:nodes:370100";
    @Autowired
    private RedissonClient redissonClient;

    @Test
    public void searchPoint() {
        double longitude = 117.1089668;
        double latitude = 36.7151653;
        RGeo<String> geo = redissonClient.getGeo(GEO_KEY, new StringCodec());
        GeoSearchArgs args = GeoSearchArgs.from(longitude, latitude)
                .radius(100, GeoUnit.METERS)
                .order(GeoOrder.ASC)
                .count(1);
        Map<String, Double> resultMap = geo.searchWithDistance(args);
        resultMap.keySet().stream().forEach(member ->
                log.info("检索结果,匹配位置的标识:{},距离:{}.", member, resultMap.get(member)));
    }
}

总结

以上两种方式操作Redis Geo 都是可以的,有一点要注意,如果集成了Redisson依赖,Spring Data方式无法获取范围内点的Distance(距离)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 142,061评论 1 300
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 60,947评论 1 255
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 93,744评论 0 211
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 40,878评论 0 175
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 48,601评论 1 255
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 38,696评论 1 175
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 30,324评论 2 267
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,120评论 0 165
  • 想象着我的养父在大火中拼命挣扎,窒息,最后皮肤化为焦炭。我心中就已经是抑制不住地欢快,这就叫做以其人之道,还治其人...
    爱写小说的胖达阅读 28,921评论 6 229
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 32,459评论 0 213
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,234评论 2 214
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 30,558评论 1 226
  • 白月光回国,霸总把我这个替身辞退。还一脸阴沉的警告我。[不要出现在思思面前, 不然我有一百种方法让你生不如死。]我...
    爱写小说的胖达阅读 24,203评论 0 31
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,088评论 2 213
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 31,460评论 3 204
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,604评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 25,975评论 0 166
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 33,460评论 2 230
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 33,536评论 2 229

推荐阅读更多精彩内容