网站降权不更新文章可以吗,网站建设seo方案,nodejs做网站还是app,网站建设 主要内容1. 前言
现在就来实践一下如何使用 Redis 来解决实际问题#xff0c;市面上很多网站都提供了投票功能#xff0c;比如 Stack OverFlow 以及 Reddit 网站都提供了根据文章的发布时间以及投票数计算出一个评分#xff0c;然后根据这个评分进行文章的展示顺序。本文就简单演示…1. 前言
现在就来实践一下如何使用 Redis 来解决实际问题市面上很多网站都提供了投票功能比如 Stack OverFlow 以及 Reddit 网站都提供了根据文章的发布时间以及投票数计算出一个评分然后根据这个评分进行文章的展示顺序。本文就简单演示了如何使用 Redis 构建一个网站投票后端逻辑。
2. 数据结构设计
要想完成这个后端系统我们就需要思考如何设计 Redis 的存储内容及其结构
文章信息包含文章id、标题、内容、作者、投票数、发布时间hash 结构评分排行榜成员是文章id、分数是评分zset 结构发布时间排行榜成员是文章id、分数是发布时间zset 结构文章投票用户集合成员是用户idset 结构
3. 接口设计
想要设计一个投票网站我们就必须限定一些数值和规则条件
用户只能给在发布时间一周内的文章投票每个用户不得重复给一个文章投票
3.1 对文章进行投票
想要对文章进行投票我们前提是需要设定一个评分函数投票数越高评分越高、发布时间越久评分越低我们假设使用以下函数rate 100 * vote_num publish_time
其中
rate表示该文章的评分vote_num: 表示该文章的得票数publish_time: 表示发布时间的 unix 时间戳
详细步骤如下
校验文章发布时间是否已经超过一周校验该用户是否已经给该文章投过票给评分加上 100 使用ZINCRBY命令重新放入 zset 中使用HINCRBY命令修改文章信息将投票数1将投票用户 id 使用SADD命令加入到该文章对应已投票用户集合当中
示例代码如下
const ONE_WEEK_SECONDS 7 * 86400
const ARTICLE_PREFIX article:
const VOTED_USERS_PREFIX voted:
const RATE_SCORE_KEY rate:
const TIME_SCORE_KEY time:
const USER_PREFIX user:
const BASE_SCORE 100 // 基准分// ArticleVote 给文章投票函数
func ArticleVote(articleId string, userId string, client *redis.Client, ctx context.Context) {// 1. 校验文章发布时间是否超过一周var articleKey ARTICLE_PREFIX articleIdresult, _ : client.HGet(ctx, articleKey, publish_time).Result()publishTime, _ : strconv.Atoi(result)if int64(publishTime) time.Now().Unix()-ONE_WEEK_SECONDS {panic(发布时间已经超过一周!)}// 2. 校验用户是否已经投过票了var votedKey VOTED_USERS_PREFIX articleKeyvar userKey USER_PREFIX userIdi, _ : client.SAdd(ctx, votedKey, userKey).Result()if i 0 {// 已经投过票了panic(用户已经投过票!)}// 3. 重新计算文章评分client.ZIncrBy(ctx, RATE_SCORE_KEY, float64(BASE_SCORE), articleKey)// 4. 重置文章得票数client.HIncrBy(ctx, articleKey, vote_num, int64(1))
}注意 实际上我们应该用 redis 的事务保证修改操作的同步但是由于还没有介绍 Lua 脚本之类的知识所以暂不考虑我们常用 “:” 冒号分隔符分隔 key 中的多个标识符 3.2 发布文章
详细步骤如下
构建一个 redis 当中的 hash 结构使用HMSET命令保存到 redis 中键格式为“article:articleId”将发布的用户id保存到文章对应已投票用户集合当中并设置一周的过期时间保存 发布时间-文章id 使用ZADD命令添加到有序集合中保存 评分-文章id 使用ZADD命令添加到有序集合中
// PublishArticle 发布文章
func PublishArticle(articleId string, userId string, article article.Article, client *redis.Client, ctx context.Context) {// 1. 保存文章信息var articleKey ARTICLE_PREFIX articleIdvar publishTime time.Now().Unix()article.PublishTime publishTimearticle.VoteNum 0client.HMSet(ctx, articleKey, article)// 2. 保存发布人到已发布用户集合中并设置过期时间var votedKey VOTED_USERS_PREFIX articleKeyvar voteUser USER_PREFIX userIdclient.SAdd(ctx, votedKey, voteUser)client.Expire(ctx, votedKey, ONE_WEEK_SECONDS*time.Second)// 3.设置初始评分到有序集合中client.ZAdd(ctx, RATE_SCORE_KEY, redis.Z{Member: articleKey,Score: float64(publishTime),})// 4. 设置初始发布时间到有序集合中client.ZAdd(ctx, TIME_SCORE_KEY, redis.Z{Member: articleKey,Score: float64(publishTime),})
}3.3 获取文章
我们已经实现了给文章投票以及发布文章的功能那么写下来就要考虑如何获取评分最高的前 n 个文章以及获取发布时间最新的前 n 个文章了详细流程如下以评分为例
使用zrevrange命令按照 score 从高到低获取score:有序集合中指定数量的成员根据每个成员的文章 id 从article:articleId中使用HGETALL命令获取详细文章数据构建结果返回
// GetArticlesByCondition 根据条件获取特定页文章列表
func GetArticlesByCondition(pageNo int64, scoreCondition string, client *redis.Client, ctx context.Context) []article.Article {// 1. 计算起始和结束索引下标var start (pageNo - 1) * ARTICLES_PER_PAGEvar end start ARTICLES_PER_PAGE - 1// 2. 使用ZREVRANGE命令按照score倒序获取数据// 2.1 先判断是否存在该keyresult, _ : client.Exists(ctx, scoreCondition).Result()if result 0 {// 没有这个有序集合键panic(不存在该有序集合键!)}articleIds, _ : client.ZRevRange(ctx, scoreCondition, start, end).Result()// 3. 根据id获取文章具体内容// 4. 构建响应var articles make([]article.Article, 0, len(articleIds))for _, articleId : range articleIds {articleMap, _ : client.HGetAll(ctx, articleId).Result()var article article.Articlearticle.Id articleMap[id]article.Title articleMap[title]article.Content articleMap[content]publishTime, _ : strconv.ParseInt(articleMap[publish_time], 10, 64)article.PublishTime publishTimevoteNum, _ : strconv.ParseInt(articleMap[vote_num], 10, 64)article.VoteNum voteNumarticles append(articles, article)}return articles
}3.4 给文章分组
3.4.1 添加或删除分组
我们有些时候希望网站能够提供一个分组展示的功能比如Java分组、Go分组等等在 redis 中就可以设计为set集合类型对应 key 为group:group_name我们就需要提供一个往分组中添加或者删除指定文章的功能
构建文章对应 key从addGroups中将文章添加到每个分组中从removeGroups每个分组中删除文章
const GROUP_PREFIX group: // 分组前缀// AddOrRemoveGroups 添加或删除文章到分组中
func AddOrRemoveGroups(articleId string, addGroups []string, removeGroups []string, client *redis.Client, ctx context.Context) {var articleKey ARTICLE_PREFIX articleIdfor _, group : range addGroups {// 添加到分组中client.SAdd(ctx, GROUP_PREFIXgroup, articleKey)}for _, group : range removeGroups {// 从分组中删除client.SRem(ctx, GROUP_PREFIXgroup, articleKey)}
}3.4.2 获取分组文章
我们已经有了对应的分组比如group:test分组成员为article:1现在我们希望能够对某个特定分组当中的文章按照指定 score 进行排序即构建一个新的有序集合我们可以借助ZINTERSTORE命令将rate:有序集合或者time:有序集合中的元素与group:test当中的元素取交集设定 aggregate 为 max 表示得分为较大值除此以外我们还可以缓存过期时间提高效率
检查分组有序集合 key 是否存在若不存在则使用ZINTERSTORE命令构建分组有序集合设定过期时间为 60s复用GetArticlesByCondition方法获取文章列表
const SCORE_GROUP_EXPIRATION 60 // 分组有序集合过期时间// GetGroupArticlesByCondition 根据条件获取分组特定页文章列表
func GetGroupArticlesByCondition(pageNo int64, group string, scoreCondition string, client *redis.Client, ctx context.Context) []article.Article {// 2. 判断是否已经存在该分组下的有序集合var scoreGroupKey scoreCondition groupresult, _ : client.Exists(ctx, scoreGroupKey).Result()if result 0 {// 创建分组评分集合client.ZInterStore(ctx, scoreGroupKey, redis.ZStore{Keys: []string{GROUP_PREFIX group, scoreCondition},Aggregate: max,})// 设置过期时间client.Expire(ctx, scoreGroupKey, SCORE_GROUP_EXPIRATION*time.Second)}// 3. 返回响应return GetArticlesByCondition(pageNo, scoreGroupKey, client, ctx)
}4. 总结
我们可以把上述功能中提到的 redis 命令总结如下
对于hash结构 HMSET批量向 hash 结构插入键值对HGETALL获取 key 对应的 hash 结构全部键值对HINCRBY向 key 对应的 hash 结构特定的键进行自增 对于set集合结构 SADD向 set 结构插入成员SREM从 set 结构中删除成员 对于zset有序集合结构 ZADD向 zset 结构插入成员-分数ZREVRANGE从 zset 结构中按照分数从大到小取出成员ZINCRBY向 zset 结构特定成员分数自增ZINTERSTORE将两个集合进行交集运算得到一个新的 zset 结构 通用命令 EXPIRE对某个 key 设置过期时间单位为 ms EXISTS检查某个 key 是否存在