java redis 自增计数器_Redis之如何对文章进行投票、获取、分组

1.对文章进行投票

要构建一个文章投票网站,我们首先要做的就是为了这个网站设置一些数值和限制条件:如 果一篇文章获得了至少200张支持票(up vote ),那么网站就认为这篇文章是一篇有趣的文章; 假如这个网站每天发布1000篇文章,而其中的50篇符合网站对有趣文章的要求,那么网站要做 的就是把这50篇文章放到文章列表前100位至少一天;另外,这个网站暂时不提供投反对票 (down vote )的功能 °

为了产生一个能够随着时间流逝而不断减少的评分,程序需要根据文章的发布时间和当前时 间来计算文章的评分,具体的计算方法为:将文章得到的支持票数量乘以一个常量,然后加上文 章的发布时间,得出的结果就是文章的评分。

我们使用从UTC时区1970年1月1日到现在为止经过的秒数来计算文章的评分,这个值通 常被称为Unix时间。之所以选择使用Unix时间,是因为在所有能够运行Redis的平台上面,使 用编程语言获取这个值都是一件非常简单的事情。另外, 计算评分时与支持票数量相乘的常量为432,这个常量是通 过将一天的秒数(86 400 )除以文章展示一天所需的支持票 数量(200)得出的:文章每获得一张支持票,程序就需要 将文章的评分增加432分。

构建文章投票网站除了需要计算文章评分之外,还需要 使用Redis结构存储网站上的各种信息。对于网站里的每篇 文章,程序都使用一个散列来存储文章的标题、指向文章的 网址、发布文章的用户、文章的发布时间、文章得到的投票 数量等信息,图1-8展示了一个使用散列来存储文章信息的 例子。

9cfb8f5758344cf44a2c320d4003e70c.png

使用冒号作为分隔符 本书使用冒号(:)来分隔名字的不同部分:比如图1・8里面的键名 article:92617就使用了冒号来分隔单词article和文章的ID号92617,以此来构建命名 空间(namespace )0使用:作为分隔符只是我的个人喜好,不过大部分Redis用户也都是这么做的, 另外还有一些常见的分隔符,如句号(.)、斜线(/),有些人甚至还会使用管道符号(I )。无论 使用哪个符号来做分隔符,都要保持分隔符的一致性。同时,请读者注意观察和学习本书使用冒号 创建嵌套命名空间的方法。

我们的文章投票网站将使用两个有序集合来有序地存储文章:第一个有序集合的成员为文 章ID,分值为文章的发布时间;第二个有序集合的成员同样为文章1D,而分值则为文章的评分。 通过这两个有序集合,网站既可以根据文章发布的先后顺序来展示文章,又可以根据文章评分的 高低来展示文章,图1・9展示了这两个有序集合的一个示例

814a73a5ca1b4cb3889badee1a8d9e27.png

为了防止用户对同一篇文章进行多次投票,网站需要为每篇文章记录一个已投票用户名单。此,程序将为每篇文章创建一个集合,并使用这个集合 来存储所有已投票用户的ID,图1.10展示了一个这样的集 合示例。

了尽量节约内存,我们规定当一篇文章发布期满 一周之后,用户将不能再对它进行投票,文章的评分将 被固定下来,而记录文章已投票用户名单的集合也会被 删除。

在实现投票功能之前,让我们来看看图Ml:这幅图 展示了当115423号用户给100408号文章投票的时候,数 据结构发生的变化。

20949f1c6b4f248e26d20de5d92eb1eb.png

bc425e1e0803f3943889d40e7a81e5e3.png

既然我们已经知道了网站计算文章评分的方法,也知道了网站存储数据所需的数据结构, 那么现在是时候实际地实现这个投票功能了!当用户尝试对一篇文章进行投票时,程序需要使 用ZSCORE命令检查记录文章发布时间的有序集合,判断文章的发布时间是否未超过一周。如 果文章仍然处于可以投票的时间范围之内,那么程序将使用SADD命令,尝试将用户添加到记 录文章已投票用户名单的集合里面。如果添加操作执行成功的话,那么说明用户是第一次对这 篇文章进行投票,程序将使用ZINCRBY命令为文章的评分增加432分(ZINCRBY命令用于对 有序集合成员的分值执行自增操作),并使用HINCRBY命令对散列记录的文章投票数量进行更 新(HINCRBY命令用于对散列存储的值执行自增操作),代码清单1-6展示了投票功能的实现 代码。

1476719ba6e14d1d1db1b23ea1a0c10b.png

Redis事务 从技术上来讲,要正确地实现投票功能,我们需要将代码清单1-6里面的sadd、 ZINCRBY和HINCRBY这3个命令放到一个事务里面执行,不过因为本书要等到第4章才介绍Redis 事务,所以我们暂时忽略这个问题。

这个投票功能还是很不错的,对吧?那么发布文章的功能要怎么实现呢?

2.发布并获取文章

发布一篇新文章首先需要创建一个新的文章ID,这项工作可以通过对一个计数器(counter) 执行INCR命令来完成。接着程序需要使用SADD将文章发布者的ID添加到记录文章已投票用 户名单的集合里面,并使用EXPIRE命令为这个集合设置一个过期时间,让Redis在文章发布期 满一周之后自动删除这个集合。之后,程序会使用HMSET命令来存储文章的相关信息,并执行 两个ZADD命令,将文章的初始评分(initial score )和发布时间分别添加到两个相应的有序集合 里面。代码清单1・7展示了发布新文章功能的实现代码。

f6a93aad02ce5182fe1db03fdc33df65.png

好了,我们已经陆续实现了文章投票功能和文章发布功能,接下来要考虑的就是如何取出评分 最高的文章以及如何取出最新发布的文章了。为了实现这两个功能,程序需要先使用ZREVRANGE 命令取出多个文章ID,然后再对每个文章ID执行一次HGETALL命令来取出文章的详细信息,这个 方法既可以用于取出评分最高的文章,又可以用于取出最新发布的文章。这里特别要注意的一点是, 因为有序集合会根据成员的分值从小到大地排列元素,所以使用ZREVRANGE命令,以“分值从大 到小”的排列顺序取岀文章ID才是正确的做法,代码清单1.8展示了文章获取功能的实现函数。

68a26679ea3b18e5241fd5c1907e5e42.png

虽然我们构建的网站现在已经可以展示最新发布的文章和评分最高的文章了,但它还不具备 目前很多投票网站都支持的群组(group)功能:这个功能可以让用户只看见与特定话题有关的 文章,比如与“可爱的动物”有关的文章、与“政治”有关的文章、与“Java编程”有关的文章 或者介绍“Redis用法”的文章等等。接下来的一节将向我们展示为文章投票网站添加群组功能 的方法。

3.对文章进行分组

群组功能由两个部分组成,一个部分负责记录文章属于哪个群组,另一个部分负责取出群组 里面的文章。为了记录各个群组都保存了哪些文章,网站需要为每个群组创建一个集合,并将所 有同属一个群组的文章ID都记录到那个集合里面。代码清单1-9展示了将文章添加到群组里面 的方法,以及从群组里面移除文章的方法。

2d2ac21fda5cd58dc10ed973ad14c735.png

初看上去,可能会有读者觉得使用集合来记录群组文章并没有多大用处。到目前为止,读者 只看到了集合结构检查某个元素是否存在的能力,但实际上Redis不仅可以对多个集合执行操作, 甚至在一些情况下,还可以在集合和有序集合之间执行操作。

为了能够根据评分对群组文章进行排序和分页(paging),网站需要将同一个群组里面的所 有文章都按照评分有序地存储到一个有序集合里面。Redis的ZINTERSTORE命令可以接受多 个集合和多个有序集合作为输入,找出所有同时存在于集合和有序集合的成员,并以几种不同 的方式来合并(combine )这些成员的分值(所有集合成员的分值都会被视为是1 )。对于我们 的文章投票网站来说,程序需要使用ZINTERSTORE命令选出相同成员中最大的那个分值来作 为交集成员的分值:取决于所使用的排序选项,这些分值既可以是文章的评分,也可以是文章 的发布时间。

图1-12展示了对一个包含少量文章的群组集合和一个包含大量文章及评分的有序集合执行 ZINTERSTORE命令的过程,注意观察那些同时出现在集合和有序集合里面的文章是怎样被添加 到结果有序集合里面的。

c0d0cf3cc2f3dc7e01920e2487e26ef4.png

通过对存储群组文章的集合和存储文章评分的有序集合执行ZINTERSTORE命令,程序可以 得到按照文章评分排序的群组文章;而通过对存储群组文章的集合和存储文章发布时间的有序集 合执行ZINTERSTORE命令,程序则可以得到按照文章发布时间排序的群组文章。如果群组包含 的文章非常多,那么执行ZINTERSTORE命令就会比较花时间,为了尽量减少Redis的工作量, 程序会将这个命令的计算结果缓存60秒。另外,我们还重用了已有的get_articles()函数来 分页并获取群组文章,代码清单1-10展示了网站从群组里面获取一整页文章的方法。

691547a08bf10b6ced48aae54b088d84.png

有些网站只允许用户将文章放在一个或者两个群组里面(其中一个是“所有文章”群组, 另一个是最适合文章的群组)。在这种情况下,最好直接将文章所在的群组记录到存储文章信 息的散列里面,并在article_vote ()函数的末尾增加一个ZINCRBY命令调用,用于更新 文章在群组中的评分。但是在这个示例里面,我们构建的文章投票网站允许一篇文章同时属 于多个群组(比如一篇文章可以同时属于“编程”和“算法”两个群组),所以对于一篇同时 属于多个群组的文章来说,更新文章的评分意味着程序需要对文章所属的全部群组执行自增 操作。在这种情况下,如果一篇文章同时属于很多个群组,那么更新文章评分这一操作可能 会变得相当耗时,因此,我们在get_group_articles()函数里面对ZINTERSTORE命令 的执行结果进行了缓存处理,以此来尽量减少ZINTERSTORE命令的执行次数。开发者在灵 活性或限制条件之间的取舍将改变程序存储和更新数据的方式,这一点对于任何数据库都是 适用的,Redis也不例外。

练习:实现投反对票的功能

我们的示例目前只实现了投支持票的功能,但是在很多实际的网站里面,反对票也能给用户提供 有用的反馈信息。因此,请读者能想办法在article_vote ()函数和post_article ()函数里面添 加投反对票的功能。除此之外,读者还可以尝试为用户提供对调投票的功能:比如将支持票转换成反 对票,或者将反对票转换成支持票。提示:如果读者在实现对调投票功能时出现了困难,可以参考一 下第3章介绍的SMOVE命令。

好的,现在我们已经成功地构建起了一个展示最受欢迎文章的网站后端,这个网站可以获取 文章、发布文章、对文章进行投票甚至还可以对文章进行分组。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值