从Redis中读取多个哈希的最快方法是什么?

3

我有一个包含指向一些HASH数据的指针的LIST。类似这样:

[LIST] app:1 ["article1", "article2", "article3" ...]
[HASH] article1 {title: "Hello", description: "World"}
[HASH] article2 {title: "Hello", description: "World"}
[HASH] article3 {title: "Hello", description: "World"}

当收到这个请求时:

api/v1/app/1/articles/20

我做以下工作:

$pointers = $this->redis->lrange($appID, 0, $request->articles);
$articles = [];

foreach($pointers as $pointer) {
   $articles[] = $this->redis->hgetall($pointer);
}

所以最终我需要:1次lrange调用,然后是$request->articles数量的hgetall调用。请问什么是最快的解决方案?
我考虑了以下几个方面:
  1. 使用HMGET

  2. 使用MULTI/EXEC

  3. 使用LUA编写此功能,并将其放入单个命令中。

有什么想法吗?
2个回答

3
如果你只是存储文章数据,我建议你将每个文章属性存储在一个文章哈希表中,但是你应该构建一个单一的哈希表,其中键应该是文章标识符,而值应该是一个JSON序列化对象字符串。
通常情况下,当你需要访问某些对象的特定属性时,你会使用哈希表,但我猜你获取这些文章是为了在某个UI中列出它们,所以没有理由为每篇文章使用一个哈希表。无论如何,哈希表和所有文章的JSON哈希表可以共存:对于需要访问特定文章属性而不获取整个对象的情况,可以使用哈希表;对于获取整个对象或列出对象的情况,可以使用所有文章的哈希表。
想象一下,使用这种方法你可以避免多少次对Redis的调用。你可以从列表中获取所有文章标识符,然后使用单个hmget命令在一次请求中获取所有文章。由于你使用lrange,我理解你不会获取所有文章,但你使用分页。
你的API获取所有JSON对象作为字符串,并直接将它们返回给API客户端。
关于你的API资源URI的一些关注点:
我检查了你的语句:
在REST中,articles/20意味着“按id获取第20篇文章”,而不是“获取20篇文章”。
让我向你建议两种方法来处理范围问题:
使用查询字符串:api/v1/app/1/articles?startFrom=0&max=20(参数名称只是我的建议...)。
使用HTTP头。你可以在请求中发送一个HTTP头,比如MyApi-Range: 0 20,其中0是起始位置,20是最大页面大小(即最大结果)。
更新:一些关于这种方法的细节。
OP在某个评论中说:
我们每次只保留20篇文章。因此,当应用程序推送新文章时,最后一篇文章从列表中删除,新文章添加到列表的左侧。然后我们删除artice:{ID}哈希表。使用你的解决方案,我需要读取JSON序列化字符串,删除article:{ID}属性,添加新的属性,然后保存它(并覆盖以前的键)。后端需要进行更多的工作。除了将它们保留为JSON序列化之外,还有没有其他更快的方法来获取这些哈希表?我知道LUA可以帮助Redis执行一个命令,但我不确定Redis的负载是否会保持不变。
我的方法是:
文章存储在哈希表articles中,其中键是文章id,值是JSON序列化的文章对象。
[1] => {title: "Hello", description: "World"}
[2] => {title: "Hello 2", description: "World 2"}
....
  • 另外,您应该保持插入顺序,将文章ID添加到名为articles:ids的列表中:

    [1, 2]

  • 当您想要存储新文章时,您需要对文章对象进行序列化,并使用hset将其添加到articles哈希表中,同时使用lpush将文章ID添加到articles:ids列表中。请使用MULTI命令确保操作以原子方式完成!

  • 如果您想按插入顺序获取文章,则需要获取articles:ids文章ID,并使用hmget获取所有文章。

  • 当有20篇文章时,如您在评论中所说,您需要使用rpop命令获取articles:id中最新的文章ID,并使用hdel命令从articles哈希表中删除文章对象。请使用MULTI命令确保操作以原子方式完成!

更新2:一些澄清

OP说:

我将如何使用HMGET检索文章? 当哈希包含约一百万个键时,它会扩展得有多好?

关于如何使用hmget检索文章,它很容易:您获取列表项(可能使用lrange),并将所有获得的ID作为hmget的参数以从哈希表中获取整个文章。

关于哈希在具有数百万个键时的扩展性如何,需要注意的是hget时间复杂度为O(1),因此这意味着键的数量不会影响访问时间,而hmget(因为它是一个“哈希多个获取”)是O(n),因为访问时间增加了被获取的键的数量(而不是散列中存储的总键数)。

顺便说一下,由于Redis 3.x是稳定版本,并且由于Redis Cluster提供了可大大提高可伸缩性的改进,因此您应该了解更多有关此新功能以及在大数据集的情况下如何分片的信息。


所以你是告诉我要将所有文章作为一个序列化的JSON对象保存在单个字符串中?很遗憾,我觉得这不可能发生。因为我们保留了大约20篇文章,当我将新文章推入列表时,我会删除最后一篇文章。如果我将它们保存为序列,则需要每次更新时重新编写它,否则序列中将出现垃圾值。此外,最初我选择了这种结构,因为它更有组织性,这也是哈希的目的。我认为这比每次返回一个长的JSON序列更快。 - Aristona
关于终端点,请忽略它。 :) 实际上,这是一个POST终端点,用于监听这些请求,但必须这样做以避免我们在CORS /旧版本jQuery / URI中使用Unicode字符时遇到的一些问题。无论如何,这是一个内部API,没有公共访问权限。 - Aristona
在我看来,Aristona,我认为你的方法有误。你不需要从 JSON 中删除“id”属性。你只需要将整个 JSON 删除,然后将新文章 JSON 存储为哈希表中的新键。使用 Lua 在这里毫无帮助... - Matías Fidemraizer
@Aristona,请查看我的回答更新,以获取有关方法的更多详细信息... - Matías Fidemraizer
2
@Aristona 欢迎您,也欢迎来到令人兴奋的 Redis 世界 :) - Matías Fidemraizer
显示剩余6条评论

0

将您的哈希键从article1更改为app1:article1


我无法这样做,我需要以最快的方式跟踪文章/应用程序关系。否则Redis将扫描整个数据库,因为我无法执行类似于HGETALL app1的操作,除非您说的是另一种方法。 - Aristona
好的,我误解了你的问题。 - halil

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