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)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章