如何使用Redis实现附近商家查询

这篇具有很好参考价值的文章主要介绍了如何使用Redis实现附近商家查询。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

如何使用Redis实现附近商家查询,redis,数据库,缓存,java

🔥🔥宏夏Coding网站,致力于为编程学习者、互联网求职者提供最需要的内容!网站内容包括求职秘籍,葵花宝典(学习笔记),资源推荐等内容。在线阅读:https://hongxiac.com🔥🔥

导读

在日常生活中,我们经常能看见查询附近商家的功能。

常见的场景有,比如你在点外卖的时候,就可能需要按照距离查询附近几百米或者几公里的商家。

本文将介绍如何使用Redis实现按照距离查询附近商户的功能,并以SpringBoot项目作为举例。

如何使用Redis实现附近商家查询,redis,数据库,缓存,java

想知道这样的功能是如何实现的吗?接着往下看吧!

Redis地理位置功能

Redis是一种高性能的键值存储数据库,具有快速读写能力和丰富的数据结构支持。在Redis 3.2版本之后,它引入了地理位置(Geospatial)功能,使其可以轻松处理与地理位置相关的数据。

地理位置功能的核心数据结构是有序集合(Sorted Set),它将元素与分数(score)关联起来。在地理位置功能中,分数表示地理位置的经度和纬度,而元素则是一个标识符,比如商户的ID。

我们只需要在数据库中存储商家的经纬度,以商家id作为key,经纬度作为value存入redis中,就可以通过redis命令来获得以某一个点为圆心一定范围内的商家,以及他们之间的距离。

如何使用Redis实现附近商家查询,redis,数据库,缓存,java

常用命令

1. GEOADD:将地理位置添加到有序集合中
   使用GEOADD命令,可以将一个或多个地理位置添加到有序集合中。语法如下:

GEOADD key longitude latitude member [longitude latitude member ...]

示例:
   GEOADD stores 116.404 39.915 "storeA"
   GEOADD stores 116.418 39.917 "storeB"

2. GEODIST:计算两个位置之间的距离

  GEODIST命令用于计算两个位置之间的距离,可以指定单位(米、千米、英里、英尺等)。


   GEODIST key member1 member2 [unit]

   示例:

   GEODIST stores storeA storeB km

3. GEORADIUS:按照距离查询位置范围内的元素
   GEORADIUS命令用于在指定的地理位置范围内查询元素。它可以按照经纬度坐标和半径来查询,还可以限制返回的结果数量。

 GEORADIUS key longitude latitude radius unit [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key]

   示例:
 
   GEORADIUS stores 116.408 39.916 1 km WITHDIST COUNT 5
  

4. GEOHASH:获取位置的geohash值
   GEOHASH命令用于获取指定位置的geohash值,geohash是一种将地理位置编码成字符串的方法,可以用于快速近似的位置计算。

 GEOHASH key member [member ...]

   示例:

   GEOHASH stores storeA storeB

5. GEOPOS:获取一个或多个位置的经纬度坐标
   GEOPOS命令用于获取一个或多个位置的经纬度坐标。


   GEOPOS key member [member ...]

   示例:

   GEOPOS stores storeA storeB
   

6. GEORADIUSBYMEMBER:根据成员获取范围内的元素
   这个命令与GEORADIUS类似,但是它以一个已有的成员作为中心点进行查询。

 
   GEORADIUSBYMEMBER key member radius unit [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key]
  
   示例:
   
   GEORADIUSBYMEMBER stores storeA 1 km
   

地理位置功能不仅在查询附近商户等实际应用中非常有用,还可以应用于地理分析、位置推荐等领域。它通过利用Redis强大的有序集合数据结构,使得处理地理信息变得高效、灵活,并且易于集成到现有的应用中。无论是构建LBS应用还是处理位置相关数据,Redis的地理位置功能都能为开发者提供强大的支持。

Java代码实现

将数据库中的商家经纬度存入redis

数据库中有一张商家表,其中有经度,纬度这两个字段。我们可以通过单元测试批量将这些商家的经纬度数据存入redis。key为商家id,value为经纬度。

/**
     * 将数据库中的商户坐标添加到缓存
     */
    @Test
    void addShopGeo2Redis(){
        //获取商户集合
        List<Shop> list = shopService.list();
        //根据商户类型分类
        Map<Long, List<Shop>> collect = list.stream().collect(Collectors.groupingBy(Shop::getTypeId));
        for (Map.Entry<Long, List<Shop>> longListEntry : collect.entrySet()) {
            Long typeId = longListEntry.getKey();
            String key = "shop:geo:" + typeId;
            //获取商户经纬度
            List<Shop> shopList = longListEntry.getValue();
            List<RedisGeoCommands.GeoLocation<String>> locations = new ArrayList<>(shopList.size());
            for (Shop shop : shopList) {
//                stringRedisTemplate.opsForGeo().add(key,new Point(shop.getX(),shop.getY()),shop.getId().toString());
                //先收集完所有商户的地理位置,再一次性添加到redis
                locations.add(new RedisGeoCommands.GeoLocation<>(shop.getId().toString(),new Point(shop.getX(),shop.getY())));
            }
            stringRedisTemplate.opsForGeo().add(key,locations);
        }
    }

接口类:queryShopByType(typeId,current,x,y)

定义一个根据商家类型查询所有商家的接口,如果前端传来的参数中携带该用户的经纬度,则代表需要根据距离查询附近商家。

  /**
     * 根据商铺类型分页查询商铺信息
     * @param typeId 商铺类型
     * @param current 页码
     * @return 商铺列表
     */
    @GetMapping("/of/type")
    public Result queryShopByType(
            @RequestParam("typeId") Integer typeId,
            @RequestParam(value = "current", defaultValue = "1") Integer current,
            @RequestParam(value = "x", required = false) Double x,
            @RequestParam(value = "y", required = false) Double y
    ) {

        return shopService.queryShopByType(typeId, current, x, y);
    }

服务类:queryShopByType(typeId,current,x,y)

1.首先判断是否经纬度参数x和y是否为空

2.计算分页参数(redis无法分页,需要手动分页)

3.查询redis

4.获取商户id集合

5.根据商户id查询数据库

6.返回

  @Override
    public Result queryShopByType(Integer typeId, Integer current, Double x, Double y) {
        //1.判断是否需要根据坐标查询
        if(x == null || y == null){
            //直接数据库查询
            Page<Shop> page = query().eq("type_id", typeId).page(new Page<>(current, SystemConstants.DEFAULT_PAGE_SIZE));
            return Result.ok(page.getRecords());
        }
        //2.计算分页参数
        int from = (current - 1) * SystemConstants.DEFAULT_PAGE_SIZE;
        int end = current * SystemConstants.DEFAULT_PAGE_SIZE;

        //3.查询redis,按照距离排序,分页。结果:shopId,distance
        String key = SHOP_GEO_KEY + typeId;
        GeoResults<RedisGeoCommands.GeoLocation<String>> results = stringRedisTemplate.opsForGeo()
                .search(
                        key,
                        GeoReference.fromCoordinate(x, y),
                        new Distance(5000),
                        RedisGeoCommands.GeoSearchCommandArgs.newGeoSearchArgs().includeDistance().limit(end)
                );

        //4.解析出id
        if(results == null){
            return Result.ok(Collections.emptyList());
        }
        List<GeoResult<RedisGeoCommands.GeoLocation<String>>> list = results.getContent();
        if(list.size() <= from){
            //没有下一页
            return Result.ok(Collections.emptyList());
        }
        //4.1截取from——end部分
        List<Long> ids = new ArrayList<>(list.size());
        Map<String, Distance> distanceMap = new HashMap<>(list.size());
        list.stream().skip(from).forEach(result -> {
            String shopIdStr = result.getContent().getName();
            ids.add(Long.valueOf(shopIdStr));
            Distance distance = result.getDistance();
            distanceMap.put(shopIdStr,distance);
        });
        //5.根据id查询shop
        String idStr = StrUtil.join(",",ids);
        List<Shop> shops = query().in("id",ids).last("ORDER BY FIELD(id," + idStr + ")").list();
        for (Shop shop : shops){
            shop.setDistance(distanceMap.get(shop.getId().toString()).getValue());
        }

        //6.返回
        return Result.ok(shops);
    }
}

注意点

1.redis查询的结果是从第1条到第end条,不能直接返回第begin条到第end条。

那么如何跳过begin前面的记录呢?

可以使用stream()流的skip()方法,skip()方法中指定参数begin,就会跳过前面的begin条记录。

2.通过redis获取的ids集合,再使用mybatis-plus使用query().in()进行查询时,会破坏数据顺序,如何解决?

手动指定顺序。在后面加上last("ORDER BY FIELD(id," + idStr + ")").list()。而idStr = StrUtil.join(",",ids);文章来源地址https://www.toymoban.com/news/detail-658299.html

到了这里,关于如何使用Redis实现附近商家查询的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Redis--Geo指令的语法和使用场景举例(附近的人功能)

    前言 Redis除了常见的五种数据类型之外,其实还有一些少见的数据结构,如Geo,HyperLogLog等。虽然它们少见,但是作用却不容小觑。本文将介绍Geo指令的语法和使用场景。 Geo介绍 Geo是\\\"geolocation\\\"的缩写,即地理定位器,顾名思义就是记录地理位置信息,用来进行地址位置排序

    2024年01月20日
    浏览(41)
  • Redis - 附近商铺、用户签到、UV统计

    底层都是基于地理坐标进行搜索,支持地理坐标的技术有很多,Redis就是其中之一 GEO 就是Geolocation的简写形式,代表 地理坐标 。 Redis 在3.2版本中加入了对GEO的支持, 允许存储地理坐标信息 ,帮助我们根据经纬度来检索数据。 常见的命令有 : GEOADD :添加一个地理空间信息,

    2024年02月13日
    浏览(42)
  • 使用Nodejs搭建HTTP服务,并实现公网远程访问Redis数据库「内网穿透」

    转载自cpolar极点云文章:公网远程连接Redis数据库「内网穿透」 Redis作为一款高速缓存的key value键值对的数据库,在许许多多的场景中广泛使用,由于是把数据存储在内存中,所以读写效率极高。 下面介绍如何在内网虚拟机的linux中搭建redis并通过cpolar内网穿透实现公网访问 进入

    2024年02月13日
    浏览(45)
  • 如何使用 Redis 快速实现分布式锁?

    本文我们来讨论如何使用 Redis 快速实现分布式锁。 分布式锁有很多种解决方案,前面简单介绍过,Redis 可以通过 set key 方式来实现分布式锁,但实际情况要更加复杂,比如如何确保临界资源的串行执行,如何及时释放,都是需要额外考虑的。 本文要讲的是一个完备的分布式

    2024年02月04日
    浏览(41)
  • 在Spring中,可以使用不同的方式来实现分布式锁,例如基于数据库、Redis、ZooKeeper等

    在Spring中,可以使用不同的方式来实现分布式锁,例如基于数据库、Redis、ZooKeeper等。下面是两种常见的实现方式: 使用Redis实现分布式锁: 使用自定义注解实现本地锁: 以上是两种常见的在Spring中实现分布式锁的方式。第一种方式使用Redis作为分布式锁的存储介质,通过

    2024年03月17日
    浏览(46)
  • 126、高频Redis面试题:如何保证Redis和数据库数据一致性

    问题:如果数据库中的某条数据放入缓存后,又马上被更新了,那我们应该如何更新缓存 缺点: 如果先更新缓存成功,在更新数据库的时候失败,这时候会导致数据不一致;缓存的作用是不是临时将我们数据保存在内存,便于提高查询速度;但是如果某条数据在数据库中都

    2024年02月13日
    浏览(43)
  • redis的缓存更新策略以及如何保证redis与数据库的数据一致性

    redis的缓存更新策略有这么几种: 1、由应用直接和redis以及数据库相连接:         查询数据时,应用去redis中查询,查不到的话再由应用去数据库中查询,并将查询结果放在redis;         更新数据时,由应用去触发redis数据的删除以及数据库的update。 2、应用只跟redi

    2024年02月13日
    浏览(57)
  • Redis如何保证缓存和数据库一致性?

    现在我们在面向增删改查开发时,数据库数据量大时或者对响应要求较快,我们就需要用到Redis来拿取数据。 Redis:是一种高性能的内存数据库,它将数据以键值对的形式存储在内存中,具有读写速度快、支持多种数据类型、原子性操作、丰富的特性等优势。 优势: 性能极高

    2024年01月16日
    浏览(70)
  • Redis---数据库和缓存如何保证一致性?

    用「读 + 写」请求的并发的场景来分析: 假如某个用户数据在缓存中不存在,请求 A 读取数据时从数据库中查询到年龄为 20,在未写入缓存中时另一个请求 B 更新数据。它更新数据库中的年龄为 21,并且清空缓存。这时请求 A 把从数据库中读到的年龄为 20 的数据写入到缓存

    2024年01月24日
    浏览(57)
  • mysql和redis如何保证数据库一致性

    如果对于小公司的单机服务器来说在更新和删除mysql数据的同时对redis缓存进行更新或者删除就行,一般有两个选择,例如: 先更新MySQL,后删除(或更新)Redis 先删除(或更新)Redis,后更新MySQL 但是不管使用其中哪种方式,都存在两个可能的问题: 由于第一步与第二步并不是原

    2023年04月24日
    浏览(52)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包