文章目录
- 0. Redis从入门到精通系列文章
- 0. 前言
- 1. 基本介绍
- 2. 使用示例
- 3. 原理解析
- 4.Redis的GeoHash数据结构常见问题
- 4.1. 什么是Redis的GeoHash数据结构?
- 4.2. GeoHash编码是什么?
- 4.3. Redis的GeoHash数据结构如何存储地理位置信息?
- 4.4. 如何查询附近的地理位置?
- 4.5. GeoRadius命令有哪些参数?
- 4.6. 如何根据经纬度添加地理位置信息?
- 4.7. GeoAdd命令有哪些参数?
- 4.8. Redis的GeoHash数据结构的应用场景有哪些?
- 4.9. Redis的GeoHash数据结构相比其他数据库有哪些优点?
- 4.10. 如何实现实时更新地理位置信息?
- 4.11. 如何计算两个地理位置之间的距离?
- 4.12. GeoDist命令有哪些参数?
- 4.13. GeoHash编码的精度对结果有何影响?
- 4.14. 如何批量查询多个地理位置的经纬度?
- 4.15. GeoPos命令有哪些参数?
- 4.16. 如何限制查询结果的数量?
- 4.17. Redis的GeoHash数据结构支持集群模式吗?
- 4.18. 在Redis的GeoHash数据结构中,地理位置信息的存储是有序的吗?
- 4.19. Redis的GeoHash数据结构是否支持事务?
- 4.20. Redis的GeoHash数据结构在大规模数据下的性能如何?
0. Redis从入门到精通系列文章
- 《Redis 从入门到精通【进阶篇】之Lua脚本详解》
- 《Redis 从入门到精通【实践篇】SpringBoot Redis 配置多数据源》
- 《Redis 从入门到精通【进阶篇】三分钟了解Redis地理位置数据结构GeoHash》
- 《Redis 从入门到精通【进阶篇】一文学会Lua脚本》
- 《Redis使用Lua脚本和Redisson来保证库存扣减中的原子性和一致性》
- 《SpringBoot Redis 使用Lettuce和Jedis配置哨兵模式》
- 《Redis【应用篇】之RedisTemplate基本操作》
- 《Redis 从入门到精通【实践篇】之SpringBoot配置Redis多数据源》
- 《Redis 从入门到精通【进阶篇】之三分钟了解Redis HyperLogLog 数据结构》
- 《Redis 从入门到精通【进阶篇】之三分钟了解Redis地理位置数据结构GeoHash》
- 《Redis 从入门到精通【进阶篇】之高可用哨兵机制(Redis Sentinel)详解》
- 《Redis 从入门到精通【进阶篇】之redis主从复制详解》
- 《Redis 从入门到精通【进阶篇】之Redis事务详解》
- 《Redis从入门到精通【进阶篇】之对象机制详解》
- 《Redis从入门到精通【进阶篇】之消息传递发布订阅模式详解》
- 《Redis从入门到精通【进阶篇】之持久化 AOF详解》
- 《Redis从入门到精通【进阶篇】之持久化RDB详解》
- 《Redis从入门到精通【高阶篇】之底层数据结构字典(Dictionary)详解》
- 《Redis从入门到精通【高阶篇】之底层数据结构快表QuickList详解》
- 《Redis从入门到精通【高阶篇】之底层数据结构简单动态字符串(SDS)详解》
- 《Redis从入门到精通【高阶篇】之底层数据结构压缩列表(ZipList)详解》
- 《Redis从入门到精通【进阶篇】之数据类型Stream详解和使用示例》
0. 前言
随着移动互联网的普及,越来越多的应用需要对地理位置信息进行存储和查询,如LBS(Location-Based Service)服务、附近的人功能、地图搜索等。为了满足这些应用的需求,Redis提供了一种地理位置数据结构,称为GeoHash,可以存储和查询地理位置信息。本文将对Redis地理位置数据结构进行详细介绍。
1. 基本介绍
一、什么是Redis地理位置数据结构?
Redis地理位置数据结构是一种基于GeoHash算法实现的数据结构,用于存储和查询地理位置信息。GeoHash算法是一种将地理位置信息映射为一个字符串的算法,可以将一个经纬度坐标转换为一个字符串,这个字符串可以用来比较两个地理位置之间的距离。Redis中的地理位置数据结构支持Point和Member两种数据类型,其中Point表示一个地理位置的经纬度坐标,Member表示一个地理位置,包括名称和经纬度坐标。
二、Redis中的地理位置数据结构命令
Redis提供了以下命令来处理地理位置数据结构:
- GEOADD:将一个或多个地理位置添加到指定的键中。
语法:GEOADD key longitude latitude member [longitude latitude member …]
示例:GEOADD mylocations 116.48105 39.996794 “Beijing” 121.47370 31.23037 “Shanghai”
- GEOPOS:获取一个或多个地理位置的经纬度坐标。
语法:GEOPOS key member [member …]
示例:GEOPOS mylocations “Beijing” “Shanghai”
返回结果:1) 1) “116.48104810762405” 2) “39.99679381454699” 2) 1) “121.4737012386322” 2) “31.23037014274587”
- GEODIST:计算两个地理位置之间的距离。
语法:GEODIST key member1 member2 [unit]
示例:GEODIST mylocations “Beijing” “Shanghai” km
返回结果:“1068.9887”
- 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”
- 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”
三、场景示例
-
附近的商店:一个购物应用程序可以使用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
- 存储商店位置信息:
-
出租车调度:出租车调度系统可以使用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
- 存储出租车位置信息:
-
社交媒体定位:社交媒体平台可以使用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
- 存储用户位置信息:
-
地理围栏提醒:一个提醒应用程序可以使用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
- 存储地理围栏位置信息:
-
路线规划:一个导航应用程序可以使用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"
- 存储道路和交叉口位置信息:
-
旅游推荐:一个旅游应用程序可以使用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
- 存储景点和酒店位置信息:
-
物流管理:一个物流管理系统可以使用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
- 存储仓库和货物位置信息:
-
地理分析:一个地理数据分析应用程序可以使用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. 使用示例
假设我们要开发一个餐厅订餐应用,用户可以根据自己的位置查询附近的餐厅,并进行订餐。
- 数据库设计
首先,我们需要设计数据库表来存储餐厅和订单信息。其中,餐厅表包含餐厅名称、经度和纬度等信息,订单表包含订单号、餐厅名称、用户名称和下单时间等信息。
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
);
- 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;
}
}
- 地理位置数据操作
接下来,我们可以使用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;
}
}
- 订单数据操作
最后,我们可以使用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. 原理解析
-
GeoHash将二维的经纬度转换成字符串,比如下图展示了北京9个区域的GeoHash字符串,分别是WX4ER,WX4G2、WX4G3等等,每一个字符串代表了某一矩形区域。也就是说,这个矩形区域内所有的点(经纬度坐标)都共享相同的GeoHash字符串,这样既可以保护隐私(只表示大概区域位置而不是具体的点),又比较容易做缓存,比如左上角这个区域内的用户不断发送位置信息请求餐馆数据,由于这些用户的GeoHash字符串都是WX4ER,所以可以把WX4ER当作key,把该区域的餐馆信息当作value来进行缓存,而如果不使用GeoHash的话,由于区域内的用户传来的经纬度是各不相同的,很难做缓存。
-
字符串越长,表示的范围越精确。如图所示,5位的编码能表示10平方千米范围的矩形区域,而6位编码能表示更精细的区域(约0.34平方千米)
- 字符串相似的表示距离相近(特殊情况后文阐述),这样可以利用字符串的前缀匹配来查询附近的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数据结构详解,全部内容就是这些。如果你有疑问或见解可以在评论区留言。