【进阶篇】2.3 三分钟了解Redis地理位置数据结构GeoHash


在这里插入图片描述

0. Redis从入门到精通系列文章

  1. 《Redis 从入门到精通【进阶篇】之Lua脚本详解》
  2. 《Redis 从入门到精通【实践篇】SpringBoot Redis 配置多数据源》
  3. 《Redis 从入门到精通【进阶篇】三分钟了解Redis地理位置数据结构GeoHash》
  4. 《Redis 从入门到精通【进阶篇】一文学会Lua脚本》
  5. 《Redis使用Lua脚本和Redisson来保证库存扣减中的原子性和一致性》
  6. 《SpringBoot Redis 使用Lettuce和Jedis配置哨兵模式》
  7. 《Redis【应用篇】之RedisTemplate基本操作》
  8. 《Redis 从入门到精通【实践篇】之SpringBoot配置Redis多数据源》
  9. 《Redis 从入门到精通【进阶篇】之三分钟了解Redis HyperLogLog 数据结构》
  10. 《Redis 从入门到精通【进阶篇】之三分钟了解Redis地理位置数据结构GeoHash》
  11. 《Redis 从入门到精通【进阶篇】之高可用哨兵机制(Redis Sentinel)详解》
  12. 《Redis 从入门到精通【进阶篇】之redis主从复制详解》
  13. 《Redis 从入门到精通【进阶篇】之Redis事务详解》
  14. 《Redis从入门到精通【进阶篇】之对象机制详解》
  15. 《Redis从入门到精通【进阶篇】之消息传递发布订阅模式详解》
  16. 《Redis从入门到精通【进阶篇】之持久化 AOF详解》
  17. 《Redis从入门到精通【进阶篇】之持久化RDB详解》
  18. 《Redis从入门到精通【高阶篇】之底层数据结构字典(Dictionary)详解》
  19. 《Redis从入门到精通【高阶篇】之底层数据结构快表QuickList详解》
  20. 《Redis从入门到精通【高阶篇】之底层数据结构简单动态字符串(SDS)详解》
  21. 《Redis从入门到精通【高阶篇】之底层数据结构压缩列表(ZipList)详解》
  22. 《Redis从入门到精通【进阶篇】之数据类型Stream详解和使用示例》

0. 前言

随着移动互联网的普及,越来越多的应用需要对地理位置信息进行存储和查询,如LBS(Location-Based Service)服务、附近的人功能、地图搜索等。为了满足这些应用的需求,Redis提供了一种地理位置数据结构,称为GeoHash,可以存储和查询地理位置信息。本文将对Redis地理位置数据结构进行详细介绍。

1. 基本介绍

一、什么是Redis地理位置数据结构?

Redis地理位置数据结构是一种基于GeoHash算法实现的数据结构,用于存储和查询地理位置信息。GeoHash算法是一种将地理位置信息映射为一个字符串的算法,可以将一个经纬度坐标转换为一个字符串,这个字符串可以用来比较两个地理位置之间的距离。Redis中的地理位置数据结构支持Point和Member两种数据类型,其中Point表示一个地理位置的经纬度坐标,Member表示一个地理位置,包括名称和经纬度坐标。

二、Redis中的地理位置数据结构命令

Redis提供了以下命令来处理地理位置数据结构:

  1. GEOADD:将一个或多个地理位置添加到指定的键中。

语法:GEOADD key longitude latitude member [longitude latitude member …]

示例:GEOADD mylocations 116.48105 39.996794 “Beijing” 121.47370 31.23037 “Shanghai”

  1. GEOPOS:获取一个或多个地理位置的经纬度坐标。

语法:GEOPOS key member [member …]

示例:GEOPOS mylocations “Beijing” “Shanghai”

返回结果:1) 1) “116.48104810762405” 2) “39.99679381454699” 2) 1) “121.4737012386322” 2) “31.23037014274587”

  1. GEODIST:计算两个地理位置之间的距离。

语法:GEODIST key member1 member2 [unit]

示例:GEODIST mylocations “Beijing” “Shanghai” km

返回结果:“1068.9887”

  1. GEORADIUS:查询指定地理位置附近的其他地理位置。

语法:GEORADIUS key longitude latitude radius unit [WITHCOORD] [WITHDIST] [ASC|DESC] [COUNT count]

示例:GEORADIUS mylocations 116.405285 39.904989 10 km WITHDIST WITHCOORD

返回结果:1) 1) “Beijing” 2) “0.0000” 3) 1) “116.48104810762405” 2) “39.99679381454699” 2) 1) “Shanghai” 2) “1068.9887” 3) 1) “121.4737012386322” 2) “31.23037014274587”

  1. GEORADIUSBYMEMBER:查询指定地理位置附近的其他地理位置,以另一个地理位置为中心。

语法:GEORADIUSBYMEMBER key member radius unit [WITHCOORD] [WITHDIST] [ASC|DESC] [COUNT count]

示例:GEORADIUSBYMEMBER mylocations “Beijing” 10 km WITHDIST WITHCOORD

返回结果:1) 1) “Beijing” 2) “0.0000” 3) 1) “116.48104810762405” 2) “39.99679381454699”

三、场景示例

  1. 附近的商店:一个购物应用程序可以使用GeoHash存储商店的位置信息,并根据用户的当前位置查询附近的商店列表。

    • 存储商店位置信息:
      GEOADD shops 116.397, 39.908 "商店A"
      GEOADD shops 116.412, 39.908 "商店B"
      GEOADD shops 116.395, 39.913 "商店C"
      
    • 查询附近的商店列表(以当前位置116.400, 39.910为例):
      GEORADIUS shops 116.400 39.910 1000 m
      
  2. 出租车调度:出租车调度系统可以使用GeoHash存储出租车的当前位置,并根据乘客的请求查询附近的可用出租车。

    • 存储出租车位置信息:
      GEOADD taxis 116.397, 39.908 "出租车A"
      GEOADD taxis 116.412, 39.908 "出租车B"
      GEOADD taxis 116.395, 39.913 "出租车C"
      
    • 查询附近的可用出租车(以当前位置116.400, 39.910为例):
      GEORADIUS taxis 116.400 39.910 1000 m
      
  3. 社交媒体定位:社交媒体平台可以使用GeoHash存储用户的位置信息,并为用户提供基于位置的推荐和附近的朋友列表。

    • 存储用户位置信息:
      GEOADD users 116.397, 39.908 "用户A"
      GEOADD users 116.412, 39.908 "用户B"
      GEOADD users 116.395, 39.913 "用户C"
      
    • 查询附近的朋友列表(以当前位置116.400, 39.910为例):
      GEORADIUS users 116.400 39.910 1000 m
      
  4. 地理围栏提醒:一个提醒应用程序可以使用GeoHash存储地理围栏的位置信息,并在用户接近或离开围栏区域时发送提醒。

    • 存储地理围栏位置信息:
      GEOADD geofences 116.397, 39.908 "围栏A"
      GEOADD geofences 116.412, 39.908 "围栏B"
      GEOADD geofences 116.395, 39.913 "围栏C"
      
    • 查询接近或离开围栏区域的提醒(以当前位置116.400, 39.910为例):
      GEORADIUSBYMEMBER geofences "用户A" 1000 m
      
  5. 路线规划:一个导航应用程序可以使用GeoHash存储道路和交叉口的位置信息,并根据用户的目的地查询最佳路线。

    • 存储道路和交叉口位置信息:
      GEOADD roads 116.397, 39.908 "交叉口A"
      GEOADD roads 116.412, 39.908 "交叉口B"
      GEOADD roads 116.395, 39.913 "交叉口C"
      ```bash
      
    • 查询最佳路线(以起点116.400, 39.910和终点116.410, 39.912为例):
      GEODIST roads "交叉口A" "交叉口B"
      
  6. 旅游推荐:一个旅游应用程序可以使用GeoHash存储景点和酒店的位置信息,并为用户提供附近的旅游景点和推荐酒店。

    • 存储景点和酒店位置信息:
      GEOADD attractions 116.397, 39.908 "景点A"
      GEOADD attractions 116.412, 39.908 "景点B"
      GEOADD attractions 116.395, 39.913 "景点C"
      
      GEOADD hotels 116.397, 39.908 "酒店A"
      GEOADD hotels 116.412, 39.908 "酒店B"
      GEOADD hotels 116.395, 39.913 "酒店C"
      
    • 查询附近的旅游景点和推荐酒店(以当前位置116.400, 39.910为例):
      GEORADIUS attractions 116.400 39.910 1000 m
      GEORADIUS hotels 116.400 39.910 1000 m
      
  7. 物流管理:一个物流管理系统可以使用GeoHash存储仓库和货物的位置信息,并根据货物的目的地查询最近的仓库。

    • 存储仓库和货物位置信息:
      GEOADD warehouses 116.397, 39.908 "仓库A"
      GEOADD warehouses 116.412, 39.908 "仓库B"
      GEOADD warehouses 116.395, 39.913 "仓库C"
      
      GEOADD shipments 116.397, 39.908 "货物A"
      GEOADD shipments 116.412, 39.908 "货物B"
      GEOADD shipments 116.395, 39.913 "货物C"
      
    • 查询最近的仓库(以货物目的地116.410, 39.912为例):
      GEORADIUSBYMEMBER warehouses "货物A" 1000 m
      
  8. 地理分析:一个地理数据分析应用程序可以使用GeoHash存储地理数据点的位置信息,并进行空间分析和可视化。
    以下是每个应用场景的示例代码或命令:

    • 存储地理数据点位置信息:
      GEOADD data_points 116.397, 39.908 "数据点A"
      GEOADD data_points 116.412, 39.908 "数据点B"
      GEOADD data_points 116.395, 39.913 "数据点C"
      
    • 进行空间分析和可视化:
      GEORADIUS data_points 116.400 39.910 1000 m
      

四、Redis中的地理位置数据结构应用场景

Redis中的地理位置数据结构可以用于各种地理位置相关的应用场景,如LBS(Location-Based Service)服务、附近的人功能、地图搜索等。举个例子,如果你开发了一个餐厅订餐应用,可以使用Redis地理位置数据结构来存储餐厅的地理位置信息,并使用GEORADIUS命令查询用户附近的餐厅,再根据用户的选择进行订餐。

另外,如果你开发了一个社交应用,可以使用Redis地理位置数据结构来存储用户的地理位置信息,并使用GEORADIUSBYMEMBER命令查询用户附近的其他用户,实现附近的人功能。同时,你也可以使用GEODIST命令计算两个用户之间的距离,从而方便你为用户推荐附近的好友或兴趣群组。

总之,Redis提供的地理位置数据结构是一种非常有用的数据结构,可以方便地存储和查询地理位置信息,为地理位置相关的应用提供了很好的支持。同时,通过结合其他Redis数据结构,如hash、set、list等,可以实现更多的功能和应用场景,如地理位置分布图、热门地点排行榜等。如果你正在开发一个地理位置相关的应用,不妨考虑使用Redis地理位置数据结构来实现相关功能。

2. 使用示例

假设我们要开发一个餐厅订餐应用,用户可以根据自己的位置查询附近的餐厅,并进行订餐。

  1. 数据库设计

首先,我们需要设计数据库表来存储餐厅和订单信息。其中,餐厅表包含餐厅名称、经度和纬度等信息,订单表包含订单号、餐厅名称、用户名称和下单时间等信息。

CREATE TABLE restaurant (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(255) NOT NULL,
    longitude DECIMAL(10, 7) NOT NULL,
    latitude DECIMAL(10, 7) NOT NULL
);

CREATE TABLE order (
    id INT PRIMARY KEY AUTO_INCREMENT,
    order_no VARCHAR(255) NOT NULL,
    restaurant_name VARCHAR(255) NOT NULL,
    user_name VARCHAR(255) NOT NULL,
    order_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
  1. Spring Boot配置

接下来,我们需要在Spring Boot中配置Redis,包括连接信息、序列化方式等。这里我们使用Jedis客户端来访问Redis。

@Configuration
public class RedisConfig {

    @Value("${spring.redis.host}")
    private String host;

    @Value("${spring.redis.port}")
    private int port;

    @Bean
    public JedisConnectionFactory jedisConnectionFactory() {
        RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(host, port);
        return new JedisConnectionFactory(config);
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate(JedisConnectionFactory jedisConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(jedisConnectionFactory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
        return template;
    }

}
  1. 地理位置数据操作

接下来,我们可以使用Redis的地理位置数据结构来存储餐厅的位置信息,并使用GEORADIUS命令查询用户附近的餐厅。

@Service
public class RestaurantService {

    private final RedisTemplate<String, Object> redisTemplate;

    public RestaurantService(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    public void addRestaurant(Restaurant restaurant) {
        Point point = new Point(restaurant.getLongitude(), restaurant.getLatitude());
        redisTemplate.opsForGeo().add("restaurants", new GeoLocation<>(restaurant.getName(), point));
    }

    public List<Restaurant> getNearbyRestaurants(double longitude, double latitude, double radius) {
        Circle circle = new Circle(longitude, latitude, new Distance(radius, Metrics.KILOMETERS));
        GeoResults<GeoLocation<Object>> results = redisTemplate.opsForGeo().radius("restaurants", circle);
        List<Restaurant> restaurants = new ArrayList<>();
        for (GeoResult<GeoLocation<Object>> result : results) {
            GeoLocation<Object> location = result.getContent();
            Point point = location.getPoint();
            Restaurant restaurant = new Restaurant();
            restaurant.setName((String) location.getName());
            restaurant.setLongitude(point.getX());
            restaurant.setLatitude(point.getY());
            restaurants.add(restaurant);
        }
        return restaurants;
    }

}
  1. 订单数据操作

最后,我们可以使用Redis的字符串数据结构来存储订单信息,并使用Redis的事务机制来保证订单号的唯一性。

@Service
public class OrderService {

    private final RedisTemplate<String, Object> redisTemplate;

    public OrderService(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    public void placeOrder(String restaurantName, String userName) {
        String orderNo = null;
        while (orderNo == null) {
            redisTemplate.watch("orderNo");
            String currentNo = (String) redisTemplate.opsForValue().get("orderNo");
            if (currentNo == null) {
                redisTemplate.multi();
                redisTemplate.opsForValue().set("orderNo", "1000000001");
                redisTemplate.opsForValue().setBit("orderBitmap", 1, true);
                redisTemplate.exec();
                orderNo = "1000000001";
            } else {
                long nextBit = redisTemplate.opsForValue().bitCount("orderBitmap");
                if (nextBit < 1000000000) {
                    redisTemplate.multi();
                    redisTemplate.opsForValue().set("orderNo", String.valueOf(Long.parseLong(currentNo) + 1));
                   redisTemplate.opsForValue().setBit("orderBitmap", nextBit, true);
                    redisTemplate.exec();
                    orderNo = String.valueOf(Long.parseLong(currentNo) + 1);
                }
            }
        }
        redisTemplate.opsForValue().set(orderNo, new Order(orderNo, restaurantName, userName));
    }

    public List<Order> getOrdersByRestaurant(String restaurantName) {
        List<Order> orders = new ArrayList<>();
        Set<String> keys = redisTemplate.keys("*");
        for (String key : keys) {
            Object value = redisTemplate.opsForValue().get(key);
            if (value instanceof Order) {
                Order order = (Order) value;
                if (order.getRestaurantName().equals(restaurantName)) {
                    orders.add(order);
                }
            }
        }
        return orders;
    }

}

以上就是一个简单的餐厅订餐应用的实现。通过使用Redis的地理位置数据结构和字符串数据结构,我们可以方便地存储和查询地理位置信息和订单信息,同时保证订单号的唯一性。当然,这只是一个简单的示例,实际应用中还需要考虑更多的因素,如并发访问、错误处理、安全性等。

3. 原理解析

  1. GeoHash将二维的经纬度转换成字符串,比如下图展示了北京9个区域的GeoHash字符串,分别是WX4ER,WX4G2、WX4G3等等,每一个字符串代表了某一矩形区域。也就是说,这个矩形区域内所有的点(经纬度坐标)都共享相同的GeoHash字符串,这样既可以保护隐私(只表示大概区域位置而不是具体的点),又比较容易做缓存,比如左上角这个区域内的用户不断发送位置信息请求餐馆数据,由于这些用户的GeoHash字符串都是WX4ER,所以可以把WX4ER当作key,把该区域的餐馆信息当作value来进行缓存,而如果不使用GeoHash的话,由于区域内的用户传来的经纬度是各不相同的,很难做缓存。
    在这里插入图片描述

  2. 字符串越长,表示的范围越精确。如图所示,5位的编码能表示10平方千米范围的矩形区域,而6位编码能表示更精细的区域(约0.34平方千米)

在这里插入图片描述

  1. 字符串相似的表示距离相近(特殊情况后文阐述),这样可以利用字符串的前缀匹配来查询附近的POI信息。如下两个图所示,一个在城区,一个在郊区,城区的GeoHash字符串之间比较相似,郊区的字符串之间也比较相似,而城区和郊区的GeoHash字符串相似程度要低些。

在这里插入图片描述城区
在这里插入图片描述郊区

通过上面的介绍我们知道了GeoHash就是一种将经纬度转换成字符串的方法,并且使得在大部分情况下,字符串前缀匹配越多的距离越近,回到我们的案例,根据所在位置查询来查询附近餐馆时,只需要将所在位置经纬度转换成GeoHash字符串,并与各个餐馆的GeoHash字符串进行前缀匹配,匹配越多的距离越近。

4.Redis的GeoHash数据结构常见问题

4.1. 什么是Redis的GeoHash数据结构?

Redis的GeoHash数据结构是一种用于存储地理位置信息的数据结构。它使用GeoHash编码将地理位置转换为字符串,并采用有序集合的方式存储。每个地理位置被映射为一个唯一的GeoHash值,可以通过GeoHash值进行距离计算和位置查询。

4.2. GeoHash编码是什么?

GeoHash编码是一种将地理位置转换为字符串的编码方式。它将地球表面划分为多个网格,并用二进制编码表示每个网格。GeoHash编码越长,表示的地理位置越具体。

4.3. Redis的GeoHash数据结构如何存储地理位置信息?

Redis的GeoHash数据结构使用有序集合存储地理位置信息。每个地理位置被映射为一个GeoHash值,作为有序集合的成员。有序集合的分值表示了地理位置的经纬度信息。

4.4. 如何查询附近的地理位置?

可以使用Redis的GeoRadius命令查询附近的地理位置。该命令指定了一个中心坐标和一个半径范围,返回在该范围内的地理位置信息。

4.5. GeoRadius命令有哪些参数?

GeoRadius命令有以下参数:

  • key:表示存储地理位置信息的键名。
  • longitude:中心坐标的经度。
  • latitude:中心坐标的纬度。
  • radius:半径范围。
  • unit:半径范围的单位,可以是m、km、mi、ft之一。
  • [WITHCOORD]:返回地理位置的经纬度。
  • [WITHDIST]:返回地理位置与中心坐标的距离。
  • [ASC|DESC]:返回结果的排序方式。

4.6. 如何根据经纬度添加地理位置信息?

可以使用Redis的GeoAdd命令根据经纬度添加地理位置信息。该命令指定了一个键名、一个经度和一个纬度。

4.7. GeoAdd命令有哪些参数?

GeoAdd命令有以下参数:

  • key:表示存储地理位置信息的键名。
  • longitude:地理位置的经度。
  • latitude:地理位置的纬度。
  • member:地理位置的标识符。

4.8. Redis的GeoHash数据结构的应用场景有哪些?

Redis的GeoHash数据结构可以应用于以下场景:

  • 附近的人:查询附近的用户或商家。
  • 地理围栏:检测用户是否在指定的区域内。
  • 地理位置推荐:根据用户位置推荐附近的餐厅、景点等。
  • 出租车调度:根据乘客位置和司机位置匹配。

4.9. Redis的GeoHash数据结构相比其他数据库有哪些优点?

Redis的GeoHash数据结构具有以下优点:

  • 高效的地理位置查询:使用GeoHash编码和有序集合存储,可以快速查询附近的地理位置。
  • 灵活的数据处理:可以对地理位置信息进行排序、筛选和计算距离等操作。
  • 与其他Redis数据类型的兼容性:可以与其他数据类型(如字符串、列表)结合使用,实现更复杂的功能。

4.10. 如何实现实时更新地理位置信息?

可以使用Redis的GeoAdd命令实时更新地理位置信息。每当地理位置发生变化时,可以通过GeoAdd命令更新对应的GeoHash值。

4.11. 如何计算两个地理位置之间的距离?

可以使用Redis的GeoDist命令计算两个地理位置之间的距离。该命令指定了两个地理位置的标识符和距离单位,返回它们之间的距离。

4.12. GeoDist命令有哪些参数?

GeoDist命令有以下参数:

  • key:表示存储地理位置信息的键名。
  • member1:地理位置1的标识符。
  • member2:地理位置2的标识符。
  • unit:距离单位,可以是m、km、mi、ft之一。

4.13. GeoHash编码的精度对结果有何影响?

GeoHash编码的精度越高,表示的地理位置范围越小,查询结果越精确。但较高的精度会增加存储和计算的开销。

4.14. 如何批量查询多个地理位置的经纬度?

可以使用Redis的GeoPos命令批量查询多个地理位置的经纬度。该命令指定了存储地理位置信息的键名和多个地理位置的标识符,返回它们的经纬度信息。

4.15. GeoPos命令有哪些参数?

GeoPos命令有以下参数:

  • key:表示存储地理位置信息的键名。
  • member1:地理位置1的标识符。
  • member2:地理位置2的标识符。

4.16. 如何限制查询结果的数量?

可以使用Redis的ZCOUNT命令限制查询结果的数量。通过设置最大返回结果数量,可以控制查询结果的数量。

4.17. Redis的GeoHash数据结构支持集群模式吗?

是的,Redis的GeoHash数据结构可以在Redis集群模式下使用。GeoHash数据结构的操作与其他数据类型的操作相同。

4.18. 在Redis的GeoHash数据结构中,地理位置信息的存储是有序的吗?

是的,Redis的GeoHash数据结构使用有序集合存储地理位置信息,保持了地理位置的有序性。

4.19. Redis的GeoHash数据结构是否支持事务?

是的,Redis的GeoHash数据结构支持事务。可以在事务中执行多个GeoHash命令,保证操作的原子性。

4.20. Redis的GeoHash数据结构在大规模数据下的性能如何?

Redis的GeoHash数据结构在大规模数据下的性能表现良好。由于使用了GeoHash编码和有序集合存储,可以快速进行地理位置查询和计算距离,适用于处理大量地理位置信息的场景。

在这里插入图片描述大家好,我是冰点,今天的Redis的GeoHash数据结构详解,全部内容就是这些。如果你有疑问或见解可以在评论区留言。

评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰点.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值