使用redis构建文章投票系统

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 本博客代码都是参考《Redis IN ACTION》这本书,由于书中代码都是python所写,所以本文代码为java语言编写,方便读者查阅public class Chapter01{ private static final int ONE_WEEK_IN_SECONDS = 7 *...

本博客代码都是参考《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中才能调用此方法吗?找了很久也没找到合适的解决方案,有了解过的朋友希望留言告知。

开开心心编码,快快乐乐生活。
相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
6月前
|
存储 NoSQL Redis
Redis系列学习文章分享---第十六篇(Redis原理1篇--Redis数据结构-动态字符串,insert,Dict,ZipList,QuickList,SkipList,RedisObject)
Redis系列学习文章分享---第十六篇(Redis原理1篇--Redis数据结构-动态字符串,insert,Dict,ZipList,QuickList,SkipList,RedisObject)
85 1
|
6月前
|
NoSQL Java Redis
Redis系列学习文章分享---第十八篇(Redis原理篇--网络模型,通讯协议,内存回收)
Redis系列学习文章分享---第十八篇(Redis原理篇--网络模型,通讯协议,内存回收)
87 0
|
6月前
|
存储 消息中间件 缓存
Redis系列学习文章分享---第十七篇(Redis原理篇--数据结构,网络模型)
Redis系列学习文章分享---第十七篇(Redis原理篇--数据结构,网络模型)
99 0
|
6月前
|
存储 NoSQL 算法
Redis系列学习文章分享---第十篇(Redis快速入门之附近商铺+用户签到+UV统计)
Redis系列学习文章分享---第十篇(Redis快速入门之附近商铺+用户签到+UV统计)
45 0
|
6月前
|
存储 NoSQL Redis
Redis系列学习文章分享---第九篇(Redis快速入门之好友关注--关注和取关 -共同关注 -Feed流实现方案分析 -推送到粉丝收件箱 -滚动分页查询)
Redis系列学习文章分享---第九篇(Redis快速入门之好友关注--关注和取关 -共同关注 -Feed流实现方案分析 -推送到粉丝收件箱 -滚动分页查询)
70 0
|
6月前
|
消息中间件 负载均衡 NoSQL
Redis系列学习文章分享---第七篇(Redis快速入门之消息队列--List实现消息队列 Pubsub实现消息队列 stream的单消费模式 stream的消费者组模式 基于stream消息队列)
Redis系列学习文章分享---第七篇(Redis快速入门之消息队列--List实现消息队列 Pubsub实现消息队列 stream的单消费模式 stream的消费者组模式 基于stream消息队列)
77 0
|
6月前
|
消息中间件 NoSQL Java
Redis系列学习文章分享---第六篇(Redis实战篇--Redis分布式锁+实现思路+误删问题+原子性+lua脚本+Redisson功能介绍+可重入锁+WatchDog机制+multiLock)
Redis系列学习文章分享---第六篇(Redis实战篇--Redis分布式锁+实现思路+误删问题+原子性+lua脚本+Redisson功能介绍+可重入锁+WatchDog机制+multiLock)
241 0
|
4月前
|
NoSQL Java Redis
Redis字符串数据类型之INCR命令,通常用于统计网站访问量,文章访问量,实现分布式锁
这篇文章详细解释了Redis的INCR命令,它用于将键的值增加1,通常用于统计网站访问量、文章访问量,以及实现分布式锁,同时提供了Java代码示例和分布式锁的实现思路。
141 0
|
6月前
|
存储 NoSQL 关系型数据库
Redis系列学习文章分享---第一篇(Redis快速入门之初始Redis--NoSql+安装redis+客户端+常用命令)
Redis系列学习文章分享---第一篇(Redis快速入门之初始Redis--NoSql+安装redis+客户端+常用命令)
156 1
|
6月前
|
存储 NoSQL 安全
Redis系列学习文章分享---第十五篇(Redis最佳实践--设计优雅的key+合适的数据结构+持久化如何配置+慢查询问题解决)
Redis系列学习文章分享---第十五篇(Redis最佳实践--设计优雅的key+合适的数据结构+持久化如何配置+慢查询问题解决)
98 1