摘要:文章投票網站的相關實現需求要構建一個文章投票網站,文章需要在一天內至少獲得張票,才能優先顯示在當天文章列表前列。文章發布期滿一周后,用戶不能在對它投票。此命令會覆蓋哈希表中已存在的域。
文章投票網站的redis相關Java實現 需求:
1、要構建一個文章投票網站,文章需要在一天內至少獲得200張票,才能優先顯示在當天文章列表前列。
2、但是為了避免發布時間較久的文章由于累計的票數較多而一直停留在文章列表前列,我們需要有隨著時間流逝而不斷減分的評分機制。
3、于是具體的評分計算方法為:將文章得到的支持票數乘以一個常量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命令,將文章的初始評分和發布時間分別添加到兩個相應的有序集合中。
public class Chapter01 { private static final int ONE_WEEK_IN_SECONDS = 7 * 86400; //文章發布期滿一周后,用戶不能在對它投票。 private static final int VOTE_SCORE = 432; //計算評分時間與支持票數相乘的常量,通過將一天的秒數除(86400)以文章展示一天所需的支持票數(200)得出的 private static final int ARTICLES_PER_PAGE = 25; /** * 發布并獲取文章 *1、發布一篇新文章需要創建一個新的文章id,可以通過對一個計數器(count)執行INCY命令來完成。 *2、程序需要使用SADD將文章發布者的ID添加到記錄文章已投票用戶名單的集合中, * 并使用EXPIRE命令為這個集合設置一個過期時間,讓Redis在文章發布期滿一周之后自動刪除這個集合。 *3、之后程序會使用HMSET命令來存儲文章的相關信息,并執行兩個ZADD,將文章的初始評分和發布時間分別添加到兩個相應的有序集合里面。 */ public String postArticle(Jedis conn, String user, String title, String link) { //1、生成一個新的文章ID String articleId = String.valueOf(conn.incr("article:")); //String.valueOf(int i) : 將 int 變量 i 轉換成字符串 String voted = "voted:" + articleId; //2、添加到記錄文章已投用戶名單中, conn.sadd(voted, user); //3、設置一周為過期時間 conn.expire(voted, ONE_WEEK_IN_SECONDS); long now = System.currentTimeMillis() / 1000; String article = "article:" + articleId; //4、創建一個HashMap容器。 HashMaparticleData = new HashMap (); articleData.put("title", title); articleData.put("link", link); articleData.put("user", user); articleData.put("now", String.valueOf(now)); articleData.put("oppose", "0"); articleData.put("votes", "1"); //5、將文章信息存儲到一個散列里面。 //HMSET key field value [field value ...] //同時將多個 field-value (域-值)對設置到哈希表 key 中。 //此命令會覆蓋哈希表中已存在的域。 conn.hmset(article, articleData); //6、將文章添加到更具評分排序的有序集合中 //ZADD key score member [[score member] [score member] ...] //將一個或多個 member 元素及其 score 值加入到有序集 key 當中 conn.zadd("score:", now + VOTE_SCORE, article); //7、將文章添加到更具發布時間排序的有序集合。 conn.zadd("time:", now, article); return articleId; } }
2.當用戶嘗試對一篇文章進行投票時,
(1)用ZSCORE命令檢查記錄文章發布時間的有序集合(redis設計2),判斷文章的發布時間是否未超過一周。
(2)如果文章仍然處于可以投票的時間范疇,那么用SADD將用戶添加到記錄文章已投票用戶名單的集合(redis設計4)中。
(3)如果上一步操作成功,那么說明用戶是第一次對這篇文章進行投票,那么使用ZINCRBY命令為文章的評分增加432(ZINCRBY命令用于對有序集合成員的分值執行自增操作);
并使用HINCRBY命令對散列記錄的文章投票數量進行更新
/** * 對文章進行投票 * 實現投票系統的步驟: * 1、當用戶嘗試對一篇文章進行投票時,程序要使用ZSCORE命令檢查記錄文字發布時間的有序集合,判斷文章的發布時間是否超過一周。 * 2、如果文章仍然處于可投票的時間范圍之內,那么程序將使用SADD命令,嘗試將用戶添加到記錄文章的已投票用戶名單的集合中。 * 3、如果投票執行成功的話,那么說明用戶是第一次對這篇文章進行投票,程序將使用ZINCYBY命令為文章的評分增加432(ZINCYBY命令用于對有序集合成員的分值進行自增操作), * 并使用HINCRBY命令對散列記錄的文章投票數量進行更新(HINCRBY命令用于對散列存儲的值執行自增操作) */ public void articleVote(Jedis conn, String user, String article) { //1、計算文章投票截止時間。 long cutoff = (System.currentTimeMillis() / 1000) - ONE_WEEK_IN_SECONDS; //2、檢查是否還可以對文章進行投票,(雖然使用散列也可以獲取文章的發布時間,但有序集合返回文章發布時間為浮點數,可以不進行轉換,直接使用) if (conn.zscore("time:", article) < cutoff){ return; } //3、從articleId標識符里面取出文章ID。 //nt indexOf(int ch,int fromIndex)函數:就是字符ch在字串fromindex位后出現的第一個位置.沒有找到返加-1 //String.Substring (Int32) 從此實例檢索子字符串。子字符串從指定的字符位置開始。 String articleId = article.substring(article.indexOf(":") + 1); //4、檢查用戶是否第一次為這篇文章投票,如果是第一次,則在增加這篇文章的投票數量和評分。 if (conn.sadd("voted:" + articleId, user) == 1) { //將一個或多個 member 元素加入到集合 key 當中,已經存在于集合的 member 元素將被忽略。 //為有序集 key 的成員 member 的 score 值加上增量 increment 。 //可以通過傳遞一個負數值 increment ,讓 score 減去相應的值, //當 key 不存在,或 member 不是 key 的成員時, ZINCRBY key increment member 等同于 ZADD key increment member 。 //ZINCRBY salary 2000 tom # tom 加薪啦! conn.zincrby("score:", VOTE_SCORE, article); //為哈希表 key 中的域 field 的值加上增量 increment 。 //增量也可以為負數,相當于對給定域進行減法操作。 //HINCRBY counter page_view 200 conn.hincrBy(article, "votes", 1L); } } /** * 投反對票 */ public void articleOppose(Jedis conn, String user, String article) { long cutoff = (System.currentTimeMillis() / 1000) - ONE_WEEK_IN_SECONDS; //cutoff之前的發布的文章 就不能再投票了 if (conn.zscore("time:", article) < cutoff){ return; } String articleId = article.substring(article.indexOf(":") + 1); //查看user是否給這篇文章投過票 //set里面的key是唯一的 如果 sadd返回0 表示set里已經有數據了 //如果返回1表示還沒有這個數據 if (conn.sadd("oppose:" + articleId, user) == 1) { conn.zincrby("score:", -VOTE_SCORE, article); conn.hincrBy(article, "votes", -1L); } }
3.我們已經實現了文章投票功能和文章發布功能,接下來就要考慮如何取出評分最高的文章以及如何取出最新發布的文章
(1)我們需要使用ZREVRANGE命令取出多個文章ID。(由于有序集合會根據成員的分值從小到大地排列元素,使用ZREVRANGE以分值從大到小的排序取出文章ID)
(2)對每個文章ID執行一次HGETALL命令來取出文章的詳細信息。
這個方法既可以用于取出評分最高的文章,又可以用于取出最新發布的文章。
public List
4. 對文章進行分組,用戶可以只看自己感興趣的相關主題的文章。
群組功能主要有兩個部分:一是負責記錄文章屬于哪個群組,二是負責取出群組中的文章。
為了記錄各個群組都保存了哪些文章,需要為每個群組創建一個集合,并將所有同屬一個群組的文章ID都記錄到那個集合中。
/** * 記錄文章屬于哪個群組 * 將所屬一個群組的文章ID記錄到那個集合中 * Redis不僅可以對多個集合執行操作,甚至在一些情況下,還可以在集合和有序集合之間執行操作 */ public void addGroups(Jedis conn, String articleId, String[] toAdd) { //1、構建存儲文章信息的鍵名 String article = "article:" + articleId; for (String group : toAdd) { //2、將文章添加到它所屬的群組里面 conn.sadd("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函數來分頁并獲取群組文章。
public List> getGroupArticles(Jedis conn, String group, int page) { //調用下面重載的方法 return getGroupArticles(conn, group, page, "score:"); } /** * 取出群組里面的文章 * 為了能夠根據評分對群組文章進行排序和分頁,網站需要將同一個群組里面的所有文章都按評分有序的存儲到一個有序集合內, * 程序需要使用ZINTERSTORE命令選出相同成員中最大的那個分支作為交集成員的值:取決于所使用的排序選項,可以是評分或文章發布時間。 */ public List > getGroupArticles(Jedis conn, String group, int page, String order) { //1、為每個群組的每種排列順序都創建一個鍵。 String key = order + group; //2、檢查是否有已緩存的排序結果,如果沒有則進行排序。 if (!conn.exists(key)) { //3、根據評分或者發布時間對群組文章進行排序 ZParams params = new ZParams().aggregate(ZParams.Aggregate.MAX); conn.zinterstore(key, params, "group:" + group, order); //讓Redis在60秒之后自動刪除這個有序集合 conn.expire(key, 60); } //4、調用之前定義的getArticles()來進行分頁并獲取文章數據 return getArticles(conn, page, key); }
以上就是一個文章投票網站的相關redis實現。
測試代碼如下:
public static final void main(String[] args) { new Chapter01().run(); } public void run() { //1、初始化redis連接 Jedis conn = new Jedis("localhost"); conn.select(15); //2、發布文章 String articleId = postArticle( conn, "guoxiaoxu", "A title", "http://www.google.com"); System.out.println("我發布了一篇文章,id為:: " + articleId); System.out.println("文章保存的散列格式如下:"); Map參考articleData = conn.hgetAll("article:" + articleId); for (Map.Entry entry : articleData.entrySet()){ System.out.println(" " + entry.getKey() + ": " + entry.getValue()); } System.out.println(); //2、測試文章的投票過程 articleVote(conn, "other_user", "article:" + articleId); String votes = conn.hget("article:" + articleId, "votes"); System.out.println("我們為該文章投票,目前該文章的票數 " + votes); assert Integer.parseInt(votes) > 1; //3、測試文章的投票過程 articleOppose(conn, "other_user", "article:" + articleId); String oppose = conn.hget("article:" + articleId, "votes"); System.out.println("我們為該文章投反對票,目前該文章的反對票數 " + oppose); assert Integer.parseInt(oppose) > 1; System.out.println("當前得分最高的文章是:"); List > articles = getArticles(conn, 1); printArticles(articles); assert articles.size() >= 1; addGroups(conn, articleId, new String[]{"new-group"}); System.out.println("我們將文章推到新的群組,其他文章包括:"); articles = getGroupArticles(conn, "new-group", 1); printArticles(articles); assert articles.size() >= 1; }
1.文章投票網站的redis相關實現(python)
Redis實戰相關代碼,目前有Java,JS,node,Python
2.Redis 命令參考
代碼地址https://github.com/guoxiaoxu/...
說明如果你有耐心讀到這里,請允許我說明下:
1、本文主題結構參考了文章投票網站的redis相關實現(python)
2、留下重復的注釋是為了自己對比,努力讓自己變得不一樣
3、通過一天的分析、學習。越覺得需要學的東西太多了。而不只是簡單的記住幾個命令
4、感謝所有人,感謝SegmentFault,讓你見證我脫變的過程吧。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/70989.html
摘要:為了防止用戶對同一篇文章進行多次投票,網站需要為每一篇文章記錄一個已投票用戶名單。上一篇文章實戰第一章初識第二節數據結構簡介下一篇文章實戰第二章使用構建應用第一節登錄和緩存 上一篇文章: Python--Redis實戰:第一章:初識Redis:第二節:Redis數據結構簡介下一篇文章:Python--Redis實戰:第二章:使用Redis構建Web應用:第一節:登錄和cookie緩存 ...
摘要:需求要構建一個文章投票網站,文章需要在一天內至少獲得張票,才能優先顯示在當天文章列表前列。根據評分或者發布時間對群組文章進行排序和分頁文章添加的群組移除的群組群組有序集合名以上就是一個文章投票網站的相關實現。 需求: 要構建一個文章投票網站,文章需要在一天內至少獲得200張票,才能優先顯示在當天文章列表前列。 但是為了避免發布時間較久的文章由于累計的票數較多而一直停留在文章列表前列,我...
閱讀 2852·2021-09-28 09:36
閱讀 3949·2021-09-22 15:52
閱讀 3635·2021-09-06 15:00
閱讀 1955·2021-09-02 15:40
閱讀 2803·2021-09-02 15:15
閱讀 3467·2021-08-17 10:15
閱讀 2785·2019-08-30 15:53
閱讀 2077·2019-08-29 18:39