摘要:需求要構建一個文章投票網站,文章需要在一天內至少獲得張票,才能優先顯示在當天文章列表前列。根據評分或者發布時間對群組文章進行排序和分頁文章添加的群組移除的群組群組有序集合名以上就是一個文章投票網站的相關實現。
需求:
要構建一個文章投票網站,文章需要在一天內至少獲得200張票,才能優先顯示在當天文章列表前列。
但是為了避免發布時間較久的文章由于累計的票數較多而一直停留在文章列表前列,我們需要有隨著時間流逝而不斷減分的評分機制。
于是具體的評分計算方法為:將文章得到的支持票數乘以一個常量432(由一天的秒數86400除以文章展示一天所需的支持票200得出),然后加上文章的發布時間,得出的結果就是文章的評分。
Redis設計(1)對于網站里的每篇文章,需要使用一個散列來存儲文章的標題、指向文章的網址、發布文章的用戶、文章的發布時間、文章得到的投票數量等信息。
為了方便網站根據文章發布的先后順序和文章的評分高低來展示文章,我們需要兩個有序集合來存儲文章:
(2)有序集合,成員為文章ID,分值為文章的發布時間。
(3)有序集合,成員為文章ID,分值為文章的評分。
(4)為了防止用戶對同一篇文章進行多次投票,需要為每篇文章記錄一個已投票用戶名單。使用集合來存儲已投票的用戶ID。由于集合是不能存儲多個相同的元素的,所以不會出現同個用戶對同一篇文章多次投票的情況。
(5)文章支持群組功能,可以讓用戶只看見與特定話題相關的文章,比如“python”有關或者介紹“redis”的文章等,這時,我們需要一個集合來記錄群組文章。例如 programming群組
為了節約內存,當一篇文章發布期滿一周之后,用戶將不能對它進行投票,文章的評分將被固定下來,而記錄文章已投票用戶名單的集合也會被刪除。
代碼設計1.當用戶要發布文章時,
(1)通過一個計數器counter執行INCR命令來創建一個新的文章ID。
(2)使用SADD將文章發布者ID添加到記錄文章已投票用戶名單的集合中,并用EXPIRE命令為這個集合設置一個過期時間,讓Redis在文章發布期滿一周后自動刪除這個集合。
(3)使用HMSET命令來存儲文章的相關信息,并執行兩ZADD命令,將文章的初始評分和發布時間分別添加到兩個相應的有序集合中。
import time # 截止時間,一周 ONE_WEEK_IN_SECONDS = 7 * (24 * 60 * 60) # 計分常量 VOTE_SCORE = 432 """ 發布文章 @param {object} @param {string} 用戶 @param {string} 文章title @param @return {string} 文章id """ def postArticle(conn, user, title, link): # 創建一個新的文章ID article_id = str(conn.incr("article:")) # 將文章發布者ID添加到記錄文章已投票用戶名單的集合中,并用EXPIRE為這個集合設置過期時間 voted = "voted:" + article_id conn.sadd(voted, user) conn.expire(voted, ONE_WEEK_IN_SECONDS) now = time.time() # 用HMSET存儲文章的相關信息 article = "article:" + article_id conn.hmset(article, { "title": title, "link": link, "poster": user, "time": now, "votes": 1 }) # 執行兩個ZADD,將文章的初始評分與發布時間添加到兩個相應的有序集合中 conn.zadd("time:", article, now) conn.zadd("score:", article, now + VOTE_SCORE) return article_id
2.當用戶嘗試對一篇文章進行投票時,
(1)用ZSCORE命令檢查記錄文章發布時間的有序集合(redis設計2),判斷文章的發布時間是否未超過一周。
(2)如果文章仍然處于可以投票的時間范疇,那么用SADD將用戶添加到記錄文章已投票用戶名單的集合(redis設計4)中。
(3)如果上一步操作成功,那么說明用戶是第一次對這篇文章進行投票,那么使用ZINCRBY命令為文章的評分增加432(ZINCRBY命令用于對有序集合成員的分值執行自增操作);
并使用HINCRBY命令對散列記錄的文章投票數量進行更新
""" 用戶投票功能 @param {object} @param {string} 用戶 @param {string} 文章 """ def voteArticle(conn, user, article): # 判斷文章是否超過了投票截止時間,如果超過,則不允許投票 outoff = time.time() - ONE_WEEK_IN_SECONDS if conn.zscore("time:", article) < outoff: return article_id = article.partition(":")[-1] # 將用戶添加到記錄已投票用戶名單的集合中 if conn.sadd("voted:" + article_id, user): # 增加該文章的評分和投票數量 conn.zincrby("score:", article, VOTE_SCORE) conn.hincrby(article, "votes", 1)
3.我們已經實現了文章投票功能和文章發布功能,接下來就要考慮如何取出評分最高的文章以及如何取出最新發布的文章
(1)我們需要使用ZREVRANGE命令取出多個文章ID。(由于有序集合會根據成員的分值從小到大地排列元素,使用ZREVRANGE以分值從大到小的排序取出文章ID)
(2)對每個文章ID執行一次HGETALL命令來取出文章的詳細信息。
這個方法既可以用于取出評分最高的文章,又可以用于取出最新發布的文章。
""" 取出評分最高的文章,或者最新發布的文章 @param {object} @param {int} 頁碼 @param {string} 有序集合名稱,可以是score:,time: @return array """ # 每頁的文章數 ARTICLES_PER_PAGE = 25 def getArticles(conn, page, order = "score:"): # 獲取指定頁碼文章的起始索引和結束索引 start = (page - 1) * ARTICLES_PER_PAGE end = start + ARTICLES_PER_PAGE - 1 # 取出指定位置的文章id article_ids = conn.zrevrange(order, start, end) articles = [] for id in article_ids: article_data = conn.hgetall(id) article_data["id"] = id articles.append(article_data) return articles
4. 對文章進行分組,用戶可以只看自己感興趣的相關主題的文章。
群組功能主要有兩個部分:一是負責記錄文章屬于哪個群組,二是負責取出群組中的文章。
為了記錄各個群組都保存了哪些文章,需要為每個群組創建一個集合,并將所有同屬一個群組的文章ID都記錄到那個集合中。
""" 添加移除文章到指定的群組中 @param {object} @param {int} 文章ID @param {array} 添加的群組 @param {array} 移除的群組 """ def addRemoveGroups(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的ZINTERSTORE命令可以接受多個集合和多個有序集合作為輸入,找出所有同時存在于集合和有序集合的成員,并以幾種不同的方式來合并這些成員的分值(所有集合成員的分支都會視為1)。
對于文章投票網站來說,可以使用ZINTERSTORE命令選出相同成員中最大的那個分值來作為交集成員的分值:取決于所使用的排序選項,這些分值既可以是文章的評分,也可以是文章的發布時間。
如下的示例圖,顯示了執行ZINTERSTORE命令的過程:
對集合groups:programming和有序集合score:進行交集計算得出了新的有序集合score:programming,它包含了所有同時存在于集合groups:programming和有序集合score:的成員。因為集合groups:programming的所有成員分值都被視為1,而有序集合score:的所有成員分值都大于1,這次交集計算挑選出來的分值為相同成員中的最大分值,所以有序集合score:programming的成員分值實際上是由有序集合score:的成員的分值來決定的。
所以,我們的操作如下:
(1)通過群組文章集合和評分的有序集合或發布時間的有序集合執行ZINTERSTORE命令,而得到相關的群組文章有序集合。
(2)如果群組文章很多,那么執行ZINTERSTORE需要花費較多的時間,為了盡量減少redis的工作量,我們將查詢出的有序集合進行緩存處理,盡量減少ZINTERSTORE命令的執行次數。
為了保持持續更新后我們能獲取到最新的群組文章有序集合,我們只將結果緩存60秒。
(3)使用上一步的getArticles函數來分頁并獲取群組文章。
""" 根據評分或者發布時間對群組文章進行排序和分頁 @param {object} @param {int} 文章ID @param {array} 添加的群組 @param {array} 移除的群組 """ def getGroupArticles(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 getArticles(conn, page, key)
以上就是一個文章投票網站的相關redis實現。
測試代碼如下:
import unittest class TestArticle(unittest.TestCase): """ 初始化redis連接 """ def setUp(self): import redis self.conn = redis.Redis(db=15) """ 刪除redis連接 """ def tearDown(self): del self.conn print print """ 測試文章的投票過程 """ def testArticleFunctionality(self): conn = self.conn import pprint # 發布文章 article_id = str(postArticle(conn, "username", "A titile", "http://www.baidu.com")) print "我發布了一篇文章,id為:", article_id print self.assertTrue(article_id) article = "article:" + article_id # 顯示文章保存的散列格式 print "文章保存的散列格式如下:" article_hash = conn.hgetall(article) print article_hash print self.assertTrue(article) # 為文章投票 voteArticle(conn, "other_user", article) print "我們為該文章投票,目前該文章的票數:" votes = int(conn.hget(article, "votes")) print votes print self.assertTrue(votes > 1) print "當前得分最高的文章是:" articles = getArticles(conn, 1) pprint.pprint(articles) print self.assertTrue(len(articles) >= 1) # 將文章推入到群組 addRemoveGroups(conn, article_id, ["new-group"]) print "我們將文章推到新的群組,其他文章包括:" articles = getGroupArticles(conn, "new-group", 1) pprint.pprint(articles) print self.assertTrue(len(articles) >= 1) 測試結束,刪除所有的數據結構 to_del = ( conn.keys("time:*") + conn.keys("voted:*") + conn.keys("score:*") + conn.keys("articles:*") + conn.keys("group:*") ) if to_del: conn.delete(*to_del) if __name__ == "__main__": unittest.main()代碼地址
https://github.com/NancyLin/r...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/40758.html
摘要:文章投票網站的相關實現需求要構建一個文章投票網站,文章需要在一天內至少獲得張票,才能優先顯示在當天文章列表前列。文章發布期滿一周后,用戶不能在對它投票。此命令會覆蓋哈希表中已存在的域。 文章投票網站的redis相關Java實現 需求: 1、要構建一個文章投票網站,文章需要在一天內至少獲得200張票,才能優先顯示在當天文章列表前列。 2、但是為了避免發布時間較久的文章由于累計的票數較多...
摘要:為了防止用戶對同一篇文章進行多次投票,網站需要為每一篇文章記錄一個已投票用戶名單。上一篇文章實戰第一章初識第二節數據結構簡介下一篇文章實戰第二章使用構建應用第一節登錄和緩存 上一篇文章: Python--Redis實戰:第一章:初識Redis:第二節:Redis數據結構簡介下一篇文章:Python--Redis實戰:第二章:使用Redis構建Web應用:第一節:登錄和cookie緩存 ...
閱讀 2158·2021-11-15 11:36
閱讀 1485·2021-09-23 11:55
閱讀 2493·2021-09-22 15:16
閱讀 2030·2019-08-30 15:45
閱讀 1867·2019-08-29 11:10
閱讀 1032·2019-08-26 13:40
閱讀 919·2019-08-26 10:44
閱讀 3173·2019-08-23 14:55