使用Redis进行地理位置程序设计建议

14
我正在学习Redis,并正在构建一个用于学习目的的地理程序。我想仅使用Redis存储数据,并尝试避免任何关系数据库。我的问题是如何为程序设计最佳数据库。程序的步骤如下:
1)我将在世界各地创建数百万个随机机器人,它们会漫游,以便它们可以拥有不同的地理坐标(某些机器人可能处于完全相同的空间)。
2)每个机器人都将随机向服务器发送帖子(每几个小时可能平均一次),其中包含: a)机器人发送此数据的位置(以坐标或geohash的形式) b)一些小文本
3)我将有一个带有所有机器人的地图,并希望能够单击机器人并获取此信息: a)所有在我刚才点击的机器人附近发布的帖子
4)由于我将在AWS上托管此内容,因此需要每隔几个小时删除帖子以保持内存使用率较低,因此某种类型的到期是强制性的。
我的主要关注点是性能,我对如何设计Redis数据库感兴趣。
在一天之内(我将计算随机帖子做到这一点)将生成约500,000,000个帖子。
到目前为止,我的想法还不完整:
Idea 1:
1)帖子应存储如下:
`HSET [Geohash of location] [timestamp] [small text] (<-- the value will be used in a later feature to increment the number of manual modification I make to a post).

2) 然后,我可以通过发送机器人所在的geohash位置来获取附近所有帖子。这里的问题是我还需要包括他的8个geohash邻居,这将需要8个以上的查询。这就是为什么我也在探索空间接近度概念来实现此功能。

HGETALL [GeoHash Location of robot] 

这将返回字段([timestamp])和值(“0”);

3)旧帖子的过期。由于我无法使用EXPIRE命令从哈希集中删除字段,因此需要定期扫描所有哈希集字段并查找旧时间戳并将其删除。由于Redis只允许模式搜索,当所有时间戳都不同时,这将是困难的。

想法2:

使用Redis-geo (https://matt.sh/redis-geo)。

1)为了存储帖子,我会运行:

geoadd globalSet [posts_long] [posts_lat] "small text";

2)获取附近机器人的所有帖子信息:

georadius globalSet [robots_long] [robots_lat] [X] km

这将返回所有距离机器人在X公里范围内的帖子。

3)现在我卡在了如何删除旧帖子上。


请注意,从v3.2版本开始,Redis使用GEOADD命令支持地理集合。 - Itamar Haber
正如systemjack所指出的那样,redismodules.com有一个搜索模块。它还有一个随机森林模块来查找最近的邻居。 - Srini Sydney
3个回答

2

好的,让我们分开任务:

  1. 我们需要一个包含所有机器人的索引,以便我们可以遍历它们
  2. 可能我们需要存储一些关于我们的机器人的通用信息
  3. 我们需要为每个机器人存储地理历史记录
  4. 我们需要定期清理旧数据

1) 让我们创建一个 ZSET,其中包含机器人 ID 和他的 SCORE 将是最后活动时间戳,将来我们将能够使用此索引删除非活动机器人。

ZADD ZSET:ROBOTS <timestamp> robot:17

或者更好的方法是只使用 17 而不是 robot:,因为 Redis 会将整数存储为 RAM 中的 4 个字节。

2) 让我们在 HSET 中存储我们的机器人通用信息

HSET HSET:ROBOT:17 name "Best robot ever #17" model "Terminator T-800"

通常我们可以使用几种方法来存储它,例如我们可以使用多维索引技术(多维索引)来采用常规的ZSET,但这很复杂难懂,所以让我们使用更简单的redis GEO。
      GEOADD GEO:ROBOT:17 13.361389 38.115556 "<timestamp>:<message-data>"

在内部,GEO使用常规ZSET,因此我们可以通过“ZRANGE”或“ZRANGEBYSCORE命令”轻松迭代它。
当然,我们也可以使用GEO命令,如GEORADIUS,以满足我们的需求。
4)清理过程。我建议按时间进行清理,但您也可以按条目数量进行清理,只需使用“ZRANGE”而不是“ZRANGEBYSCORE”。
让我们找到所有非活动机器人,这些机器人至少一周没有活动了。
ZRANGEBYSCORE ZSET:ROBOTS -inf <timestamp-of-week-before>

现在我们需要遍历这些ID,并删除不需要的HSET、GEO键,并将其从我们的index中删除。
ZREM ZSET:ROBOTS 17
DEL HSET:ROBOT:17
DEL GEO:ROBOT:17

现在我们只需要删除旧的GEO历史记录,就像我之前说的那样,Redis中的GEO是一个常规的ZSET,所以让我们使用ZRANGE。
ZRANGE GEO:ROBOT:17 0 -1

我们将获得条目列表,但由于GEO的存在,它们将以奇怪的方式排序,每个“score”都将是“GEO位置”。
我们的条目格式为“:”,因此我们可以使用“split(':')”来比较时间戳,如果太旧,我们将删除它。例如,我们的时间戳是“12345678”,消息是“hello”。
ZDEL GEO:ROBOT:17 1234567:hello

附言:我强烈推荐您阅读关于redis中ZSET的精彩文章。
简而言之:Redis不仅按分数排序项目,还按键名排序,这意味着具有相同分数的条目将按字母顺序排序,这非常有用!
ZADD key 0 ccc 0 bbb 0 aaa
ZRANGE key 0 -1

将返回已排序的集合:
 1. "aaa"
 2. "bbb"
 3. "ccc"

0

让我根据我理解的问题给你一个想法:

不要将值存储在哈希表中,而是将所有内容都存储在Redis中。 构建键为GeoLocation:[机器人的Geohash位置]:1[表示帖子数量,每当有新请求时就会增加]:时间戳,值为时间戳。 同样,对于小文本GeoLocation:[机器人的Geohash位置]:1[表示帖子数量]:smallText。 使用set expire设置值,并将过期时间设置为您所需的时间。

例如:setex GeoLocation:12.31939:1:timestamp 1432423232(时间戳)14400(4小时) setex GeoLocation:12.31939:1:smalltext ronaldo 14400

这样,您将获得来自所有机器人的任意数量的帖子,具有独特的键来访问和设置过期已变得容易。

现在,要获取特定机器人发布的所有信息,请使用键GeoLocation:(特定机器人的位置):*并获取每个值。

通过这种方式,您无需扫描Redis中的所有键。您将更快地获取信息,并且键会自动过期。


谢谢回复!但我不明白如何检索键。如果要使用类似GeoLocation:(特定机器人位置):*的东西,我需要使用SCAN命令才能进行模式匹配,对吗?如果是这样,那么这个命令不是O(N)吗?那么我不是必须要扫描Redis中的所有键吗? - user2924127
让我用Java(Jedis)给你一个想法。Set <String> keys=jedis.keys("GeoLocation:"+(机器人位置)+"*"); 这将返回与特定机器人匹配的所有键。然后遍历该集合以获取所需的帖子和时间戳。这将是O(M)而不是O(N)。因为我们只循环匹配特定机器人的键。因此,您将收到特定机器人的所有帖子。 - Karthikeyan Gopall
目的是尝试获取附近其他机器人发布的所有帖子,而不是特定机器人发布的帖子。 - user2924127
查找靠近特定机器人的位置,并执行上述相同的过程。 - Karthikeyan Gopall
很抱歉,我不理解你的意思。假设我有一个机器人在geohash ABCDE处。现在我想获取他周围的所有帖子。所以你是说要使用带通配符的Keys命令来匹配模式。Keys命令的时间复杂度为O(N) http://redis.io/commands/KEYS。如果你能够从头到尾地编写jedis,也许我就能更好地理解你的意思和尝试做什么了。 - user2924127

0

从描述中我得到的一个想法是,你会知道给定“机器人”的当前位置,并希望实时找到其他靠近它的移动用户,但你也需要一些历史位置信息。

我认为Redis提供了用于构建更高级数据库的原始构件。基于此,你意识到需要构建和维护自己的高级数据库功能,如索引等。

由于这种类型的数据主要在你有特定机器人要查找时访问,建议将机器人的位置历史记录和元数据存储在以机器人唯一标识符而不是其位置为基础的关键字中。

然后通过管理其ID所在的集合或散列来维护其相对位置(或任何其他分组)与其他机器人的关系。你可以使用多个集合或嵌套数据结构来实现一定程度的详细级别能力。

通过更新机器人记录和位置信息来保持数据完整性,这是redis事务的一部分。为了提高效率,可以使用批处理技术

对于旧帖子,您不需要使用expire,因为您可以通过限制机器人主记录中历史条目的数量来管理数据库大小。当您要更新机器人时,只需在其长度超过一定长度(llen、slen、hlen等)时进行某种清理操作,以便为您提供可预测/可调整的聚合数据大小。

如果您所做的事情有任何希望成为生产力,我强烈建议从一开始就考虑partitioning。任何成功的水平都需要它,所以最好从一开始就这样做。相信我。对于这种情况,我会按功能(位置与机器人状态...不同的数据库在不同的复制组上)以及按键(哈希或其他...将您的500M分解成合理的块)进行分区。

分区使事务变得棘手,但对于您的用例,我认为这不是一个交易破坏者。使用redis messaging与事务结合使用,可以通过编程方式执行各种更新,从而保持完整性。

最后,我会考虑除了redis之外的其他选择(在你的情况下我假设是elasticache)。在支持并发和能够执行复杂查询的范围内,redis非常适合前者。因此,它非常适合跟踪会话或类似状态。

你需要很多并发,但大多数情况下只是追加而不是更新。所以不像一个不断演变的状态机。而且你至少需要一些搜索能力。

如果你需要将对象相互关联(查询),支持分析等等,那么有500M用户的话,你可以负担得起一个大的redshift集群、dynamo或类似的东西。可以在前面放置kinesis来帮助并发处理,通过批量加载小消息来进行批处理。从kinesis到redshift和dynamo都有特殊的加载路径。

一定要避免使用RDS,但还有其他选项可以更简单地实现,并帮助你避免不可避免的那一天,即你必须迭代你的redis集群(对于这个你显然会使用scan)。

(虽然是旧帖子,但是问题很有趣,答案适用于许多情况。)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接