来自《Redis 实战》第一章
对于 Redis 数据结构类型及使用不太清楚的可以参考 此链接
需求:
- 网站需要根据文章的发布时间和文章获得的投票数量计算出一个评分,然后按照这个评分来决定如何排序和展示文章。
- 得分的计算方式为:将文章得到的支持票数乘以一个常量(假设为432),然后加上文章的发布时间(定为从1970年1月1日到现在经过的秒数),得出的结果就是文章的评分。
- 同时需要提供既能按照发布时间来展示文章,又能根据评分来展示文章的功能。
- 如果文章发布超过一周,则不提供投票功能。
Redis 中结构设计:
文章信息 | |
value 结构 | HASH |
key | article:<文章id> 例:article:100408 (使用冒号来区分名字的不同部分,100408为文章ID) |
value | 保存的键值对包含 标题、链接、发布者、发布时间、得票数 信息 |
文章发布时间信息 | |
value 结构 | ZSET |
key | time: |
value |
成员为 article:<文章id> 分值为 1997年1月1日到现在的秒数 例:article:100408 (成员) - 1332065474.47 (分值) |
文章评分信息 | |
value 结构 | ZSET |
key | score: |
value |
成员为 article:<文章id> 分值为 文章评分 例:article:100408 (成员) - 13320656524.47 (分值) |
投票记录信息 | |
value 结构 | SET |
key | voted:<文章id> 例:voted:100408 |
value | user:<用户id> 例:user:234487 |
Python 实现的代码
import time ONE_WEEK_IN_SECONDS = 7 * 24 * 60 * 60 VOTE_SCORE = 432 ARTICLES_PER_PAGE = 25 ''' 用户对文章进行投票(目前没有考虑事务问题) 用户尝试投票时,使用 ZSCORE 命令检查记录文章发布时间的有序集合,判断文章的发布时间是否未超过一周, 如果仍可投票,使用 SADD 命令,尝试将用户添加到 记录文章已投票用户名单的集合里面, 如果成功,说明此用户是第一次为这个文章投票,此时使用 ZINCRBY 命令为文章添加 432 的评分数, 并使用 HINCRBY 命令对散列记录的文章投票数量进行更新 ''' def article_vote(conn, user, article): # 计算投票的截止时间 cutoff = time.time() - ONE_WEEK_IN_SECONDS if conn.zscore('time:', article) < cutoff: # 超过投票截止时间则直接返回 return # 获取文章 ID 部分 article_id = article.partition(':')[-1] # 尝试将投票用户添加到文章已投票用户集合中 if conn.sadd('voted:' + article_id, user): # 为文章添加评分 conn.zincrby('score:', article, VOTE_SCORE) # 更新文章的得票数(+1) conn.hincrby(article, 'votes', 1) ''' 发布文章 通过对一个计数器执行 INCR 命令来生成文章id, 接着使用 SADD 将文章发布者的id 添加到记录文章已投票用户名单的 集合里面(这个集合需要使用 EXPIRE 命令设置为一周的过期时间,因为只有一周内的文章可以投票), 之后使用 HMSET 命令来存储文章的相关信息,并执行两个 ZADD 命令, 将文章的初始评分和发布时间分别添加到两个相应的有序集合里面 ''' def post_article(conn, user, title, link): # 通过自增方式生成文章 ID article_id = str(conn.incr('article:')) # 生成并添加对文章投票的用户信息,同时设置过期时间 voted = 'voted:' + article_id conn.sadd(voted, user) conn.expire(voted, ONE_WEEK_IN_SECONDS) # 存储文章信息 now = time.time() article = 'article:' + article_id conn.hmset(article, { 'title': title, 'link': link, 'poster': user, 'time': now, 'votes': 1, }) # 保存文章发布时间及评分信息 conn.zadd('score:', article, now + VOTE_SCORE) conn.zadd('time:', article, now) return article_id ''' 获取指定排序后的文章信息 使用 ZREVRANGE 取出多个文章id, 再对每个文章id 执行一次 HGETALL 来取出文章的详细信息 ''' def get_articles(conn, page, order='score:'): start = (page - 1) * ARTICLES_PER_PAGE end = start + ARTICLES_PER_PAGE - 1 # 逆序(从大到小)获取数据 ids = conn.zrevrange(order, start, end) articles = [] for id in ids: article_data = conn.hgetall(id) article_data['id'] = id articles.append(article_data) return articles