使用redis构建文章投票系统

释放双眼,带上耳机,听听看~!

本博客代码都是参考《Redis IN ACTION》这本书,由于书中代码都是python所写,所以本文代码为java语言编写,方便读者查阅


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
1public class Chapter01
2{
3    private static final int ONE_WEEK_IN_SECONDS = 7 * 86400; //用来计算超过7天之后的文章不可以投票
4    private static final int VOTE_SCORE = 432; //432来源 这个常量是通过将一天的秒数(86400)除以文章展示一天所需的支持票数量(200 -支持票超过200就算有趣文章)得出的,文章每获得一张支持票,程序要需要将文章的评分增加432分
5    private static final int ARTICLES_PER_PAGE = 25;
6
7    public static final void main(String[] args) {
8        new Chapter01().run();
9    }
10
11    public void run() {
12        Jedis conn = new Jedis("192.168.32.128");
13        conn.select(15);
14
15        // 初始化10篇文章
16//        for(int i=1;i<11;i++)
17//        {
18//            String articleId = postArticle(conn, "username_"+i, "A title"+i);
19//            System.out.println("create a new article: " + articleId);
20//            Map<String, String> articleData = conn.hgetAll("article:" + articleId);
21//            for(Map.Entry<String, String> entry : articleData.entrySet())
22//            {
23//                System.out.println("  " + entry.getKey() + ": " + entry.getValue());
24//            }
25//        }
26
27        // 投票代码开始
28//        articleVote(conn, "jack", "article:1");
29//        articleVote(conn, "jack", "article:2");
30//        articleVote(conn, "jack", "article:3");
31//        articleVote(conn, "jack", "article:4");
32//        articleVote(conn, "jack", "article:5");
33//        articleVote(conn, "jack", "article:6");
34//        articleVote(conn, "jack", "article:8");
35//        articleVote(conn, "jack", "article:9");
36//        articleVote(conn, "jack1", "article:1");
37//        articleVote(conn, "jack2", "article:1");
38//        articleVote(conn, "jack3", "article:1");
39//        articleVote(conn, "jack4", "article:1");
40//        articleVote(conn, "jack2", "article:3");
41//        articleVote(conn, "jack3", "article:3");
42//        String votes = conn.hget("article:1", "votes");
43//        System.out.println("We voted for the article, it now has votes: " + votes);
44
45        //得到从高到低的排名
46//        System.out.println("The currently highest-scoring articles are:");
47//        List<Map<String,String>> articles = getArticles(conn, 1);
48//        printArticles(articles);
49
50        // 给文章分组
51//        addGroups(conn, "1", new String[]{"jack01-group","jack02-group"});
52//        addGroups(conn, "2", new String[]{"jack01-group","jack02-group"});
53//        addGroups(conn, "5", new String[]{"jack01-group","jack02-group"});
54//        addGroups(conn, "6", new String[]{"jack01-group","jack03-group"});
55//        addGroups(conn, "7", new String[]{"jack03-group","jack02-group"});
56//        addGroups(conn, "10", new String[]{"jack01-group","jack03-group"});
57//        addGroups(conn, "8", new String[]{"jack03-group","jack02-group"});
58//        addGroups(conn, "9", new String[]{"jack03-group","jack02-group"});
59//        addGroups(conn, "4", new String[]{"jack03-group","jack02-group"});
60
61        val articles = getGroupArticles(conn, "jack03-group", 1);
62        printArticles(articles);
63    }
64
65    /**
66     * 1,生成文章ID
67     * 2,将发布者ID增加到已投票用户名单集合中
68     * 3,使用HMSET命令来存储文章相关信息
69     * 4,将文章初始评分和发布时间分别添加到2个相应的有序集合中
70     * @return
71     */
72    public String postArticle(Jedis conn, String userName, String title) {
73        String articleId = String.valueOf(conn.incr("article:"));
74
75        String voted = "voted:" + articleId;
76
77        //set
78        conn.sadd(voted, userName);
79        conn.expire(voted, ONE_WEEK_IN_SECONDS);
80
81        ////hash
82        long now = System.currentTimeMillis() / 1000;
83        String article = "article:" + articleId;
84        HashMap<String,String> articleData = new HashMap<String,String>();
85        articleData.put("title", title);
86        articleData.put("user", userName);
87        articleData.put("now", String.valueOf(now));
88        conn.hmset(article, articleData);
89
90        //zset
91        conn.zadd("score:", now + VOTE_SCORE, article);
92        //发布的时候 默认作者本人为文章投票
93        conn.zadd("time:", now, article);
94
95        return articleId;
96    }
97
98    /**
99     * 文章投票
100     * 1,每一票对应一个常量
101     * @return
102     */
103    public void articleVote(Jedis conn, String user, String article) {
104        long cutoff = (System.currentTimeMillis() / 1000) - ONE_WEEK_IN_SECONDS;
105        if (conn.zscore("time:", article) < cutoff){ //计算是否超过文章投票截止时间
106            System.out.println("文章发布时间太长了");
107            return;
108        }
109
110        String articleId = article.substring(article.indexOf(':') + 1);
111        if (conn.sadd("voted:" + articleId, user) == 1) { //如果值不==1 则用户已经投过此文章了  1 标识原set中没有,本次添加成功
112            //增加score的分值,使其增加VOTE_SCORE
113            conn.zincrby("score:", VOTE_SCORE, article);
114            //修改文章中votes的票数,使其增加1
115            conn.hincrBy(article, "votes", 1);
116        }
117    }
118
119    /**
120     * 分页数据
121     * @param conn
122     * @param page
123     * @return
124     */
125    public List<Map<String,String>> getArticles(Jedis conn, int page) {
126        return getArticles(conn, page, "score:");
127    }
128
129    /**
130     * 根据order获取排名靠前的数据
131     * @param conn
132     * @param page
133     * @param order
134     * @return
135     */
136    public List<Map<String,String>> getArticles(Jedis conn, int page, String order) {
137        int start = (page - 1) * ARTICLES_PER_PAGE;
138        int end = start + ARTICLES_PER_PAGE - 1;
139
140        Set<String> ids = conn.zrevrange(order, start, end);
141        List<Map<String,String>> articles = new ArrayList<Map<String,String>>();
142        for (String id : ids){
143            Map<String,String> articleData = conn.hgetAll(id);
144            articleData.put("id", id);
145            articles.add(articleData);
146        }
147
148        return articles;
149    }
150
151    /**
152     * 文章分组
153     * @return
154     */
155    public void addGroups(Jedis conn, String articleId, String[] toAdd) {
156        String article = "article:" + articleId;
157        for (String group : toAdd) {
158            conn.sadd("group:" + group, article);
159        }
160    }
161
162    public List<Map<String,String>> getGroupArticles(Jedis conn, String group, int page) {
163        return getGroupArticles(conn, group, page, "score:");
164    }
165
166    /**
167     * 按顺序得到某一组内的文章
168     * 这里会使用到关系型数据库里面的\
169     * Redis Zinterstore 命令计算给定的一个或多个有序集的交集 默认情况下,结果集中某个成员的分数值是所有给定集下该成员分数值之和。
170     * 使用 WEIGHTS 选项,你可以为 每个 给定有序集 分别 指定一个乘法因子(multiplication factor),每个给定有序集的所有成员的 score 值在传递给聚合函数(aggregation function)之前都要先乘以该有序集的因子。
171     * 使用 AGGREGATE 选项,你可以指定并集的结果集的聚合方式。默认使用的参数 SUM ,可以将所有集合中某个成员的 score 值之 和 作为结果集中该成员的 score 值;使用参数 MIN ,可以将所有集合中某个成员的 最小 score 值作为结果集中该成员的 score 值;而参数 MAX 则是将所有集合中某个成员的 最大 score 值作为结果集中该成员的 score 值。
172     * @return
173     */
174    public List<Map<String,String>> getGroupArticles(Jedis conn, String group, int page, String order) {
175        String key = order + group;
176        if (!conn.exists(key)) {
177            ZParams params = new ZParams().aggregate(ZParams.Aggregate.MAX);
178            conn.zinterstore(key, params, "group:" + group, order);
179            conn.expire(key, 60);
180        }
181        return getArticles(conn, page, key);
182    }
183
184    private void printArticles(List<Map<String,String>> articles){
185        for (Map<String,String> article : articles){
186            System.out.println("  id: " + article.get("id"));
187            for (Map.Entry<String,String> entry : article.entrySet()){
188                if (entry.getKey().equals("id")){
189                    continue;
190                }
191                System.out.println("    " + entry.getKey() + ": " + entry.getValue());
192            }
193        }
194    }
195}
196

文章中redis采用单机模式,这里小编在写上面代码的时候遇到一个问题,至今没找到原因,有路过的大神希望不吝赐教:

一开始上面代码是使用springboot + rediscluster 模式实现,但是使用redistemplate 执行 zinterstore 的时候报出  


1
2
1ZINTERSTORE can only be executed when all keys map to the same slot
2

 

使用redis构建文章投票系统

我们知道redis cluster 值是有16384个卡槽分布在集群的master上存储数据的,每个master分别存储部分数据,这里难道需要我把数据集中到一个slot中才能调用此方法吗?找了很久也没找到合适的解决方案,有了解过的朋友希望留言告知。
开开心心编码,快快乐乐生活。

给TA打赏
共{{data.count}}人
人已打赏
安全运维

OpenSSH-8.7p1离线升级修复安全漏洞

2021-10-23 10:13:25

安全运维

设计模式的设计原则

2021-12-12 17:36:11

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索