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

news/2024/5/6 14:23:32/文章来源:https://blog.csdn.net/weixin_33727510/article/details/88985648

需求:

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

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

于是具体的评分计算方法为:将文章得到的支持票数乘以一个常量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命令,将文章的初始评分和发布时间分别添加到两个相应的有序集合中。

import time# 截止时间,一周
ONE_WEEK_IN_SECONDS = 7 * (24 * 60 * 60)
# 计分常量
VOTE_SCORE = 432"""
发布文章@param {object}
@param {string} 用户
@param {string} 文章title
@param@return {string} 文章id
"""
def postArticle(conn, user, title, link):# 创建一个新的文章IDarticle_id = str(conn.incr('article:'))# 将文章发布者ID添加到记录文章已投票用户名单的集合中,并用EXPIRE为这个集合设置过期时间voted = 'voted:' + article_idconn.sadd(voted, user)conn.expire(voted, ONE_WEEK_IN_SECONDS)now = time.time()# 用HMSET存储文章的相关信息article = 'article:' + article_idconn.hmset(article, {'title': title,'link': link,'poster': user,'time': now,'votes': 1})# 执行两个ZADD,将文章的初始评分与发布时间添加到两个相应的有序集合中conn.zadd('time:', article, now)conn.zadd('score:', article, now + VOTE_SCORE)return article_id

2.当用户尝试对一篇文章进行投票时,
(1)用ZSCORE命令检查记录文章发布时间的有序集合(redis设计2),判断文章的发布时间是否未超过一周。
(2)如果文章仍然处于可以投票的时间范畴,那么用SADD将用户添加到记录文章已投票用户名单的集合(redis设计4)中。
(3)如果上一步操作成功,那么说明用户是第一次对这篇文章进行投票,那么使用ZINCRBY命令为文章的评分增加432(ZINCRBY命令用于对有序集合成员的分值执行自增操作);
并使用HINCRBY命令对散列记录的文章投票数量进行更新

"""
用户投票功能@param {object}
@param {string} 用户
@param {string} 文章"""
def voteArticle(conn, user, article):# 判断文章是否超过了投票截止时间,如果超过,则不允许投票outoff = time.time() - ONE_WEEK_IN_SECONDSif conn.zscore('time:', article) < outoff:returnarticle_id = article.partition(':')[-1]# 将用户添加到记录已投票用户名单的集合中if conn.sadd('voted:' + article_id, user):# 增加该文章的评分和投票数量conn.zincrby('score:', article, VOTE_SCORE)conn.hincrby(article, 'votes', 1)

3.我们已经实现了文章投票功能和文章发布功能,接下来就要考虑如何取出评分最高的文章以及如何取出最新发布的文章
(1)我们需要使用ZREVRANGE命令取出多个文章ID。(由于有序集合会根据成员的分值从小到大地排列元素,使用ZREVRANGE以分值从大到小的排序取出文章ID)
(2)对每个文章ID执行一次HGETALL命令来取出文章的详细信息。

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

"""
取出评分最高的文章,或者最新发布的文章@param {object}
@param {int}    页码
@param {string} 有序集合名称,可以是score:,time:@return array
"""
# 每页的文章数
ARTICLES_PER_PAGE = 25def getArticles(conn, page, order = 'score:'):# 获取指定页码文章的起始索引和结束索引start = (page - 1) * ARTICLES_PER_PAGEend   = start + ARTICLES_PER_PAGE - 1# 取出指定位置的文章idarticle_ids = conn.zrevrange(order, start, end)articles = []for id in article_ids:article_data = conn.hgetall(id)article_data['id'] = idarticles.append(article_data)return articles

4. 对文章进行分组,用户可以只看自己感兴趣的相关主题的文章。
群组功能主要有两个部分:一是负责记录文章属于哪个群组,二是负责取出群组中的文章。

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

"""
添加移除文章到指定的群组中@param {object}
@param {int}   文章ID
@param {array} 添加的群组
@param {array} 移除的群组"""
def addRemoveGroups(conn, article_id, to_add = [], to_remove = []):article = 'article:' + article_id# 添加文章到群组中for group in to_add:conn.sadd('group:' + group, article)# 从群组中移除文章for group in to_remove:conn.srem('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函数来分页并获取群组文章。

"""
根据评分或者发布时间对群组文章进行排序和分页@param {object}
@param {int}   文章ID
@param {array} 添加的群组
@param {array} 移除的群组"""
def getGroupArticles(conn, group, page, order = 'score:'):# 群组有序集合名key = order + groupif not conn.exists(key):conn.zinterstore(key, ['group:' + group, order], aggregate = 'max')conn.expire(key, 60)return getArticles(conn, page, key)

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

测试代码如下:

import unittest
class TestArticle(unittest.TestCase):"""初始化redis连接"""def setUp(self):import redisself.conn = redis.Redis(db=15)"""删除redis连接"""def tearDown(self):del self.connprintprint"""测试文章的投票过程"""def testArticleFunctionality(self):conn = self.connimport pprint# 发布文章article_id = str(postArticle(conn, 'username', 'A titile', 'http://www.baidu.com'))print "我发布了一篇文章,id为:", article_idprintself.assertTrue(article_id)article = 'article:' + article_id# 显示文章保存的散列格式print "文章保存的散列格式如下:"article_hash = conn.hgetall(article)print article_hashprintself.assertTrue(article)# 为文章投票voteArticle(conn, 'other_user', article)print '我们为该文章投票,目前该文章的票数:'votes = int(conn.hget(article, 'votes'))print votesprintself.assertTrue(votes > 1)print '当前得分最高的文章是:'articles = getArticles(conn, 1)pprint.pprint(articles)printself.assertTrue(len(articles) >= 1)# 将文章推入到群组addRemoveGroups(conn, article_id, ['new-group'])print "我们将文章推到新的群组,其他文章包括:"articles = getGroupArticles(conn, 'new-group', 1)pprint.pprint(articles)printself.assertTrue(len(articles) >= 1)测试结束,删除所有的数据结构to_del = (conn.keys('time:*') + conn.keys('voted:*') + conn.keys('score:*') + conn.keys('articles:*') + conn.keys('group:*'))if to_del:conn.delete(*to_del)if __name__ == '__main__':unittest.main()

代码地址

https://github.com/NancyLin/r...

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

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

相关文章

网址 管理 php,php-如何在Shopware中管理SEO网址?

似乎Shopware在生成URL时有一些“魔术”,我正在寻找可以向我解释这一点的人.标准模板This is the code生成带有变量的SEO网址,这些变量可以在后端的SEO-router-settings中配置&#xff1a;{* Product name *}{block namefrontend_listing_box_article_name}class"product-…

漫谈《大型网站技术架构》

本文的内容来自阿里巴巴员工李智慧的著作《大型网站技术架构 核心原理与案例分析》&#xff0c;这本书很值得一看&#xff0c;故整理之。 一、大型网站的架构演化 1.1 大型网站软件的特点 高并发、大流量高可用&#xff1a;系统7*24小时不间断提供服务海量数据用户分布广泛安全…

jsoup_解析任意网站,做任意网站客户端

jsoup是一个解析网页源码的开源库&#xff0c;他能按照给定的规则提取出一个网页中的任意元素&#xff0c;和其他网页解析库不同的是&#xff0c;他提取网页内容的方式和css、jquery的选择器非常相似。因此如果你懂得前端的知识&#xff0c;只需根据以下的代码样例就可以在3分钟…

【笔记1-架构演进】从0开始 独立完成企业级Java电商网站开发(服务端)

高大上的淘宝架构 我们以淘宝架构为例&#xff0c;了解下大型的电商项目的服务端的架构是怎样&#xff0c;如图所示 上面是一些安全体系系统&#xff0c;如数据安全体系、应用安全体系、前端安全体系等。中间是业务运营服务系统&#xff0c;如会员服务、商品服务、店铺服务、…

【笔记3-用户模块】从0开始 独立完成企业级Java电商网站开发(服务端)

数据表结构设计 关系设计 为什么不用外键&#xff1f;分库分表有外键会非常麻烦&#xff0c;清洗数据也很麻烦。数据库内置触发器也不适合采用。 查业务问题的后悔药——时间戳create_time 数据创建时间update_time 数据更新时间可以用于查询业务&#xff0c;主要要存储datet…

【笔记4-商品模块】从0开始 独立完成企业级Java电商网站开发(服务端)

分类管理模块 数据表结构设计 分类表 CREATE TABLE、mmall_ category ( id int(11) NOT NULL AUTO_ INCREMENT COMMENT 类别Id&#xff0c; parent_ id int(11) DEFAULT NULL COMMENT 父类 别id当id0时说明是根节点,一级类别 , name varchar(50) DEFAULT NULL COMMENT 类别…

【笔记5-购物车及地址模块】从0开始 独立完成企业级Java电商网站开发(服务端)

购物车模块 数据库表设计 购物车表 CREATE TABLE mmall_ cart ( id int(11) NOT NULL AUTO_ INCREMENT, user_ id int(11) NOT NULL, product_ id int(11) DEFAULT NULL COMMENT 商品id, quantity int(11) DEFAULT NULL COMMENT 数量, checked int(11) DEFAULT NULL COMMEN…

【笔记6-支付及订单模块】从0开始 独立完成企业级Java电商网站开发(服务端)

支付模块 实际开发工作中经常会遇见如下场景&#xff0c;一个支付模块&#xff0c;一个订单模块&#xff0c;有一定依赖&#xff0c;一个同事负责支付模块&#xff0c;另一个同事负责订单模块&#xff0c;但是开发支付模块的时候要依赖订单模块的相关类 &#xff0c;方法&…

【笔记8-Redis分布式锁】从0开始 独立完成企业级Java电商网站开发(服务端)

Redis分布式锁 Redis分布式锁命令 setnx当且仅当 key 不存在。若给定的 key 已经存在&#xff0c;则 setnx不做任何动作。setnx 是『set if not exists』(如果不存在&#xff0c;则 set)的简写,setnx 具有原子性。 getset先 get 旧值&#xff0c;后set 新值&#xff0c;并返回…

多网站项目的 CSS 架构

复杂的 CSS 架构&#xff0c;可不是你在科班里能学到的东西。 我在互联网行业的第四份工作&#xff0c;是在我国一家领先的媒体新闻公司中任职一名 CSS/HTML 专家&#xff0c;我的主要职责就是开发可重用的、可扩展的、用于多网站的 CSS 架构。 在本文中&#xff0c;我将与大家…

部署网站(虚拟主机,MVC站点,前后台,百度编辑器)出现的问题

开发工具&#xff1a;Vs2010 数据库&#xff1a;Sql2008 部署主机&#xff1a;万网虚拟主机&#xff0c;支持.NET 4.0 / 4.5 SQL2008 等.. 出现问题1&#xff1a;无法访问页面 出现问题2:后台无法访问 出现问题3&#xff1a;使用的编辑器是百度的UM&#xff0c;有使用到的地方…

电商详情页缓存架构(一)电商网站的商品详情页架构

小型电商网站的商品详情页的页面静态化架构以及其缺陷 小型电商网站&#xff0c;一般使用页面静态化的方案&#xff0c;提前将数据渲染到模板中。 问题&#xff1a;每次模板变更&#xff0c;模板对应的所有数据需要全部重新渲染 大型电商网站的异步多级缓存构建 nginx 数据本…

云南楚雄做网站找哪家?

2019独角兽企业重金招聘Python工程师标准>>> 云南楚雄做网站哪家强&#xff1f; 点击进入&#xff1a;http://www.anline.cn 转载于:https://my.oschina.net/jiankian/blog/614142

一分钟了解阿里云产品:利用Rsync服务SLB下多台centos服务器网站文件同步更新...

易淘帮使用了SLB负载均衡&#xff0c;为了保证SLB下两台服务器下面的网站文件同步&#xff0c;易淘帮采用了rsync服务进行同步&#xff0c;每三分钟进行同步一次。 简单介绍下reync&#xff0c;rsync—remote synchronize是类unix系统下的数据镜像备份工具&#xff0c;它的特性…

从全球最大同性交友网站抄了一份不一样的2048小游戏

大家好&#xff0c;我是“前端点线面”&#xff0c;一位新生代农民工&#xff0c;欢迎关注我获取最新前端知识和《前端百题斩》pdf版(包括JS基础篇、浏览器篇、网络篇共计50个章节&#xff0c;5万多字)&#xff0c;此外有喜欢划水的老铁们&#xff0c;可以加我进“前端划水群”…

10个大佬经常逛的小网站,各个爆款!!!

欢迎和号主【前端点线面】进群盘算法&#xff0c;此外本号干货满满&#xff1a;14个门类&#xff08;100篇原创&#xff09;内容&#xff08;又干又硬&#xff09;、《前端百题斩》pdf&#xff08;助力薪资double&#xff09;、20篇思维导图&#xff08;知识系统化、记忆简单化…

一步步构建大型网站架构

之前我简单向大家介绍了各个知名大型网站的架构&#xff0c;MySpace的五个里程碑、Flickr的架构、YouTube的架构、PlentyOfFish的架构、WikiPedia的架构。这几个都很典型&#xff0c;我们可以从中获取很多有关网站架构方面的知识&#xff0c;看了之后你会发现你原来的想法很可能…

【问底】徐汉彬:大规模网站架构的缓存机制和几何分形学

【导读】徐汉彬曾在阿里巴巴和腾讯从事4年多的技术研发工作&#xff0c;负责过日请求量过亿的Web系统升级与重构&#xff0c;目前在小满科技创业&#xff0c;从事SaaS服务技术建设。 在过去的工作中&#xff0c;徐汉彬从事各类缓存建设和优化&#xff0c;遇到问题无数&#xf…

npm的gh-pages结合github发布repository网站

如果你在使用github而且创建了一个新的仓库&#xff0c;你会发现&#xff0c;在仓库设置里面有结合github pages发布网站的设置&#xff0c;如下所示&#xff1a; 用过github pages都知道它可以做为静态网站来处理比如html和markdown的文件产生预览效果&#xff0c;我之前做过类…

大型网站技术架构(二)架构模式

2019独角兽企业重金招聘Python工程师标准>>> 每一个模式描述了一个在我们周围不断重复发生的问题及该问题解决方案的核心。这样&#xff0c;你就能一次又一次地使用该方案而不必做重复工作。 所谓网站架构模式即为了解决大型网站面临的高并发访问、海量数据、高可靠…