提供文章分组需要两个步骤。第一步是添加关于哪些文章属于哪些组的信息,第二步是从一个组中实际获取文章。我们将为每个组使用一个 SET,它存储该组中所有文章的 ID。在清单 1.9 中,我们看到了一个允许我们添加和删除文章组的函数。
def add_remove_groups(conn, article_id, to_add=[], to_remove=[]):
article = 'article:' + article_id
像在 post_article 中一样构造文章信息。
for group in to_add:
conn.sadd('group:' + group, article)
将文章添加到它应该属于的组中。
for group in to_remove:
conn.srem('group:' + group, article)
从文章应该被移除的组中移除文章。
乍一看,这些带有文章信息的 SET 似乎没什么用。到目前为止,您只看到了检查 SET 是否包含一个条目的能力。但 Redis 具有执行涉及多个 SET 的操作的能力,并且在某些情况下,Redis 可以在 SET 和 ZSET 之间执行操作。
当我们浏览一个特定的组时,我们希望能够看到该组中所有文章的分数。或者,实际上,我们希望它们在一个 ZSET 中,以便我们可以对分数进行排序并准备好进行分页。 Redis 有一个名为 ZINTERSTORE 的命令,当提供 SET 和 ZSET 时,它会找到所有 SET 和 ZSET 中都存在的条目,并以几种不同的方式组合它们的分数(SET 中的项目被认为具有等于 1 的分数)。在我们的例子中,我们希望每个项目的最高分数(这将是文章分数或文章发布时间,具体取决于所选择的排序选项)。
为了可视化发生了什么,让我们看看图 1.12。该图显示了一个 ZINTERSTORE 操作的例子,该操作作用于存储为 SET 的一小组文章和更大(但未完全显示)的评分文章 ZSET。注意只有那些同时存在于 SET 和 ZSET 中的文章才会进入结果 ZSET?
要计算组中所有项目的分数,我们只需要使用组和评分或最近的 ZSET 进行 ZINTERSTORE 调用。因为一个组可能很大,所以可能需要一些时间来计算,所以我们将 ZSET 保留 60 秒以减少 Redis 正在做的工作量。如果我们小心(而且我们很小心),我们甚至可以使用我们现有的 get_articles() 函数来处理分页和文章数据获取,这样我们就不需要重写它。我们可以在清单 1.10 中看到从一个组中获取文章页面的函数。
def get_group_articles(conn, group, page, order='score:'):
key = order + group
为每个组和每个排序顺序创建一个键。
if not conn.exists(key):
如果我们最近没有对这些文章进行排序,我们应该对它们进行排序。
conn.zinterstore(key, ['group:' + group, order], aggregate='max',
实际上根据分数或最近时间对组中的文章进行排序。
)
conn.expire(key, 60)
告诉 Redis 在 60 秒后自动过期 ZSET。
return get_articles(conn, page, key)
调用我们之前的 get_articles() 函数来处理分页和文章数据获取。
在某些站点上,文章通常最多只在一个或两个组中(“所有文章”和最适合文章的组)。在这种情况下,将文章所在的组作为文章 HASH 的一部分更有意义,并在我们的 article_vote() 函数的末尾添加另一个 ZINCRBY 调用。但在我们的例子中,我们选择允许文章同时属于多个组(也许一张图片既可以可爱又有趣),因此为了更新多个组中文章的分数,我们需要同时增加所有这些组。对于许多组中的文章,这可能很昂贵,所以我们偶尔会执行一个交集。我们如何选择提供灵活性或限制可能会改变我们在任何数据库中存储和更新数据的方式,Redis 也不例外。
在我们的例子中,我们只计算了对文章投赞成票的人。但在许多网站上,负面投票可以为每个人提供有用的反馈。你能想到一种方法来为 article_vote() 和 post_article() 添加反对票支持吗? 如果可能,尝试允许用户切换他们的投票。 提示:如果您在投票切换方面遇到困难,请查看 SMOVE,我在第 3 章中简要介绍了它。
现在我们可以获取文章、发布文章、对文章进行投票,甚至能够对文章进行分组,我们已经构建了一个用于显示热门链接或文章的后端。 恭喜你走到这一步! 如果您在理解、理解示例或使解决方案工作方面有任何困难,请继续阅读以了解您可以从哪里获得帮助。