目录

让我们回到地理空间数据。什么是Geohash?

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

用于处理地理空间数据的命令

通过地理空间数据读取和搜索呢?

结论


众所周知,处理地理空间数据非常困难,因为纬度和经度是浮点数,应该非常精确。此外,纬度和经度似乎可以表示为网格,但实际上它们不能,仅仅是因为地球不是平的,数学是一门复杂的科学。在本文中,我们将了解Redis如何帮助我们在处理地理空间数据时最大限度地减少计算。

众所周知,处理地理空间数据非常困难,因为纬度和经度是浮点数,应该非常精确。此外,纬度和经度似乎可以表示为网格,但实际上它们不能,仅仅是因为地球不是平坦的,数学很难。

例如,要根据球体上的两点之间的纬度和经度确定大圆的距离,请使用哈弗正弦公式,如下所示:

redis gps redis gps数据存储_Geospatial

与纬度和经度相关的另一个常见任务是查找地球表面半径内的点数。也就是说,给定一个大球(地球),并且您正在尝试找到该球半径内的点。但地球,其实不是一个完美的球体,它仍然是一个椭球体。正如您可能猜到的那样,此类操作的数学计算变得非常复杂。

在本文中,我们将了解Redis如何帮助我们在处理地理空间数据时最大限度地减少计算。

Redis,代表远程字典服务器,是一个快速的开源键值数据存储。由于其速度,Redis是缓存、会话管理、游戏、分析、地理空间数据等的热门选择。

让我们回到地理空间数据。什么是Geohash?

Geohash是一个将坐标表示为字符串的系统。Geohash使用Base32编码将纬度和经度转换为字符串。例如,圣彼得堡宫殿广场的Geohash将如下所示:udtscze2chgq。可变Geohash长度表示可变位置精度,换句话说,Geohash越短,它表示的坐标越不精确。也就是说,较短的Geohash将表示相同的地理位置,但准确性较低。您可以尝试在http://geohash.org 上在Geohash中编码坐标。

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

地理空间数据存储在Redis中使用排序列表(ZSET)作为底层数据结构实现,但对位置数据以及新的API进行动态编码和解码。这意味着使用内置命令:GEOADD,GEODIST,GEORADIUS和GEORADIUSBYMEMBERGEOSEARCH可以将按特定位置的索引,搜索和排序以最少的代码行和最少的工作量投入到Redis中。

Geo Set是在Redis中处理地理空间数据的基础——它是一种旨在管理地理空间索引的数据结构。每个地理集由一个或多个元素组成,每个元素由一个唯一标识符和一对坐标(经度和纬度)组成。

用于处理地理空间数据的命令

要在Redis存储中添加新列表(或向现有列表添加新元素),请使用GEOADD命令。为了清楚起见,我将给出Redis中的命令示例,以及Ruby客户端中用于使用Redis的示例:


# Redis example:
GEOADD "buses" -74.00020246342898 40.717855101298305 "Bus A"

# Ruby example:
RedisClient.geoadd("buses", -74.00020246342898, 40.717855101298305, "Bus A")


这些命令将总线“总线A”的位置坐标添加到名为“总线”的地理集。如果具有此名称的地理集尚未存储在Redis中,则将创建该地理集。仅当列表中尚不存在同名条目(“总线A”)时,才会将新条目添加到索引中。也就是说,总线A是唯一标识符。

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


# Redis example:
GEOADD "buses" -74.00020246342898 40.717855101298305 
"Bus A" -73.99472237472686 40.725856700515855 "Bus B"

# Ruby example:
RedisClient.geoadd("buses", -74.00020246342898, 40.717855101298305, "Bus A",
                           -73.99472237472686, 40.725856700515855, "Bus B")


相同的命令用于更新记录的索引。如果GEOADD调用时带有Geo Set中已经存在的条目,Redis只是更新这些条目的数据,只要总线A开始移动,它的位置就可以更新:


# Redis example:
GEOADD "buses" -76.99265963484487 38.87275545298483 "Bus A"

# Ruby example:
RedisClient.geoadd("buses", -76.99265963484487, 38.87275545298483, "Bus A")

除了添加和更新之外,当然还可以从索引中删除条目。提供ZREM命令以从Redis中的地理集中删除条目。ZREM采用要从中删除记录的索引的名称和要删除的记录的ID:


# Redis example:
ZREM buses "Bus A" "Bus B"

# Ruby example:
RedisClient.zrem("buses", "Bis A", "Bus B")

地理索引可以完全删除,并且由于它存储为Redis密钥,因此可以使用以下DEL命令:


# Redis example:
DEL buses

# Ruby example:
RedisClient.del("buses")

但是,为大列表使用DEL可能是一个坏主意,因为它可以长时间阻止Redis。因此,最好始终使用UNLINK而不是DEL,“非阻塞”删除:


# Redis example:
UNLINK buses

# Ruby example:
RedisClient.unlink("buses")

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


# Redis example:
EXPIRE buses 1000

# Ruby example:
RedisClient.expire("buses", 1000)

Redis采用半延迟过期机制,这意味着索引直到不被读取才过期,如果事实证明读取操作过程中过期时间已经过去了,那么结果不会返回,对象本身也会从存储中删除。也就是说,在我们请求地理集之前,它将无限期地存储在内存中。


但是,Redis具有第二个过期级别——它是主动和随机的。它是一个随机读取不同密钥的垃圾回收器,当读取密钥时,将发生检查过期的标准机制。

遗憾的是,Redis无法直接使索引中的记录过期。这样的功能必须独立开发。

通过地理空间数据读取和搜索呢?

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


# Redis example:
ZRANGE buses 0 -1

# Ruby example:
RedisClient.zrange("buses", 0, -1)

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


# Redis example:
GEOPOS buses "Bus A"

# Ruby example:
RedisClient.geopos("buses", "Bus A")

第二个命令——GEOHASH返回在Geohash中编码的条目的坐标:


# Redis example:
GEOHASH buses "Bus A"

# Ruby example:
RedisClient.geohash("buses", "Bus A")

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


# Redis example:
GEODIST buses "Bus A" "Bus B"

# Ruby example:
RedisClient.geodist("buses", "Bus A", "Bus B", "km")

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


要搜索索引,还使用了GEORADIUS和GEORADIUSBYMEMBER(对于低于6.2的Redis版本)或GEOSEARCH(对于低于6.2的版本)命令。

GEORADIUS和GEORADIUSBYMEMBER接受参数WITHDIST(显示结果 + 与指定点/记录的距离)和WITHCOORD(显示结果 + 记录坐标),以及ASC和DESC排序选项(按与点的距离排序):


# Redis example:
GEORADIUS buses -73 40 200 km WITHDIST

# returns:
1) 1) "Bus A"
  2) "190.4424"
2) 1) "Bus B"
  2) "56.4413"

GEORADIUS buses -73 40 200 km WITHCOORD

# returns:
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

# returns:
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"

# Redis example:
GEORADIUSBYMEMBER buses "Bus A" 100 km

# returns:
1) "Bus B"

# Ruby example:
RedisClient.georadiusbymember("buses", "Bus A", 100, "km")

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


# Redis examples:
GEOSEARCH buses FROMMEMBER "Bus A" BYRADIUS 100 km ASC WITHCOORD WITHDIST WITHHASH
# returns all entries in 100km radius from Bus A with coordinates, 
# distances and geohashes

GEOSEARCH buses FROMLONLAT -74.00020246342898 40.717855101298305" 
BYRADIUS 200 mi DESC COUNT 2
# returns maximum 2 entries sorted from the farest to the closest within 
# 200 miles from the center
# with given coordinates

结论


在Redis中使用地理空间数据实现位置应用程序的简单性不仅可以轻松处理大量地理空间数据,还允许您对数据进行一些复杂的处理。例如,查询半径内的条目可以帮助您实现对附近兴趣点的搜索,方法是仅为用户提供最接近的兴趣点的选项。如果您的应用程序以任何方式使用地理空间数据,请考虑将复杂的计算移动到Redis,这可能会提高应用程序的效率。