使用Redis处理地理空间数据的高效方法

在处理地理空间数据时,经常会遇到需要计算两个点之间的距离、在地球表面查找特定半径内的点等复杂问题。由于地球是一个不规则的椭球体,这些计算变得相当复杂。本文将探讨如何利用Redis来简化这些计算过程。

什么是Geohash?

Geohash是一种将坐标表示为字符串的系统。它使用Base32编码将纬度和经度转换为字符串。例如,圣彼得堡宫殿广场的Geohash看起来像这样:udtscze2chgq。Geohash的长度可变,代表了不同的定位精度,即Geohash越短,它所代表的坐标精度就越低。换句话说,较短的Geohash将代表相同的地理位置,但精度较低。可以在尝试编码坐标。

Redis如何存储地理空间数据?

Redis使用有序集合(ZSET)作为底层数据结构来实现地理空间数据的存储,但同时提供了实时编码和解码位置数据以及新的API。这意味着使用内置命令进行索引、搜索和按特定位置排序可以非常轻松地完成,只需几行代码:

  • GEOADD:添加地理空间数据
  • GEODIST:计算两个点之间的距离
  • GEORADIUS:根据给定的中心点和半径查找点
  • GEORADIUSBYMEMBER(GEOSEARCH):根据成员名称查找点

如何使用Redis命令操作地理空间数据

要向Redis存储中添加新的列表(或现有列表中的新元素),可以使用GEOADD命令。以下是Redis命令和Ruby客户端操作Redis的示例:

# Redis示例: GEOADD "buses" -74.00020246342898 40.717855101298305 "Bus A" # Ruby示例: RedisClient.geoadd("buses", -74.00020246342898, 40.717855101298305, "Bus A")

这些命令将向名为"buses"的Geo Set添加公交车"Bus A"的位置坐标。如果Redis中尚未存储此名称的Geo Set,则会创建一个。如果列表中已存在相同名称("Bus A")的条目,则不会添加新条目。也就是说,"Bus A"是一个唯一标识符。

也可以使用单个GEOADD调用来一次添加多个记录,这有助于减少网络和数据库负载。记录ID必须是唯一的:

# Redis示例: GEOADD "buses" -74.00020246342898 40.717855101298305 "Bus A" -73.99472237472686 40.725856700515855 "Bus B" # Ruby示例: RedisClient.geoadd("buses", -74.00020246342898, 40.717855101298305, "Bus A", -73.99472237472686, 40.725856700515855, "Bus B")

相同的命令也用于更新记录的索引。如果GEOADD被调用时Geo Set中已有条目,Redis将简单地更新这些条目的数据,例如,当公交车A开始移动时,其位置可以被更新:

# Redis示例: GEOADD "buses" -76.99265963484487 38.87275545298483 "Bus A" # Ruby示例: RedisClient.geoadd("buses", -76.99265963484487, 38.87275545298483, "Bus A")

除了添加和更新,当然也可以从未索引中删除条目。ZREM命令用于从Redis的Geo Set中删除条目。ZREM接受要删除记录的索引名称和要删除的记录ID:

# Redis示例: ZREM buses "Bus A" "Bus B" # Ruby示例: RedisClient.zrem("buses", "Bus A", "Bus B")

可以完全删除地理索引,由于它作为Redis键存储,可以使用DEL命令:

# Redis示例: DEL buses # Ruby示例: RedisClient.del("buses")

但是,对于大列表使用DEL可能不是一个好主意,因为它可能会长时间阻塞Redis。因此,最好总是使用UNLINK而不是DEL,即'非阻塞'删除:

# Redis示例: UNLINK buses # Ruby示例: RedisClient.unlink("buses")

请记住,Redis有一个索引过期机制,如果不为索引指定过期日期,那么它将永远不会过期,并且会占用内存。为了防止这种情况发生,需要使用EXPIRE命令,传递索引的名称和过期秒数:

# Redis示例: EXPIRE buses 1000 # Ruby示例: RedisClient.expire("buses", 1000)

Redis使用半延迟过期机制,这意味着索引在未被读取之前不会被过期,如果在读取操作期间发现过期时间已过,则结果不会被返回,对象本身将从存储中删除。也就是说,直到请求Geo Set,它将无限期地存储在内存中。

如何读取和搜索地理空间数据?

有几种方法可以从索引中读取条目。可以使用ZRANGE和ZSCAN命令开始。这些命令遍历索引中的所有条目。例如,返回索引中的所有条目:

# Redis示例: ZRANGE buses 0 -1 # Ruby示例: RedisClient.zrange("buses", 0, -1)

对于地理空间数据,有两个命令可以从索引中获取条目的位置。第一个是GEOPOS命令,返回索引中条目的坐标:

# Redis示例: GEOPOS buses "Bus A" # Ruby示例: RedisClient.geopos("buses", "Bus A")

第二个命令GEOHASH返回条目编码为geohash的坐标:

# Redis示例: GEOHASH buses "Bus A" # Ruby示例: RedisClient.geohash("buses", "Bus A")

要获取索引中两个条目之间的距离,可以使用GEODIST命令:

# Redis示例: GEODIST buses "Bus A" "Bus B" # Ruby示例: RedisClient.geodist("buses", "Bus A", "Bus B", "km")

命令的结果默认以米为单位返回。可以通过将第四个参数传递给命令来指定所需的测量单位,例如:km表示公里,m表示米,mi表示英里,ft表示英尺。

要搜索索引,也使用GEORADIUS和GEORADIUSBYMEMBER(对于Redis版本小于6.2)或GEOSEARCH(对于版本大于6.2)命令。GEORADIUS和GEORADIUSBYMEMBER接受WITHDIST(显示结果+指定点/记录的距离)和WITHCOORD(显示结果+记录坐标)参数,以及ASC或DESC排序选项(按距离点排序):

# Redis示例: GEORADIUS buses -73 40 200 km WITHDIST # 返回: 1) 1) "Bus A" 2) "190.4424" 2) 1) "Bus B" 2) "56.4413" GEORADIUS buses -73 40 200 km WITHCOORD # 返回: 1) 1) "Bus A" 2) 1) "-74.00020246342898" 2) "40.717855101298305" 2) 1) "Bus B" 2) 1) "-73.99472237472686" 2) "40.725856700515855" GEORADIUS buses -73 40 200 km WITHDIST WITHCOORD # 返回: 1) 1) "Bus A" 2) "190.4424" 3) 1) "-74.00020246342898" 2) "40.717855101298305" 2) 1) "Bus B" 2) "56.4413" 3) 1) "-73.99472237472686" 2) "40.725856700515855" GEORADIUSBYMEMBER buses "Bus A" 100 km # 返回: 1) "Bus B" # Ruby示例: RedisClient.georadiusbymember("buses", "Bus A", 100, "km")

对于新版本的Redis,GEOSEARCH命令具有类似的语法并执行相同的操作。命令语法如下:

# Redis示例: GEOSEARCH buses FROMMEMBER "Bus A" BYRADIUS 100 km ASC WITHCOORD WITHDIST WITHHASH # 返回所有距离Bus A 100km范围内的条目,包括坐标、距离和geohashes GEOSEARCH buses FROMLONLAT -74.00020246342898 40.717855101298305 BYRADIUS 200 mi DESC COUNT 2 # 返回最多2个条目,从最远到最近排序,距离中心200英里,给定坐标
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485