zoukankan      html  css  js  c++  java
  • 使用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 cluster 值是有16384个卡槽分布在集群的master上存储数据的,每个master分别存储部分数据,这里难道需要我把数据集中到一个slot中才能调用此方法吗?找了很久也没找到合适的解决方案,有了解过的朋友希望留言告知。

  • 相关阅读:
    Windows Azure Web Site (19) Azure Web App链接到VSTS
    Windows Azure Virtual Machine (35) Azure VM通过Linked DB,执行SQL Job
    Azure PowerShell (16) 并行开关机Azure ARM VM
    Windows Azure Virtual Network (12) 虚拟网络之间点对点连接VNet Peering
    Azure ARM (21) Azure订阅的两种管理模式
    Windows Azure Platform Introduction (14) 申请海外的Windows Azure账户
    Azure ARM (20) 将非托管磁盘虚拟机(Unmanage Disk),迁移成托管磁盘虚拟机(Manage Disk)
    Azure ARM (19) 将传统的ASM VM迁移到ARM VM (2)
    Azure ARM (18) 将传统的ASM VM迁移到ARM VM (1)
    Azure Automation (6) 执行Azure SQL Job
  • 原文地址:https://www.cnblogs.com/blueskyli/p/9174959.html
Copyright © 2011-2022 走看看