Python--Redis實戰:第一章:初識Redis:第三節:你好Redis-文章投票試煉

上一篇文章: Python--Redis實戰:第一章:初識Redis:第二節:Redis數據結構簡介
下一篇文章: Python--Redis實戰:第二章:使用Redis構建Web應用:第一節:登陸和cookie緩存

在對Redis提供的5種結構有了基本的瞭解後,如今是時候來學習一下怎樣使用這些結構來解決實際問題了。redis

最近幾年,愈來愈多的網站開始提供對網頁連接、文章或者穩贏進行投票的功能,這些網站會根據文章的發佈時間和文章得到的投票數量計算出一個評分,而後根據這個評分來決定如何排序和展現文章。編程

本節將展現如何使用Redis來構建一個簡單的文章投票網站的後端。segmentfault

一、對文章進行投票

要構建一個文章投票網站,咱們首先要作的就是爲了這個網站設置一些數值和限制條件:若是一篇文章得到了至少200張支持票,那麼久認爲這篇文章是【有趣的】。假如這個網站天天發佈1000篇文章,而其中的50篇符合網站對【有趣】文章的要求,那麼網站要作的就是把這50篇文章放到文章放到文章列表前100位至少一天;另外,這個網站暫時不提供投反對票的功能。後端

爲了產生一個可以隨着時間流逝而不斷減小的評分,程序須要根據文章的發佈時間和當前時間來計算文章的評分,具體的計算方法爲:將文章獲得的支持票數乘以一個常量,而後加上文章的發佈時間,得出的結果就是文章的評分。緩存

咱們使用從UTC時區1970年1月1日到如今爲止所通過的秒數來計算文章的評分,這個值一般被成爲Unix時間。之因此選擇使用Unix時間,是由於全部可以運行Redis的平臺上面,使用編程語言獲取這個值都是很是簡單的事情。另外,計算評分時與支持數量相乘的常量爲432,這個常量是經過將一天的秒數(86400)除以文章展現一天所需的支持票數量(200得出的):文章沒得到一張支持票,程序就須要將程序的評分增長432分。cookie

構建文章投票網站除了須要計算文章評分以外,還須要使用Redis結構存儲網站上的各類信息。對於網站裏的每一篇文章,程序都使用了一個散列來存儲文章的標題、指向文章的網址、發佈文章的用戶、文章的發佈時間、文章獲得的投票數量等信息。數據結構

咱們的文章投票網站將使用兩個有序集合來存儲文章:app

  • 第一個有序集合的成員爲:文章ID、文章的發佈時間,該有序集合可使網站按照發布時間前後展現文章
  • 第二個有序集合的成員爲:文章ID、文章的評分,該有序集合可使網站按照評分高低展現文章。

爲了防止用戶對同一篇文章進行屢次投票,網站須要爲每一篇文章記錄一個已投票用戶名單。爲此,須要建立一個存儲全部已經投票的用戶ID。編程語言

爲了儘可能節約內存,咱們規定當一篇文章發佈期滿一週以後,用戶將不能再對它進行投票,文章的評分將被固定下來,而積累文章已經投票用戶名單的集合會被刪除。post

既然咱們已經知道了網站計算文章評分的方法,也知道了網站存儲數據所需的數據結構,那麼如今是時候實際的實現這個投票功能了!

  • 當用戶嘗試對一篇文章進行投票時,程序須要使用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)

二、發佈並獲取文章

  • 發佈一篇文章首先須要建立一個新的文章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】功能:可讓用戶看見與特定話題有關的文章,好比:【可愛的動物】、【歷史】、【政治】等等。

三、對文章進行分組

羣組功能由兩個部分組成,一個部分負責記錄文章屬於哪一個羣組,另外一部分負責取出羣組裏面的文章。爲了記錄各個羣組都保存了哪些文章,網站須要爲每一個羣組建立一個集合,並將全部同屬一個羣組的文章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緩存

相關文章
相關標籤/搜索