使用redis构建文章投票系统
首先,我得说明这篇博客基本上就是<<redis in action>>第一章内容的读书笔记。
需求
首先,说明一下,我们的需求
用户可以发表文章,发表时,自己就默认的给自己的文章投了一票。
用户在查看网站的文章时,文章当然是按顺序排列的(这个顺序怎么计算呢?我们把文章发表的时间求出来,这个时间就是离1970年的那个秒数,同时,文章每被投票一次,再那个时间的基础上加上一个常量。最后按照所有文章的总得分来排序)。
当然,我也可以就按照发表时间排序。
一篇文章发表后,七天内可以投票,七天过后就不能再投票了。
某个人只能给一篇文章投一次票。
一个文章可以属于很多个组,我们还应该可以在组内排序。
redis的介绍,以及基础操作
设计方案
首先我们肯定有几个数据域
第一个域,这是zset(有序集合)----score:
member是文章的id,score是文章的得分(发表时间+得票数*一个常量)
第二个域,也是zset(有序集合)----time:
member是文章的id,score是文章的发表时间
第三个域,是hash(散列)-------article:id
key就是文章标题,文章的作者,发表时间,获得的投票数等等
第四个域,是set(无序集合)-----voted:articleId
里面存储的是给这篇文章投过票的所有用户的id号
第五个域,是string------article:
这个域用来获得最新发表的文章的id号
第六个域,是set------group:groupname
里面存储的属于这个组的所有文章的id
发表文章
private static final int ONE_WEEK_IN_SECONDS = 7 * 86400;
public String postArticle(Jedis conn, String user, String title, String link) {
//获得最新的id
String articleId = String.valueOf(conn.incr("article:"));
String voted = "voted:" + articleId;
//自己给自己的文章投票
conn.sadd(voted, user);
//一篇文章只有在发布七天内才能投票
conn.expire(voted, ONE_WEEK_IN_SECONDS);
//保存文章到散列表
long now = System.currentTimeMillis() / 1000;
String article = "article:" + articleId;
HashMap<String,String> articleData = new HashMap<String,String>();
articleData.put("title", title);
articleData.put("link", link);
articleData.put("user", user);
articleData.put("now", String.valueOf(now));
articleData.put("votes", "1"); //自己投的第一票
conn.hmset(article, articleData);
//加了一个数据域 key是score: member是文章 score是文章的得分
//发布的时候 默认作者本人为文章投票
conn.zadd("score:", now + VOTE_SCORE, article);
//加了一个数据域 key是time: member是文章 score是文章的发布时间
conn.zadd("time:", now, article);
return articleId;
}
给文章投票
//每一票对应的常量
private static final int VOTE_SCORE = 432;
public void articleVote(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("voted:" + articleId, user) == 1) {
conn.zincrby("score:", VOTE_SCORE, article);
conn.hincrBy(article, "votes", 1l);
}
}
一篇文章 可以分为多个组
public void addGroups(Jedis conn, String articleId, String[] toAdd) {
String article = "article:" + articleId;
for (String group : toAdd) {
conn.sadd("group:" + group, article);
}
}
按照顺序,获得文章(所有组的文章统一排序)
page是分页查询,order可以分两种,score和time,前者还考虑到了投票,后者就完全按照时间排序
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;
}
按照顺序,获得某一组内的文章
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); //放进缓存 60s后过期
}
return getArticles(conn, page, key);
}
这里现在出现问题了
ZParams params = new ZParams().aggregate(ZParams.Aggregate.MAX);
conn.zinterstore(key, params, "group:" + group, order);
这是什么鬼?
OK,我们知道,我们之前设计了五个域
我们可以很容易获得某个组内所有文章
也能很方便的知道全局范围内某种顺序(score或者time)下前n个文章
问题是,我们现在没有设计某个组范围内的某个顺序下的文章呀
zinterstore简单的说就是关系型数据库里面的关联表操作
左边的"表"存放的是programing这个组下的文章信息
中间这个"表"里面存放的是系统内每篇文章的得分
这是zinterstore方法的声明:
Open Declaration Long redis.clients.jedis.Jedis.zinterstore(String dstkey, ZParams params, String... sets)
我们要联立的是score和group:programing这个两个表
group:programing这个是一个无序set,没有score这个项呀?redis就默认它等于1
params使用的聚合是max这个型
那么score:programing这个有序set"表"的项,就是score和groups:programing这个两个表的交集
同时score:programing这个有序表的score列就是取group:programing表与score:表的最大值。group:programing的缺省值是1,那么也就是说score:programing的score就是score:表的score
好绕口呀
那么params还有什么型呢?
public class ZParams {
public enum Aggregate {
SUM, MIN, MAX; //猜一下sum是什么意思
public final byte[] raw;
Aggregate() {
raw = SafeEncoder.encode(name());
}
}
// .....
}
反对票
对了,如果要设置投反对票的功能,一个人只能反对一篇文章一次,怎么做,那简单呀
在建立一个set域,key就是oppose:atrticleId
里面存放的就是反对这篇文章的用户的信息
//每一票对应的常量
private static final int VOTE_SCORE = 432;
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);
}
}
搞定!
/////////////////////////////////////////////////////////
以下为2016-10-19日更新
我们查看getArticles的代码,分析一下,
第一次交互和redis交互,获得了id名
后面还有25次交互获得了文章的详细信息
一共交互了26次
那么有没有更好的方法呢?
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);
Map<String,Response<Map<String, String>>> map=uesPipeline(conn,ids);
List<Map<String,String>> articles = new ArrayList<Map<String,String>>();
for (Entry<String, Response<Map<String, String>>> entry : map.entrySet()){
String id=entry.getKey();
Map<String, String> article=entry.getValue().get();
article.put("id", id);
articles.add(article);
}
return articles;
}
private Map<String,Response<Map<String, String>>> uesPipeline(Jedis conn, Set<String> ids) {
Pipeline p=conn.pipelined();
Map<String,Response<Map<String, String>>> map =
new HashMap<String, Response<Map<String, String>>>();
for (String id : ids){
Response<Map<String, String>> articleData= p.hgetAll(id);
map.put(id, articleData);
}
p.sync();
return map;
}
本来我们是发送一条请求,redis就处理一条,然后给我们返回结果
使用了管道后,在调用管道的sync方法前所有的请求都先储存在本地,调用了sync后,再一并发送到redis服务端,从原来的26次交互变成了2次交互,效率自然就提高了#
以上为2016-10-19日更新
/////////////////////////////////////////////////////////
所有代码
package redisinaction;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.ZParams;
import java.util.*;
public class Chapter01 {
private static final int ONE_WEEK_IN_SECONDS = 7 * 86400;
private static final int VOTE_SCORE = 432;
private static final int ARTICLES_PER_PAGE = 25;
public String postArticle(Jedis conn, String user, String title, String link) {
String articleId = String.valueOf(conn.incr("article:"));
String voted = "voted:" + articleId;
conn.sadd(voted, user);
conn.expire(voted, ONE_WEEK_IN_SECONDS);
long now = System.currentTimeMillis() / 1000;
String article = "article:" + articleId;
HashMap<String,String> articleData = new HashMap<String,String>();
articleData.put("title", title);
articleData.put("link", link);
articleData.put("user", user);
articleData.put("now", String.valueOf(now));
articleData.put("votes", "1");
conn.hmset(article, articleData);
conn.zadd("score:", now + VOTE_SCORE, article);
conn.zadd("time:", now, article);
return articleId;
}
public void articleVote(Jedis conn, String user, String article) {
long cutoff = (System.currentTimeMillis() / 1000) - ONE_WEEK_IN_SECONDS;
if (conn.zscore("time:", article) < cutoff){
return;
}
String articleId = article.substring(article.indexOf(':') + 1);
if (conn.sadd("voted:" + articleId, user) == 1) {
conn.zincrby("score:", VOTE_SCORE, article);
conn.hincrBy(article, "votes", 1l);
}
}
public List<Map<String,String>> getArticles(Jedis conn, int page) {
return getArticles(conn, page, "score:");
}
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;
}
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:");
}
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);
}
}
测试代码
package redisinaction;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.junit.BeforeClass;
import org.junit.Test;
import redis.clients.jedis.Jedis;
/**
* This class is used for ...
* @author dlf(460795365@qq.com)
* @version 1.0, 2016年10月17日 下午10:15:58
*/
public class Chapter01Test {
Jedis conn = null;
@BeforeClass
public void initConn(){
System.out.println("test before");
conn = new Jedis("10.150.0.80");
conn.auth("dlf123123");
}
//@Test
public void testPostArticle(){
Chapter01 c= new Chapter01();
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date2=sdf.format(date);
String articleId = c.postArticle(conn, "dlf", "A title"+date2, "http://www.google.com");
System.out.println(articleId);
}
@Test
public void testGetAllArticles(){
List<Map<String,String>> result= new Chapter01().getArticles(conn, 1);
for(Map<String, String> map:result){
Iterator<Entry<String, String>> iterator= map.entrySet().iterator();
while(iterator.hasNext()){
Entry<String, String> entry= iterator.next();
System.out.print(entry.getKey()+":"+entry.getValue()+" ");
}
System.out.println("");
}
}
}
参考资料
使用redis构建文章投票系统的更多相关文章
- 使用Redis构建文章投票网站
涉及到的key: 1. article_time, 记录文章的发布时间,zset结构 2. article_score, 记录文章的得分, zset结构 得分 = 发布时间 + 投票用户数 X 432 ...
- redis 实例2 构建文章投票网站后端
redis 实例2 构建文章投票网站后端 1.限制条件 一.如果网站获得200张支持票,那么这篇文章被设置成有趣的文章 二.如果网站发布的文章中有一定数量被认定为有趣的文章,那么这些文章需要被设置 ...
- Redis实现文章投票功能
Redis的具体操作这里就不说了,说一下需求和设计思路. 需求:自己实现一个文章投票的功能1.能够按照时间分页倒叙查看文章信息2.能够给文章投票,一个用户给一篇文章只能投票一次3.需要记录分值.每次投 ...
- Redis构建文章聚合信息分类网站
本系列教程内容提要 Java工程师之Redis实战系列教程教程是一个学习教程,是关于Java工程师的Redis知识的实战系列教程,本系列教程均以解决特定问题为目标,使用Redis快速解决在实际生产中的 ...
- nginx +lua +redis 构建自动缓存系统
一. nginx环境搭建 第一步下载 LuaJIT-2.0.4.tar.gz http://luajit.org/download/LuaJIT-2.0.4.tar.gz安装 make &&a ...
- php+redis 简易的实现文章发布系统(用户投票系统)
/** * @data 文章发布 * 文章详情散列表中递增ID,讲文章发布者ID写入投票用户集合中,设置投票时间为一周 * 讲文章内容写入文章散列中,讲文章写入文章评分有序集合和文章发布有序集合中 * ...
- Redis in Action 文章投票
原书用 Python 与 Redis 进行交互,我用 PHP 来实现. 环境:LNMP(CentOS 6.6 + Nginx 1.8.0 + MySQL 5.6.23 + PHP 5.6.9)+ Re ...
- 构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(37)-文章发布系统④-百万级数据和千万级数据简单测试
原文:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(37)-文章发布系统④-百万级数据和千万级数据简单测试 系列目录 我想测试EF在一百万条数据下的显示时间! ...
- 构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(36)-文章发布系统③-kindeditor使用
原文:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(36)-文章发布系统③-kindeditor使用 我相信目前国内富文本编辑器中KindEditor 属于前 ...
随机推荐
- 【转】如何使用slave_exec_mode优雅的跳过1032 1062的复制错误
今天线上的主从复制发生1062的错误,使用sql_slave_skip_counter跳过之后,由于后面的事务需要对刚刚的数据进行update,后续造成了新的1032的错误. 后来,无意中发现还有更好 ...
- Java知IO
---恢复内容开始--- Java将IO(文件.网络.终端)封装成非常多的类,看似繁杂,其实每个类的具有独特的功能. 按照存取的对象是二进制还是文本,java使用字节流和字符流实现IO. 流是java ...
- Css实现checkbox及radio样式自定义
前言 checkbox和radio样式自定义在网页中是很常见的, 比如在进行表单输入时性别的选择,用户注册时选择已阅读用户协议.随着用户对产品体验要求越来越高,我们都会对checkbox和radio重 ...
- 个人建站&mac下安装hexo
title: 个人建站&mac下安装hexo date: 2018-04-18 16:34:02 tags: [mac,blog,个人建站,markdown] --- 这两天使用了markdo ...
- [HDU 2036]改革春风吹满地
Description “ 改革春风吹满地,不会AC没关系;实在不行回老家,还有一亩三分地.谢谢!(乐队奏乐)”话说部分学生心态极好,每天就知道游戏,这次考试如此简单的题目,也是云里雾里,而且,还竟然 ...
- bzoj 3214: [Zjoi2013]丽洁体
Description 平时的练习和考试中,我们经常会碰上这样的题:命题人给出一个例句,要我们类比着写句子.这种往往被称为仿 写的题,不单单出现在小学生的考试中,也有时会出现在中考中.许多同学都喜欢做 ...
- bzoj 2435: [Noi2011]道路修建
Description 在 W 星球上有 n 个国家.为了各自国家的经济发展,他们决定在各个国家 之间建设双向道路使得国家之间连通.但是每个国家的国王都很吝啬,他们只愿 意修建恰好 n – 1条双向道 ...
- ●UVa 1589 Xiangqi(模拟)
●赘述题意 给出一个中国象棋残局,告诉各个棋子的位置,黑方只有1枚“将”,红方有至少2枚,至多7枚棋子,包含1枚“帅G”,和若干枚“车R”,“马H”,“炮C”.当前为黑方的回合,问黑方的“将”能否在移 ...
- NOIP2014-5-24模拟赛
Problem 1 护花(flower.cpp/c/pas) [题目描述] 约翰留下他的N(N<=100000)只奶牛上山采木.他离开的时候,她们像往常一样悠闲地在草场里吃草.可是,当他回来的时 ...
- [HNOI2009]最小圈
题目描述 对于一张有向图,要你求图中最小圈的平均值最小是多少,即若一个圈经过k个节点,那么一个圈的平均值为圈上k条边权的和除以k,现要求其中的最小值 输入输出格式 输入格式: 第一行2个正整数,分别为 ...