Redis辅助构建投票网站后台

Redis作为一款非关系型内存数据库,因为其丰富的数据结构和较高的效率和易用性。在很多系统中比如分布式消息队列和大型网站的缓存服务器等都有大规模的使用。
下面以一个对文章投票的网站作为例子,简单介绍Redis的使用:

1.实现文章投票功能

实现一个投票系统,并能根据票数计算出一个随时间减少的分值,并限制超过一定时间之后不能再投票。
分值的计算方法是:Votes*ConstValue + PublishTime

实现该功能所需要的数据结构有:
1)一个散列表存储文章信息 hash

{hash-name> "article:92617" | [key:value]> [title:"****"] [link:"http://****"],[poster:"user:83271"] [time:12389238] [votes:998]}

:使用”:”来分割名字的不同部分,以此来构建命名空间,如article:9276表示,类别是文章,文章ID是9276

2)两个有序集合分别用于存储文章ID对应的发布时间和评分 zset
网站通过这两个有序集合就可以实现文章列表按照发布时间或者评分排序的功能。

{zet-name> "time:" | [value:score]> ["article:10048":1332065273.23] ["article:10048":133427934.62 ] }
{zet-name> "score:" | [value:score]> ["article:10048":1332065273.23] ["article:10048":133427934.62 ] }

3)一个集合记录文章的已投票用户ID set
需要为每一篇文章创建一个集合,防止用户对同一篇文章多次投票。

{set-name> "voted:100408" | [value]> ["user:234","user:629","user:746"]}

:”voted:100408”表示ID为100408的文章

用户对文章进行投票的实现:
1)首先要使用ZSCORE命令检查ZSET集合”time:”,检查该篇文章是否在可投票时间段内。如果不可投票直接返回,如果可以投票,进入下一步
2)使用SADD命令将用户添加到该文章已投票名单中,如果添加失败说明该用户已经投过这篇文章,直接返回。添加成功,则进入下一步
3)使用ZINCRBY在有序集合”score:”中将这篇文章分值增加ConstValue
4)使用HINCRBY在散列表中将该文章对应的投票数量更新

投票实现函数如下:

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.spilt(':')[-1]                                   #获得文章id
    if conn.sadd('voted:'+article_id,user):                        #添加到已投票列表
        conn.zincrby('score:', article, VOTE_SCORE)          #修改文章评分
        conn.hincrby(article,'votes',1)                              #修改投票数量

2.发布新文章

文章的发布有两方面的工作,首先就是给文章生成一个唯一的ID,其次是设定上一节实现的所有相关数据结构的初值。
唯一的ID可以使用一个自增的计数器来实现。后面的工作就是将该ID与其他数据结构联系起来,步骤如下:
1)新建已投票者名单集合,并设置过期时间
2)将文章信息存到散列表中
3)初始化该文章的评分和发布时间
发布文章实现函数如下:

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

3.获取文章

分页取出评分最高或者发布时间最新的文章列表。共有两步:
1)使用ZREVRANGE命令取出当前页的所有文章ID
2)对每个文章ID执行HGETALL命令取得文章的详细信息

获取文章的实现函数如下:

ARTICLES_PER_PAGE = 25

def get_article(conn,page,order='score:'):
    start = (page-1)*ARTICLES_PER_PAGE
    end = start + ARTICLES_PER_PAGE - 1

    ids = conn.zrevrange(order, start, end)
    articles = []     
    for id in ids:
        article_data = conn.hegtall(id)
        article_data['id'] = id
        articles.append(article_data)
    return articles

4.文章分组

群组功能有两个部分组成,一个负责记录文章属于哪个分组,另一个负责取出群组里面的文章。
为了记录文章属于哪个分组,我们需要为每一个分组建立一个集合,并将所属的文章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)

有了分组之后我们需要根据评分或者根据发布时间,对分组中的文章进行排序和分页。网站需要将同一个分组里面的所有文章有序的存放起来。
我们需要每一个分组一个有序集合,用于有序存储当前分组下的文章。该有序集合的实现我们可以使用ZINTERSTORE命令实现,该命令该可以接受多个集合或者多个有序集合,然后求所有输入集合的交集,并且可以执行多种分值合并策略,如指定各个集合数据的权值或者选择最大值等。
分组排序的实现步骤为:
1)创建一个有序集合键
2)判断该键是否已存在,如果存在跳过第3步
3)使用zinterstore命令进行排序,并存到新建有序集合中,为该有序集合设置60秒过期时间,防止占用内存过多
4)获得有序集合中所有的文章信息

获取分组数据的实现函数为:

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)
    return get_articles(conn, page, key)

你可能感兴趣的:(Redis辅助构建投票网站后台)