上一篇文章: Python--Redis实战:第一章:初识Redis:第二节:Redis数据结构简介
下一篇文章: Python--Redis实战:第二章:使用Redis构建Web应用:第一节:登录和cookie缓存
在对Redis提供的5种结构有了基本的了解后,现在是时候来学习一下怎样使用这些结构来解决实际问题了。
最近几年,越来越多的网站开始提供对网页链接、文章或者稳赢进行投票的功能,这些网站会根据文章的发布时间和文章获得的投票数量计算出一个评分,然后根据这个评分来决定如何排序和展示文章。
本节将展示如何使用Redis来构建一个简单的文章投票网站的后端。
1、对文章进行投票
要构建一个文章投票网站,我们首先要做的就是为了这个网站设置一些数值和限制条件:如果一篇文章获得了至少200张支持票,那么久认为这篇文章是【有趣的】。假如这个网站每天发布1000篇文章,而其中的50篇符合网站对【有趣】文章的要求,那么网站要做的就是把这50篇文章放到文章放到文章列表前100位至少一天;另外,这个网站暂时不提供投反对票的功能。
为了产生一个能够随着时间流逝而不断减少的评分,程序需要根据文章的发布时间和当前时间来计算文章的评分,具体的计算方法为:将文章得到的支持票数乘以一个常量,然后加上文章的发布时间,得出的结果就是文章的评分。
我们使用从UTC时区1970年1月1日到现在为止所经过的秒数来计算文章的评分,这个值通常被成为Unix时间。之所以选择使用Unix时间,是因为所有能够运行Redis的平台上面,使用编程语言获取这个值都是非常简单的事情。另外,计算评分时与支持数量相乘的常量为432,这个常量是通过将一天的秒数(86400)除以文章展示一天所需的支持票数量(200得出的):文章没获得一张支持票,程序就需要将程序的评分增加432分。
构建文章投票网站除了需要计算文章评分之外,还需要使用Redis结构存储网站上的各种信息。对于网站里的每一篇文章,程序都使用了一个散列来存储文章的标题、指向文章的网址、发布文章的用户、文章的发布时间、文章得到的投票数量等信息。
我们的文章投票网站将使用两个有序集合来存储文章:
- 第一个有序集合的成员为:文章ID、文章的发布时间,该有序集合可以使网站按照发布时间先后展示文章
- 第二个有序集合的成员为:文章ID、文章的评分,该有序集合可以使网站按照评分高低展示文章。
为了防止用户对同一篇文章进行多次投票,网站需要为每一篇文章记录一个已投票用户名单。为此,需要创建一个存储所有已经投票的用户ID。
为了尽量节约内存,我们规定当一篇文章发布期满一周之后,用户将不能再对它进行投票,文章的评分将被固定下来,而积累文章已经投票用户名单的集合会被删除。
既然我们已经知道了网站计算文章评分的方法,也知道了网站存储数据所需的数据结构,那么现在是时候实际的实现这个投票功能了!
- 当用户尝试对一篇文章进行投票时,程序需要使用zscore命令来检查记录文章发布时间的有序集合,判断文章的发布时间是否未超过一周。
- 如果文章仍然处于可以投票的时间范围之内,那么程序将使用sadd命令,尝试将用户添加到记录文章已经投票用户名单集合里。
- 如果添加操作执行成功的话,那么说明用户是第一次对这篇文章进行投票,程序将使用zincrby命令为文章的评分增加432分(zincrby命令用于对有序集合成员的分值进行自增操作),并使用hincrby命令对散列记录的文章投票数量进行更新(hincrby命令用于对散列存储的值执行自增操作)。
投票功能实现代码:
import time
ONE_WEEK_IN_SECONDS=7*86400 #一周秒数
VOTE_SCORE=432 #点赞一次增加的分值
#投票
def article_vote(conn,user,article):
cutoff=time.time()-ONE_WEEK_IN_SECONDS
#提示:本案例使用冒号作为分隔符
if conn.zscore('time:',article)<cutoff:
#判断文章发布时间是否已经超过七天
return
article_id=article.partition(':')[-1]
if conn.sadd('voted:'+article_id,user):
#如果用户是第一次为文章投票,那么增加这篇文章的投票数量和评分
conn.zincrby('score:',article,VOTE_SCORE)
conn.hincrby(article,'votes',1)
2、发布并获取文章
- 发布一篇文章首先需要创建一个新的文章ID,这项工作可以通过对一个计数器(counter)执行incr命令来完成。
- 接着程序需要使用sadd将文章发布者的ID添加到记录文章已投票用户名单的集合里面,并使用expire命令为这个集合设置一个过期时间,让Redis在文章发布期满一周之后自动删除这个集合。
- 之后,程序会使用hmset命令来存储文章的相关信息,并执行两个zadd命令,将文章的初始评分(initial score)和发布时间分别添加到两个相应的有序集合里面。
#发布文章
def post_article(conn,user,title,link):
article_id=str(conn.incr('article:'))
voted='voted:'+article_id
conn.sadd(voted,user)
conn.expire(voted,ONE_WEEK_IN_SECONDS)
now=time.time()
article='article:'+article_id
conn.hmset(article,{
'title':title,#文章标题
'link':link,#文章链接
'poster':user,
'time':now,
'votes':1
})
conn.zadd('score:',article,now+VOTE_SCORE)
conn.zadd('time',article,now)
return article_id
好了,我们已经陆续实现了文章的投票功能和文章发布功能,接下来要考虑的就是如何取出评分最高的文章已经如何取出最新发布的文章。
为了实现这两个功能,
- 首先需要使用zrevrange命令取出多个文章ID
- 然后再对每个文章ID执行一次hgetall命令来取出文章的详细信息。
这个方法既可以用于取出评分最高的文章,又可以用于取出最新发布的文章。
注意:因为有序集合根据成员的分值从小到大排列,所以使用zrevrange命令,以【分值从大到小】的排列顺序取出文章ID才是正确的做法。
ARTICLES_PER_PAGE=25 #每页展示数量
def get_articls(conn,page,order='score:'):
start=(page-1)*ARTICLES_PER_PAGE #设置文章的起始索引
end=start+ARTICLES_PER_PAGE-1 #设置文章的结束索引
ids=conn.zrevrange(order,start,end) #获取多个文章ID
articles=[]
for id in ids:
article_data=conn.hgetall(id)
article_data['id']=id
articles.append(article_data)
return articles
虽然我们构建的网站现在已经可以展示最新发布的文章和评分最高的文章了,但它还不具备目前很多投票网站都支持的群组【group】功能:可以让用户看见与特定话题有关的文章,比如:【可爱的动物】、【历史】、【政治】等等。
3、对文章进行分组
群组功能由两个部分组成,一个部分负责记录文章属于哪个群组,另一部分负责取出群组里面的文章。为了记录各个群组都保存了哪些文章,网站需要为每个群组创建一个集合,并将所有同属一个群组的文章ID都记录到那个集合里面。
#将文章添加到群组里面的方法,以及从群组里面移除文章的方法
def add_remove_groups(conn,article_id,to_add=[],to_remove=[]):
article='article:'+article_id
for group in to_add:
conn.sadd('group:'+group,article) #将文章添加到它所属的群组里面
for group in to_remove:
conn.srem('group:'+group,article) #从群组里面移除文章
初看上去,可能会有读者觉得使用集合来记录群组文章并没有多大用处。到目前为止,我们只看到了集合结构检查某个元素是否存在的能力,但实际上Redis不仅可以对多个集合执行操作,甚至在一些情况下,还可以在集合和有序集合之间执行操作。
为了能够根据评分对群组文章进行排序和分页,网站需要将同一个群组里面的所有文章都按照评分有序的存储到一个有序集合里面。Redis的zinterstore命令可以接受多个集合和多个有序集合作为输入,找出所有同时存在于集合和有序集合的成员,并以几种不同的方式来合并【combine】这些成员的分值(所有集合成员的分值都会被视为:1)。对于我们的文章投票网站来说,程序需要使用zinterstore命令选出相同成员中最大的那个分值来作为交集成员的分值:取决于所使用的排序选项,这些分值既可以是文章的评分,也可以是文章的发布时间。
通过对存储群组文章的集合和存储文章评分的有序集合执行zinterstore命令,程序可以得到按照文章评分顺序的群组文章;
而通过对存储群组文章的集合和存储文章发布时间的有序集合执行zinterstore命令,程序则可以得到按照文章发布时间排序的群组文章。如果群组包含的文章非常多,那么执行zinterstore命令就会比较花时间,为了尽量减少Redis的工作量,程序会将这个命令的计算结果缓存60秒。
#分页并获取群组文章
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秒后自动删除这个有序集合
return get_articls(conn,page,key)
友情提示,上面只是部分代码,无法直接运行,只需要先理解思路就行。上一篇文章: Python--Redis实战:第一章:初识Redis:第二节:Redis数据结构简介
下一篇文章:Python--Redis实战:第二章:使用Redis构建Web应用:第一节:登录和cookie缓存