使用Redis构建文章投票网站(Java)

news/2024/4/28 16:22:02/文章来源:https://blog.csdn.net/weixin_33922670/article/details/88902943

文章投票网站的redis相关Java实现


需求:

1、要构建一个文章投票网站,文章需要在一天内至少获得200张票,才能优先显示在当天文章列表前列。

2、但是为了避免发布时间较久的文章由于累计的票数较多而一直停留在文章列表前列,我们需要有随着时间流逝而不断减分的评分机制。

3、于是具体的评分计算方法为:将文章得到的支持票数乘以一个常量432(由一天的秒数86400除以文章展示一天所需的支持票200得出),然后加上文章的发布时间,得出的结果就是文章的评分。

Redis设计

(1)对于网站里的每篇文章,需要使用一个散列来存储文章的标题、指向文章的网址、发布文章的用户、文章的发布时间、文章得到的投票数量等信息。

记录文章内容散列

为了方便网站根据文章发布的先后顺序和文章的评分高低来展示文章,我们需要两个有序集合来存储文章:
(2)有序集合,成员为文章ID,分值为文章的发布时间。

文章发布时间有序集合

(3)有序集合,成员为文章ID,分值为文章的评分。

文章评分有序集合

(4)为了防止用户对同一篇文章进行多次投票,需要为每篇文章记录一个已投票用户名单。使用集合来存储已投票的用户ID。由于集合是不能存储多个相同的元素的,所以不会出现同个用户对同一篇文章多次投票的情况。

用户投票集合

(5)文章支持群组功能,可以让用户只看见与特定话题相关的文章,比如“python”有关或者介绍“redis”的文章等,这时,我们需要一个集合来记录群组文章。例如 programming群组

群组文章集合

为了节约内存,当一篇文章发布期满一周之后,用户将不能对它进行投票,文章的评分将被固定下来,而记录文章已投票用户名单的集合也会被删除。

代码设计

1.当用户要发布文章时,

(1)通过一个计数器counter执行INCR命令来创建一个新的文章ID。

(2)使用SADD将文章发布者ID添加到记录文章已投票用户名单的集合中,并用EXPIRE命令为这个集合设置一个过期时间,让Redis在文章发布期满一周后自动删除这个集合。

(3)使用HMSET命令来存储文章的相关信息,并执行两ZADD命令,将文章的初始评分和发布时间分别添加到两个相应的有序集合中。

public class Chapter01 {private static final int ONE_WEEK_IN_SECONDS = 7 * 86400;          //文章发布期满一周后,用户不能在对它投票。private static final int VOTE_SCORE = 432;                        //计算评分时间与支持票数相乘的常量,通过将一天的秒数除(86400)以文章展示一天所需的支持票数(200)得出的private static final int ARTICLES_PER_PAGE = 25;/*** 发布并获取文章*1、发布一篇新文章需要创建一个新的文章id,可以通过对一个计数器(count)执行INCY命令来完成。*2、程序需要使用SADD将文章发布者的ID添加到记录文章已投票用户名单的集合中,*   并使用EXPIRE命令为这个集合设置一个过期时间,让Redis在文章发布期满一周之后自动删除这个集合。*3、之后程序会使用HMSET命令来存储文章的相关信息,并执行两个ZADD,将文章的初始评分和发布时间分别添加到两个相应的有序集合里面。*/
public String postArticle(Jedis conn, String user, String title, String link) {//1、生成一个新的文章IDString articleId = String.valueOf(conn.incr("article:"));     //String.valueOf(int i) : 将 int 变量 i 转换成字符串String voted = "voted:" + articleId;//2、添加到记录文章已投用户名单中,conn.sadd(voted, user);//3、设置一周为过期时间conn.expire(voted, ONE_WEEK_IN_SECONDS);long now = System.currentTimeMillis() / 1000;String article = "article:" + articleId;//4、创建一个HashMap容器。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("oppose", "0");articleData.put("votes", "1");//5、将文章信息存储到一个散列里面。//HMSET key field value [field value ...]//同时将多个 field-value (域-值)对设置到哈希表 key 中。//此命令会覆盖哈希表中已存在的域。conn.hmset(article, articleData);//6、将文章添加到更具评分排序的有序集合中//ZADD key score member [[score member] [score member] ...]//将一个或多个 member 元素及其 score 值加入到有序集 key 当中conn.zadd("score:", now + VOTE_SCORE, article);//7、将文章添加到更具发布时间排序的有序集合。conn.zadd("time:", now, article);return articleId;}
}

2.当用户尝试对一篇文章进行投票时,

(1)用ZSCORE命令检查记录文章发布时间的有序集合(redis设计2),判断文章的发布时间是否未超过一周。

(2)如果文章仍然处于可以投票的时间范畴,那么用SADD将用户添加到记录文章已投票用户名单的集合(redis设计4)中。

(3)如果上一步操作成功,那么说明用户是第一次对这篇文章进行投票,那么使用ZINCRBY命令为文章的评分增加432(ZINCRBY命令用于对有序集合成员的分值执行自增操作);

并使用HINCRBY命令对散列记录的文章投票数量进行更新

/*** 对文章进行投票* 实现投票系统的步骤:* 1、当用户尝试对一篇文章进行投票时,程序要使用ZSCORE命令检查记录文字发布时间的有序集合,判断文章的发布时间是否超过一周。* 2、如果文章仍然处于可投票的时间范围之内,那么程序将使用SADD命令,尝试将用户添加到记录文章的已投票用户名单的集合中。* 3、如果投票执行成功的话,那么说明用户是第一次对这篇文章进行投票,程序将使用ZINCYBY命令为文章的评分增加432(ZINCYBY命令用于对有序集合成员的分值进行自增操作),*    并使用HINCRBY命令对散列记录的文章投票数量进行更新(HINCRBY命令用于对散列存储的值执行自增操作)*/
public void articleVote(Jedis conn, String user, String article) {//1、计算文章投票截止时间。long cutoff = (System.currentTimeMillis() / 1000) - ONE_WEEK_IN_SECONDS;//2、检查是否还可以对文章进行投票,(虽然使用散列也可以获取文章的发布时间,但有序集合返回文章发布时间为浮点数,可以不进行转换,直接使用)if (conn.zscore("time:", article) < cutoff){return;}//3、从articleId标识符里面取出文章ID。//nt indexOf(int ch,int fromIndex)函数:就是字符ch在字串fromindex位后出现的第一个位置.没有找到返加-1//String.Substring (Int32)    从此实例检索子字符串。子字符串从指定的字符位置开始。String articleId = article.substring(article.indexOf(':') + 1);//4、检查用户是否第一次为这篇文章投票,如果是第一次,则在增加这篇文章的投票数量和评分。if (conn.sadd("voted:" + articleId, user) == 1) {                       //将一个或多个 member 元素加入到集合 key 当中,已经存在于集合的 member 元素将被忽略。//为有序集 key 的成员 member 的 score 值加上增量 increment 。//可以通过传递一个负数值 increment ,让 score 减去相应的值,//当 key 不存在,或 member 不是 key 的成员时, ZINCRBY key increment member 等同于 ZADD key increment member 。//ZINCRBY salary 2000 tom   # tom 加薪啦!conn.zincrby("score:", VOTE_SCORE, article);//为哈希表 key 中的域 field 的值加上增量 increment 。//增量也可以为负数,相当于对给定域进行减法操作。//HINCRBY counter page_view 200conn.hincrBy(article, "votes", 1L);}
}/*** 投反对票*/
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);}
}

3.我们已经实现了文章投票功能和文章发布功能,接下来就要考虑如何取出评分最高的文章以及如何取出最新发布的文章

(1)我们需要使用ZREVRANGE命令取出多个文章ID。(由于有序集合会根据成员的分值从小到大地排列元素,使用ZREVRANGE以分值从大到小的排序取出文章ID)

(2)对每个文章ID执行一次HGETALL命令来取出文章的详细信息。

这个方法既可以用于取出评分最高的文章,又可以用于取出最新发布的文章。


public List<Map<String,String>> getArticles(Jedis conn, int page) {//调用下面重载的方法return getArticles(conn, page, "score:");
}
/*** 取出评分最高的文章和取出最新发布的文章* 实现步骤:* 1、程序需要先使用ZREVRANGE取出多个文章ID,然后在对每个文章ID执行一次HGETALL命令来取出文章的详细信息,*    这个方法可以用于取出评分最高的文章,又可以用于取出最新发布的文章。* 需要注意的是:* 因为有序集合会根据成员的值从小到大排列元素,所以使用ZREVRANGE命令,已分值从大到小的排列顺序取出文章ID才是正确的做法**/public List<Map<String,String>> getArticles(Jedis conn, int page, String order) {//1、设置获取文章的起始索引和结束索引。int start = (page - 1) * ARTICLES_PER_PAGE;int end = start + ARTICLES_PER_PAGE - 1;//2、获取多个文章ID,Set<String> ids = conn.zrevrange(order, start, end);List<Map<String,String>> articles = new ArrayList<Map<String,String>>();for (String id : ids){//3、根据文章ID获取文章的详细信息Map<String,String> articleData = conn.hgetAll(id);articleData.put("id", id);//4、添加到ArrayList容器中articles.add(articleData);}return articles;
}

4. 对文章进行分组,用户可以只看自己感兴趣的相关主题的文章。

群组功能主要有两个部分:一是负责记录文章属于哪个群组,二是负责取出群组中的文章。

为了记录各个群组都保存了哪些文章,需要为每个群组创建一个集合,并将所有同属一个群组的文章ID都记录到那个集合中。

/*** 记录文章属于哪个群组* 将所属一个群组的文章ID记录到那个集合中* Redis不仅可以对多个集合执行操作,甚至在一些情况下,还可以在集合和有序集合之间执行操作*/
public void addGroups(Jedis conn, String articleId, String[] toAdd) {//1、构建存储文章信息的键名String article = "article:" + articleId;for (String group : toAdd) {//2、将文章添加到它所属的群组里面conn.sadd("group:" + group, article);}
}

由于我们还需要根据评分或者发布时间对群组文章进行排序和分页,所以需要将同一个群组中的所有文章按照评分或者发布时间有序地存储到一个有序集合中。
但我们已经有所有文章根据评分和发布时间的有序集合,我们不需要再重新保存每个群组中相关有序集合,我们可以通过取出群组文章集合与相关有序集合的交集,就可以得到各个群组文章的评分和发布时间的有序集合。

Redis的ZINTERSTORE命令可以接受多个集合和多个有序集合作为输入,找出所有同时存在于集合和有序集合的成员,并以几种不同的方式来合并这些成员的分值(所有集合成员的分支都会视为1)。

对于文章投票网站来说,可以使用ZINTERSTORE命令选出相同成员中最大的那个分值来作为交集成员的分值:取决于所使用的排序选项,这些分值既可以是文章的评分,也可以是文章的发布时间。

如下的示例图,显示了执行ZINTERSTORE命令的过程:

群组文章的有序集合过程

对集合groups:programming和有序集合score:进行交集计算得出了新的有序集合score:programming,它包含了所有同时存在于集合groups:programming和有序集合score:的成员。因为集合groups:programming的所有成员分值都被视为1,而有序集合score:的所有成员分值都大于1,这次交集计算挑选出来的分值为相同成员中的最大分值,所以有序集合score:programming的成员分值实际上是由有序集合score:的成员的分值来决定的。

所以,我们的操作如下:

(1)通过群组文章集合和评分的有序集合或发布时间的有序集合执行ZINTERSTORE命令,而得到相关的群组文章有序集合。

(2)如果群组文章很多,那么执行ZINTERSTORE需要花费较多的时间,为了尽量减少redis的工作量,我们将查询出的有序集合进行缓存处理,尽量减少ZINTERSTORE命令的执行次数。

为了保持持续更新后我们能获取到最新的群组文章有序集合,我们只将结果缓存60秒。

(3)使用上一步的getArticles函数来分页并获取群组文章。

public List<Map<String,String>> getGroupArticles(Jedis conn, String group, int page) {//调用下面重载的方法return getGroupArticles(conn, group, page, "score:");
}/*** 取出群组里面的文章* 为了能够根据评分对群组文章进行排序和分页,网站需要将同一个群组里面的所有文章都按评分有序的存储到一个有序集合内,* 程序需要使用ZINTERSTORE命令选出相同成员中最大的那个分支作为交集成员的值:取决于所使用的排序选项,可以是评分或文章发布时间。*/
public List<Map<String,String>> getGroupArticles(Jedis conn, String group, int page, String order) {//1、为每个群组的每种排列顺序都创建一个键。String key = order + group;//2、检查是否有已缓存的排序结果,如果没有则进行排序。if (!conn.exists(key)) {//3、根据评分或者发布时间对群组文章进行排序ZParams params = new ZParams().aggregate(ZParams.Aggregate.MAX);conn.zinterstore(key, params, "group:" + group, order);//让Redis在60秒之后自动删除这个有序集合conn.expire(key, 60);}//4、调用之前定义的getArticles()来进行分页并获取文章数据return getArticles(conn, page, key);
}

以上就是一个文章投票网站的相关redis实现。

测试代码如下:

public static final void main(String[] args) {new Chapter01().run();
}public void run() {//1、初始化redis连接Jedis conn = new Jedis("localhost");conn.select(15);//2、发布文章String articleId = postArticle(conn, "guoxiaoxu", "A title", "http://www.google.com");System.out.println("我发布了一篇文章,id为:: " + articleId);System.out.println("文章保存的散列格式如下:");Map<String,String> articleData = conn.hgetAll("article:" + articleId);for (Map.Entry<String,String> entry : articleData.entrySet()){System.out.println("  " + entry.getKey() + ": " + entry.getValue());}System.out.println();//2、测试文章的投票过程articleVote(conn, "other_user", "article:" + articleId);String votes = conn.hget("article:" + articleId, "votes");System.out.println("我们为该文章投票,目前该文章的票数 " + votes);assert Integer.parseInt(votes) > 1;//3、测试文章的投票过程articleOppose(conn, "other_user", "article:" + articleId);String oppose = conn.hget("article:" + articleId, "votes");System.out.println("我们为该文章投反对票,目前该文章的反对票数 " + oppose);assert Integer.parseInt(oppose) > 1;System.out.println("当前得分最高的文章是:");List<Map<String,String>> articles = getArticles(conn, 1);printArticles(articles);assert articles.size() >= 1;addGroups(conn, articleId, new String[]{"new-group"});System.out.println("我们将文章推到新的群组,其他文章包括:");articles = getGroupArticles(conn, "new-group", 1);printArticles(articles);assert articles.size() >= 1;
}

参考

1.文章投票网站的redis相关实现(python)

Redis实战相关代码,目前有Java,JS,node,Python

2.Redis 命令参考

代码地址

https://github.com/guoxiaoxu/...

说明

如果你有耐心读到这里,请允许我说明下:

  • 1、本文主题结构参考了文章投票网站的redis相关实现(python)
  • 2、留下重复的注释是为了自己对比,努力让自己变得不一样
  • 3、通过一天的分析、学习。越觉得需要学的东西太多了。而不只是简单的记住几个命令
  • 4、感谢所有人,感谢SegmentFault,让你见证我脱变的过程吧。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_735122.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

无法访问此网站 localhost 拒绝了我们的连接请求

今天部署项目到云服务器上的时候&#xff0c;我改变了端口号&#xff0c;后来就无法访问了QAQ 后来百度到大佬的博客&#xff0c;https://blog.csdn.net/qq_36305327/article/details/73555351?t1506091971926 才知道&#xff0c;错误原因&#xff1a;服务没有开启。 解决办…

seo中的竞价排名是什么

seo中的竞价排名是什么 一、总结 一句话总结&#xff1a;竞价排名的基本特点是按点击付费&#xff0c;推广信息出现在搜索结果中&#xff08;一般是靠前的位置&#xff09;&#xff0c;如果没有被用户点击&#xff0c;则不收取推广费。 搜索引擎的一种推广广告的方式 1、竞价排…

大型门户网站架构设计的可伸缩性

转自&#xff1a;http://siteguide.jzxue.com/jianzhanjingyan/200910/08-2877.html 我们知道&#xff0c;对于一个大型门户网站来说&#xff0c;可伸缩性是非常重要的&#xff0c;怎么样在纵向和横向有良好的可伸缩性&#xff0c;就需要在做架构设计的时候考虑到一个分的原则&…

读书笔记:《亿级流量网站架构核心技术 -- 跟开涛学搭建高可用高并发系统》

from 《亿级流量网站架构核心技术 – 跟开涛学搭建高可用高并发系统》 概述 一个好的设计要做到&#xff0c;解决现有的需求和问题&#xff0c;把控实现和进度风险&#xff0c;预测和规划未来&#xff0c;不要过度设计&#xff0c;从迭代中演进和完善。 在设计系统时&#xf…

大型网站技术架构(五)网站高可用架构

2019独角兽企业重金招聘Python工程师标准>>> 网站的可用性&#xff08;Avaliability&#xff09;描述网站可有效访问的特性。 1、网站可用性的度量与考核 网站不可用时间&#xff08;故障时间&#xff09;故障修复时间点-故障发现&#xff08;报告&#xff09;时间点…

运维实录:服务器优化,网站上线,又崩了?还崩了?异常恢复

本文主要教会大家如何部署上线你的网站&#xff1f;大半夜的数据库又崩了&#xff0c;老板夺命连环call来了&#xff0c;整个人都疯掉了&#xff0c;大半夜去公司维护&#xff1f; 本文以实用为主&#xff0c;跟我学如何选择服务器&#xff1f;CPU核数、内存、带宽、硬盘这么多…

收藏精美网页设计作品的200佳网站推荐(系列四)

这个系列将向大家分享200佳收藏精美网页设计作品的网站。网页设计师们可通过这些网站收集的优秀网页设计作品来获取灵感&#xff0c;进而设计出更加时尚、更有创意的作品&#xff0c;网页设计师也可以把自己得意的作品提交到这些网站&#xff0c;分享给其它的设计师朋友。如果你…

在PEA上海做演讲主题:大型、高负载网站架构和应用初探

主题&#xff1a;大型、高负载网站架构和应用初探时间&#xff1a;30-45分钟 开题&#xff1a;163,sina,sohu等网站他们有很多应用程序都是PHP写的&#xff0c;为什么他们究竟是如何能做出同时跑几千人甚至上万同时在线应用程序呢? 挑选性能更好web服务器 单台 Apache web se…

SHAREPOINT 2007 网站模板(解决方案)安装和卸载

安装SHAREPOINT 模板&#xff0c;在网上下了个BAT文件&#xff0c;内容如下&#xff1a; CodeECHO OFFECHO **********************************ECHO.开始WSS3.0模版安装&#xff08;请将模版文件存放在D:\ServerAdminTemplates目录&#xff09;ECHO *************************…

SharePoint 2007 Restore Site Collection遇到的网站集Url缓存问题【已解决】

这个问题比较奇怪&#xff0c;可以说是SharePoint的一个BUG吧。请看下图&#xff1a; 在Site Collection List里面看不到正常的网站集信息。但是这个Url缺存在于此。 这是正常网站集选择时显示的信息&#xff1a; 在做Site Collection的Restore时候碰到如下错误&#xff1a; 错…

html5代码_HTML5响应式网站拥有哪些优势与特点

在现如今&#xff0c;随着互联网科技飞速的发展和进步&#xff0c;目前说起H5响应式网站&#xff0c;很多人第一时间的反应是&#xff1a;“HTML5网站拥有丰富的展示形式”、“H5网站的功能很多”、“响应式网站能够适应不同屏幕大小分辨率的设备”、“H5响应式网站更易于优化”…

ASP在线音乐系统网站-原创毕业设计作品

前言 随着社会的发展时代的前进&#xff0c;网络的发展也是日新月异&#xff0c;对人类的生产和生活方式产生了很大的影响。人们通过网络彼此沟通和交流&#xff0c;各种各样的网站也随之产生了&#xff0c;比如音乐网站、新闻网站、娱乐网站、政府网站等等&#xff0c;它们的出…

cms php vue 开源_开源PHP组件漏洞曝光,多个运行CMS系统的网站受影响

据外媒报道&#xff0c;研究人员发现&#xff0c;CMS制造商Typo3开发的开源PHP组件PharStreamWrapper存在安全漏洞&#xff0c;运行Drupal、Joomla或Typo3内容管理系统的网站均受影响。图片来源于创客贴据悉&#xff0c;该漏洞由研究人员Daniel Le Gall发现&#xff0c;被命名为…

index.html dreamweaver 设置为主业,在 Dreamweaver 中如何链接页面和内容以及设置网站导航...

了解 Dreamweaver 中的链接和导航功能&#xff0c;以及绝对路径、文档相对路径和站点根目录相对路径。在设置 Dreamweaver 站点以存储网站文档和创建 HTML 页面之后&#xff0c;您将需要创建文档之间的连接。Dreamweaver 提供多种创建链接的方法&#xff0c;可创建指向文档、图…

php代码网站分享

2019独角兽企业重金招聘Python工程师标准>>> http://www.codepearl.com/ 转载于:https://my.oschina.net/pureboys/blog/180329

经典网页设计:10个响应式设计的国外购物网站

今天我想与大家分享电子商务主题的网站设计&#xff0c;更精确地说是为设计在线商店提供新思想。每个人都知道移动技术的市场发展迅速&#xff0c;已经很难找到一个人没有手机的人了。响应设计给我们提供了一个巨大的机遇&#xff1a;站点的访问量的增长和销售收入的增加。在今…

解决网站的并发处理

原文链接http://hi.baidu.com/yjy198125/blog/item/9f1952b4bf22dd768ad4b25e.htmlhttp://www.javaeye.com/topic/235725一个小型的网站&#xff0c;比如个人网站&#xff0c;可以使用最简单的html静态页面就实现了&#xff0c;配合一些图片达到美化效果&#xff0c;所有的页面…

php云人才系统源码_运用ptcms搭建自用无广告网站(附采集规则和源码)

相信很多喜欢看小说的朋友都碰见过这种情况&#xff1a;看小说的时候突然点错了&#xff0c;然后就进入广告&#xff0c;甚至满篇文章都被广告遮完了。今天xiaotaiqi就来分享给大家一个运用ptcms搭建一个自己的小说网站的方法&#xff0c;还可以自动采集。&#xff08;附源码和…

ASP.NET的SEO:Linq to XML---网站地图和RSS Feed

本系列目录网站地图的作用是让搜索引擎尽快的&#xff0c;更多的收录网站的各个网页。这里我们首先要明白一个基本的原理&#xff0c;搜索引擎的爬行方式。整个互联网就像一张纵横交错的“网”&#xff1a;网的各个节点就是各个网页&#xff0c;而各个网页之间通过url相互连接。…

IE无法打开internet网站已终止操作的解决的方法

用IE内核浏览器的朋友&#xff0c;或许不经意间会碰到这样滴问题&#xff1a;打开某个网页时&#xff0c;浏览器“嘣”跳出一个提示框“Internet Explorer无法打开Internet 站点...已终止操作”。而大多数情况下该页面甚至非常可能看起来已经载入完成&#xff0c;内容能够全然显…