一说到Redis,大家首先想到的是非关系型的key-value存储系统,有5种基本数据结构:字符串String、列表List、哈希Hash、集合Set、有序集合 sorted set。除了这些,可能还简单的使用过,仅限于set、get。至于其他的可能在实际项目并没有真正的实现过。
本文将介绍如何使用Redis实现一个简单的文章投票的功能。
投票规则:根据文章的发布时间和当前时间来计算文章的得分,计算方法:把文章的得分数量乘以一个常量,然后加上文章的发布时间,就是文章的投票得分。
需要设计一下可能用到的数据的结构类型。
①文章信息,可以使用哈希存储。数据结构如图:
键为"article:"+文章的ID
②文章发布时间和投票评分,使用有序集合存储。
第一个根据发布时间排序文章的有序集合的key是"time:",成员为文章ID,分值为发布时间;第二个文章得分排序的有序集合的key是"score:",成员是同样是文章ID,分值则为文章的投票得分。
结构如下:
③防止同一用户重复投票,为每一篇文章记录一个已投票用户名单。投票有效期为一周,一周后将不能投票。
已投票用户:
现在知道了文章投票评分的规则,也知道了存储数据需要的数据结构,现在就可以动手开始实现这个投票功能了。
发布文章
发布文章首先要创建一个新的文章ID,可以通过对一个计数器执行INCR命令完成。然后使用SADD命令把文章发布者的ID添加到已投票用户名单的集合里面,并且使用EXIPRE命令为这个集合设置一个过期时间,在文章发布一周后自动删除这个集合。再使用HMSET命令存储文章的信息,并执行两个ZADD命令,把文章的初始得分和发布时间分别添加到两个相应的有序集合中。
发布文章代码实现
public function poster() { $title = $this->request->param('title'); // 文章标题 $user = $this->request->param('user'); // 作者 $link = $this->request->param('link'); // 链接 $redis = SysUtil::getRedis(); $article_id = strtolower($redis->incr("article:")); //生成文章ID $voted = "voted:".$article_id; $redis->sAdd($voted, $user); // 加入已投票用户名单集合 $redis->expire($voted,RedisConstant::ONE_WEEK_IN_SECONDS); // 设置过期时间 $now = time(); $article = "article:".$article_id; $redis->hMSet($article, ["title"=>$title,"link"=>$link,"poster"=>$user,"time"=>$now,"votes"=>1]); // 存储文章信息 $redis->zAdd('score:', ($now+RedisConstant::VOTE_SCORE), $article); // 文章初始得分 $redis->zAdd("time:", $now, $article); // 文章发布时间 echo $article_id; }
文章发布之后就可以对文章进行投票了。当对一篇文章进行投票时,可以使用ZSCORE命令检查记录文章发布时间的集合,判断文章发布是否超过了一周。如果在一周内,则可使用SADD命令把投票用户加入到已投票用户名单集合里面,然后使用ZINCRBY命令为文章增加得分,最后使用HINCRBY命令对hash记录的文章投票数量进行更新。
投票代码实现
public function vote() { $redis = SysUtil::getRedis(); $user = $this->request->param('user'); $article = $this->request->param('article'); $votes = $this->request->param('votes'); $votes = $votes == 1 ? 1: -1; //支持投反对票 1:支持票 -1:反对票 $score = $votes == 1 ? RedisConstant::VOTE_SCORE : -RedisConstant::VOTE_SCORE; $cutOff = time() - RedisConstant::ONE_WEEK_IN_SECONDS; //判断文章投票是否过期 if ($redis->zScore('time:', $article) < $cutOff) { echo "expired"; die; } $article_id = explode(':',$article)[1]; //把投票人加入到已投票用户名单集合 if ($redis->sAdd("voted:".$article_id, $user)) { $redis->zIncrBy("score:", $score,$article); // 为文章增加得分 $redis->hIncrBy($article, 'votes', $votes);// 更新文章得票数量 echo "success"; }else { echo "failure"; } }
现在已经实现了投票和发布文章的功能,下面呢就可以考虑如何实现获取得分最高的文章以及如何获取最新发布的文章了。
可以先试用ZREVRANGE命令获取多个文章ID,然后对每个文章ID执行一次HGETALL命令获取文章的详细信息,这样既可以获取得分最高的文章,又可以获取到最新发布的文章。因为有序集合会根据成员的分值从小到大排序元素,使用ZREVRANGE命令,是以"从大到小"的顺序取出文章ID的。
获取文章功能代码实现
public function get_articles() { $page = $this->request->param('page'); // 页码 $redis = SysUtil::getRedis(); $order = "score:"; $start = ($page - 1) * RedisConstant::ARTICLES_PRE_PAGE; $end = $start + RedisConstant::ARTICLES_PRE_PAGE - 1; $ids = $redis->zRevRange($order,$start, $end); // 获取文章ID $articles = []; foreach ($ids as $val) { $article_data = $redis->hGetAll($val); //获取文章信息 $article_data['id'] = $val; array_push($articles, $article_data); } echo json_encode($articles); }
通过以上三个过程我们就简单的实现了一个文章投票功能。
还可以对文章进行分组,然后获取分组的文章信息。这两个功能源码也有实现,可以参考,也可自己实现。