首先,我得说明这篇博客基本上就是<<redis in action>>第一章内容的读书笔记。

需求

首先,说明一下,我们的需求

用户可以发表文章,发表时,自己就默认的给自己的文章投了一票。

用户在查看网站的文章时,文章当然是按顺序排列的(这个顺序怎么计算呢?我们把文章发表的时间求出来,这个时间就是离1970年的那个秒数,同时,文章每被投票一次,再那个时间的基础上加上一个常量。最后按照所有文章的总得分来排序)。

当然,我也可以就按照发表时间排序。

一篇文章发表后,七天内可以投票,七天过后就不能再投票了。

某个人只能给一篇文章投一次票。

一个文章可以属于很多个组,我们还应该可以在组内排序。

redis的介绍,以及基础操作

http://doc.redisfans.com/

设计方案

首先我们肯定有几个数据域

第一个域,这是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("");

		}
	}

}

参考资料

http://www.cnblogs.com/nick-huang/p/5785304.html

使用redis构建文章投票系统的更多相关文章

  1. 使用Redis构建文章投票网站

    涉及到的key: 1. article_time, 记录文章的发布时间,zset结构 2. article_score, 记录文章的得分, zset结构 得分 = 发布时间 + 投票用户数 X 432 ...

  2. redis 实例2 构建文章投票网站后端

    redis 实例2 构建文章投票网站后端   1.限制条件 一.如果网站获得200张支持票,那么这篇文章被设置成有趣的文章 二.如果网站发布的文章中有一定数量被认定为有趣的文章,那么这些文章需要被设置 ...

  3. Redis实现文章投票功能

    Redis的具体操作这里就不说了,说一下需求和设计思路. 需求:自己实现一个文章投票的功能1.能够按照时间分页倒叙查看文章信息2.能够给文章投票,一个用户给一篇文章只能投票一次3.需要记录分值.每次投 ...

  4. Redis构建文章聚合信息分类网站

    本系列教程内容提要 Java工程师之Redis实战系列教程教程是一个学习教程,是关于Java工程师的Redis知识的实战系列教程,本系列教程均以解决特定问题为目标,使用Redis快速解决在实际生产中的 ...

  5. nginx +lua +redis 构建自动缓存系统

    一. nginx环境搭建 第一步下载 LuaJIT-2.0.4.tar.gz http://luajit.org/download/LuaJIT-2.0.4.tar.gz安装 make &&a ...

  6. php+redis 简易的实现文章发布系统(用户投票系统)

    /** * @data 文章发布 * 文章详情散列表中递增ID,讲文章发布者ID写入投票用户集合中,设置投票时间为一周 * 讲文章内容写入文章散列中,讲文章写入文章评分有序集合和文章发布有序集合中 * ...

  7. Redis in Action 文章投票

    原书用 Python 与 Redis 进行交互,我用 PHP 来实现. 环境:LNMP(CentOS 6.6 + Nginx 1.8.0 + MySQL 5.6.23 + PHP 5.6.9)+ Re ...

  8. 构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(37)-文章发布系统④-百万级数据和千万级数据简单测试

    原文:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(37)-文章发布系统④-百万级数据和千万级数据简单测试 系列目录 我想测试EF在一百万条数据下的显示时间! ...

  9. 构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(36)-文章发布系统③-kindeditor使用

    原文:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(36)-文章发布系统③-kindeditor使用 我相信目前国内富文本编辑器中KindEditor 属于前 ...

随机推荐

  1. eclipse导包导不进来

    今天某个类转移了位置,结果导包导不进来: 解决方法:1.查看本项目中pom的依赖关系,查看是否引用了转移后的项目. 2.查看导不进来的报错类,查看类刚开始import的信息,如果有报错,删除后重新导包 ...

  2. [LeetCode] My Calendar III 我的日历之三

    Implement a MyCalendarThree class to store your events. A new event can always be added. Your class ...

  3. win10配置Memcached及MVC5测试分布式缓存入门

    win10配置Memcached: 1.安装包下载 2.解压后有: 3.以管理员省份运行cmd: 4.安装:输入cmd命令: E:/memcached-amd64/memcached.exe -d  ...

  4. Java爬虫原理分析

    当我们需要从网络上获取资源的时候,我们一般的做法就是通过浏览器打开某个网站,然后将我们需要的东西下载或者保存下来. 但是,当我们需要大量下载的时候,这个时候通过人工一个个的去点击下载,就显得太没有效率 ...

  5. [Luogu 1559]运动员最佳匹配问题

    Description 题库链接 求 \(2\times N\) 个点的带权二分图最佳匹配. \(1\leq N\leq 20\) Solution 我还是太菜了啊...到现在才学 \(KM\) . ...

  6. [USACO09DEC]牛收费路径Cow Toll Paths

    跟所有人一样,农夫约翰以着宁教我负天下牛,休叫天下牛负我的伟大精神,日日夜夜苦思生 财之道.为了发财,他设置了一系列的规章制度,使得任何一只奶牛在农场中的道路行走,都 要向农夫约翰上交过路费. 农场中 ...

  7. 删数方案数(regex)

    [题目描述] 给出一个正整数序列 a,长度为 n,cyb 不喜欢完美,他要删掉一些数(也可以不删,即删掉0个),但是他不会乱删,他希望删去以后,能将 a 分成 2 个集合,使得两个非空集合的数的和相同 ...

  8. ●POJ 2774 Long Long Message

    题链: http://poj.org/problem?id=2774题解: 后缀自动机 使用后缀自动机匹配,思路如下: 即如果当前的x字符匹配失败了,就可以从当前已经匹配的串的后缀去继续匹配. 然后不 ...

  9. ●BZOJ 2002 [Hnoi2010]Bounce 弹飞绵羊

    题链: http://www.lydsy.com/JudgeOnline/problem.php?id=2002 题解: LCT 如果把弹跳的起点和终点连一条边,弹出去的与n+1号点连边, 则不难发现 ...

  10. UVA - 11468:Substring

    随机生成一个字符可以看成在AC自动机里面向前走一个节点,那么ans就是0向前走L步并且不经过单词节点, 由概率知识可得,f[p][L]=∑f[nxt[p][i]][L-1]*g[i] 其中p表示位于p ...