天天看點

使用redis建構文章投票系統

本部落格代碼都是參考《Redis IN ACTION》這本書,由于書中代碼都是python所寫,是以本文代碼為java語言編寫,友善讀者查閱

public class Chapter01
{
    private static final int ONE_WEEK_IN_SECONDS = 7 * 86400; //用來計算超過7天之後的文章不可以投票
    private static final int VOTE_SCORE = 432; //432來源 這個常量是通過将一天的秒數(86400)除以文章展示一天所需的支援票數量(200 -支援票超過200就算有趣文章)得出的,文章每獲得一張支援票,程式要需要将文章的評分增加432分
    private static final int ARTICLES_PER_PAGE = 25;

    public static final void main(String[] args) {
        new Chapter01().run();
    }

    public void run() {
        Jedis conn = new Jedis("192.168.32.128");
        conn.select(15);

        // 初始化10篇文章
//        for(int i=1;i<11;i++)
//        {
//            String articleId = postArticle(conn, "username_"+i, "A title"+i);
//            System.out.println("create a new article: " + articleId);
//            Map<String, String> articleData = conn.hgetAll("article:" + articleId);
//            for(Map.Entry<String, String> entry : articleData.entrySet())
//            {
//                System.out.println("  " + entry.getKey() + ": " + entry.getValue());
//            }
//        }

        // 投票代碼開始
//        articleVote(conn, "jack", "article:1");
//        articleVote(conn, "jack", "article:2");
//        articleVote(conn, "jack", "article:3");
//        articleVote(conn, "jack", "article:4");
//        articleVote(conn, "jack", "article:5");
//        articleVote(conn, "jack", "article:6");
//        articleVote(conn, "jack", "article:8");
//        articleVote(conn, "jack", "article:9");
//        articleVote(conn, "jack1", "article:1");
//        articleVote(conn, "jack2", "article:1");
//        articleVote(conn, "jack3", "article:1");
//        articleVote(conn, "jack4", "article:1");
//        articleVote(conn, "jack2", "article:3");
//        articleVote(conn, "jack3", "article:3");
//        String votes = conn.hget("article:1", "votes");
//        System.out.println("We voted for the article, it now has votes: " + votes);

        //得到從高到低的排名
//        System.out.println("The currently highest-scoring articles are:");
//        List<Map<String,String>> articles = getArticles(conn, 1);
//        printArticles(articles);

        // 給文章分組
//        addGroups(conn, "1", new String[]{"jack01-group","jack02-group"});
//        addGroups(conn, "2", new String[]{"jack01-group","jack02-group"});
//        addGroups(conn, "5", new String[]{"jack01-group","jack02-group"});
//        addGroups(conn, "6", new String[]{"jack01-group","jack03-group"});
//        addGroups(conn, "7", new String[]{"jack03-group","jack02-group"});
//        addGroups(conn, "10", new String[]{"jack01-group","jack03-group"});
//        addGroups(conn, "8", new String[]{"jack03-group","jack02-group"});
//        addGroups(conn, "9", new String[]{"jack03-group","jack02-group"});
//        addGroups(conn, "4", new String[]{"jack03-group","jack02-group"});

        val articles = getGroupArticles(conn, "jack03-group", 1);
        printArticles(articles);
    }

    /**
     * 1,生成文章ID
     * 2,将釋出者ID增加到已投票使用者名單集合中
     * 3,使用HMSET指令來存儲文章相關資訊
     * 4,将文章初始評分和釋出時間分别添加到2個相應的有序集合中
     * @return
     */
    public String postArticle(Jedis conn, String userName, String title) {
        String articleId = String.valueOf(conn.incr("article:"));

        String voted = "voted:" + articleId;

        //set
        conn.sadd(voted, userName);
        conn.expire(voted, ONE_WEEK_IN_SECONDS);

        ////hash
        long now = System.currentTimeMillis() / 1000;
        String article = "article:" + articleId;
        HashMap<String,String> articleData = new HashMap<String,String>();
        articleData.put("title", title);
        articleData.put("user", userName);
        articleData.put("now", String.valueOf(now));
        conn.hmset(article, articleData);

        //zset
        conn.zadd("score:", now + VOTE_SCORE, article);
        //釋出的時候 預設作者本人為文章投票
        conn.zadd("time:", now, article);

        return articleId;
    }

    /**
     * 文章投票
     * 1,每一票對應一個常量
     * @return
     */
    public void articleVote(Jedis conn, String user, String article) {
        long cutoff = (System.currentTimeMillis() / 1000) - ONE_WEEK_IN_SECONDS;
        if (conn.zscore("time:", article) < cutoff){ //計算是否超過文章投票截止時間
            System.out.println("文章釋出時間太長了");
            return;
        }

        String articleId = article.substring(article.indexOf(':') + 1);
        if (conn.sadd("voted:" + articleId, user) == 1) { //如果值不==1 則使用者已經投過此文章了  1 辨別原set中沒有,本次添加成功
            //增加score的分值,使其增加VOTE_SCORE
            conn.zincrby("score:", VOTE_SCORE, article);
            //修改文章中votes的票數,使其增加1
            conn.hincrBy(article, "votes", 1);
        }
    }

    /**
     * 分頁資料
     * @param conn
     * @param page
     * @return
     */
    public List<Map<String,String>> getArticles(Jedis conn, int page) {
        return getArticles(conn, page, "score:");
    }

    /**
     * 根據order擷取排名靠前的資料
     * @param conn
     * @param page
     * @param order
     * @return
     */
    public List<Map<String,String>> getArticles(Jedis conn, int page, String order) {
        int start = (page - 1) * ARTICLES_PER_PAGE;
        int end = start + ARTICLES_PER_PAGE - 1;

        Set<String> ids = conn.zrevrange(order, start, end);
        List<Map<String,String>> articles = new ArrayList<Map<String,String>>();
        for (String id : ids){
            Map<String,String> articleData = conn.hgetAll(id);
            articleData.put("id", id);
            articles.add(articleData);
        }

        return articles;
    }

    /**
     * 文章分組
     * @return
     */
    public void addGroups(Jedis conn, String articleId, String[] toAdd) {
        String article = "article:" + articleId;
        for (String group : toAdd) {
            conn.sadd("group:" + group, article);
        }
    }

    public List<Map<String,String>> getGroupArticles(Jedis conn, String group, int page) {
        return getGroupArticles(conn, group, page, "score:");
    }

    /**
     * 按順序得到某一組内的文章
     * 這裡會使用到關系型資料庫裡面的\
     * Redis Zinterstore 指令計算給定的一個或多個有序集的交集 預設情況下,結果集中某個成員的分數值是所有給定集下該成員分數值之和。
     * 使用 WEIGHTS 選項,你可以為 每個 給定有序集 分别 指定一個乘法因子(multiplication factor),每個給定有序集的所有成員的 score 值在傳遞給聚合函數(aggregation function)之前都要先乘以該有序集的因子。
     * 使用 AGGREGATE 選項,你可以指定并集的結果集的聚合方式。預設使用的參數 SUM ,可以将所有集合中某個成員的 score 值之 和 作為結果集中該成員的 score 值;使用參數 MIN ,可以将所有集合中某個成員的 最小 score 值作為結果集中該成員的 score 值;而參數 MAX 則是将所有集合中某個成員的 最大 score 值作為結果集中該成員的 score 值。
     * @return
     */
    public List<Map<String,String>> getGroupArticles(Jedis conn, String group, int page, String order) {
        String key = order + group;
        if (!conn.exists(key)) {
            ZParams params = new ZParams().aggregate(ZParams.Aggregate.MAX);
            conn.zinterstore(key, params, "group:" + group, order);
            conn.expire(key, 60);
        }
        return getArticles(conn, page, key);
    }

    private void printArticles(List<Map<String,String>> articles){
        for (Map<String,String> article : articles){
            System.out.println("  id: " + article.get("id"));
            for (Map.Entry<String,String> entry : article.entrySet()){
                if (entry.getKey().equals("id")){
                    continue;
                }
                System.out.println("    " + entry.getKey() + ": " + entry.getValue());
            }
        }
    }
}      

文章中redis采用單機模式,這裡小編在寫上面代碼的時候遇到一個問題,至今沒找到原因,有路過的大神希望不吝賜教:

一開始上面代碼是使用springboot + rediscluster 模式實作,但是使用redistemplate 執行 zinterstore 的時候報出  

ZINTERSTORE can only be executed when all keys map to the same slot      
使用redis建構文章投票系統

我們知道redis cluster 值是有16384個卡槽分布在叢集的master上存儲資料的,每個master分别存儲部分資料,這裡難道需要我把資料集中到一個slot中才能調用此方法嗎?找了很久也沒找到合适的解決方案,有了解過的朋友希望留言告知。

開開心心編碼,快快樂樂生活。