• Redis in Action 文章投票


    原书用 Python 与 Redis 进行交互,我用 PHP 来实现。

    环境:LNMP(CentOS 6.6 + Nginx 1.8.0 + MySQL 5.6.23 + PHP 5.6.9)+ Redis 3.0.7 + phpredis 2.2.4
    

    首先在 Linux 开启 Redis 服务:

    [root@localhost ~]# cd /usr/local/redis/
    [root@localhost redis]# ./bin/redis-server ./etc/redis.conf 
    [root@localhost redis]# ps aux|grep redis
    

    如果显示:

    root      2239  0.2  0.1  35556  1744 ?        Ssl  12:08   0:00 ./bin/redis-server *:6379          
    root      2243  0.0  0.0   5976   724 pts/0    S+   12:08   0:00 grep redis
    

    说明 Redis 服务已经开启,端口号 6379  

      

    redis.php

    <?php
    
    // 一周的秒数
    $seconds = 7 * 86400;
    define(ONE_WEEK_IN_SECONDS, $seconds);
    // 每投一票文章加的分值
    define(VOTE_SCORE, 432);
    
    // 实例化Redis对象
    $redis = new Redis();
    // 连接到Redis服务器
    $conn = $redis->connect('localhost', 6379);
    

      

    init_data.php 用于添加案例的数据

    <?php
    
    /* 为投票网站准备数据 */
    
    require 'redis.php';
    
    //1.根据发布时间排序文章的有序集合 zset
    $article_info = [
    	'100408'=>[
    			'time'=>strtotime('2016-4-10'),
    			'score'=>1332164063.49
    	],
    	'100635'=>[
    			'time'=>strtotime('2016-4-28'),
    			'score'=>1332174713.47
    	],
    	'100716'=>[
    			'time'=>strtotime('2016-4-29'),
    			'score'=>1332225027.26
    	]
    ];
    
    foreach($article_info as $key => $val) {
    	$redis->zadd('time:', $val['time'], 'article:'.$key);
    }
    
    //2.根据评分排序文章的有序集合 zset
    foreach($article_info as $key => $val) {
    	$redis->zadd('score:', $val['score'], 'article:'.$key);
    }
    
    //3.为某篇文章(id:100408)投过票的用户集合 set
    $users = [234487, 253378, 364680, 132097, 350917];
    foreach($users as $key => $val) {
    	$redis->sadd('voted:100408', $val);
    }
    

      

    vote.php 用于给文章投票,其中文章 id(article_id)和投票用户 id(user_id)通过 get 方式传递(代码清单 1-6 article_vote() 函数

    <?php
    
    header('Content-type:text/html;charset=utf-8');
    
    require 'redis.php';
    
    $user_id = empty($_GET['user_id']) ? 0 : (int)$_GET['user_id'];
    $article_id = empty($_GET['article_id']) ? 0 : (int)$_GET['article_id'];
    
    function article_vote($redis, $user_id, $article_id) {
    	$cutoff = time() - ONE_WEEK_IN_SECONDS;
    	// 127.0.0.1:6379> zscore time article:100408
    	// "1460217600"
    	if(intval($redis->zscore('time:', 'article:'.$article_id)) < $cutoff) {
    		return false;
    	}
    
    	if($redis->sadd('voted:'.$article_id, $user_id)) {
    		// ZINCRBY key increment member
    		// 为有序集 score 的成员 article:100408 的 score 值加上增量 increment
    		$score_new = $redis->zincrby('score:', VOTE_SCORE, 'article:'.$article_id);
    		echo $score_new;
    		// HINCRBY key field increment
    		// 为哈希表key中的域field的值加上增量increment。
    		// 如果用户是第一次为这篇文章投票,那么增加这篇文章的投票数量和评分
    
    	} else {
    		return false;
    	}	
    	return true;
    }
    
    if(! article_vote($redis, $user_id, $article_id)) {
    	echo '投票失败';
    } else {
    	echo '投票成功';
    }
    

      

    执行 http://yourdomain/init_data.php,完成 Redis 的连接和数据的初始化,可以进入 Redis 的客户端查询文章投票积分的有序集合(zset)和文章 100408 的投票用户的集合(set):

    [root@localhost redis]# ./bin/redis-cli
    127.0.0.1:6379> zrange score: 0 -1 withscores
    1) "article:100408"
    2) "1332164063.49"
    3) "article:100635"
    4) "1332174713.47"
    5) "article:100716"
    6) "1332225027.26"
    127.0.0.1:6379> zrange time: 0 -1 withscores
    1) "article:100408"
    2) "1460217600"
    3) "article:100635"
    4) "1461772800"
    5) "article:100716"
    6) "1461859200"
    

      

    然后访问 http://yourdomain/vote.php?user_id=100&article_id=100408 让 user_id 为 100 的用户给编号100408 的文章投票。

    再次进入 Redis 的客户端查询文章投票积分的有序集合(zset)和文章 100408 的投票用户的集合(set):

    127.0.0.1:6379> zrange score: 0 -1 withscores
    1) "article:100408"
    2) "1332164495.49"
    3) "article:100635"
    4) "1332174713.47"
    5) "article:100716"
    6) "1332225027.26"
    127.0.0.1:6379> smembers voted:100408
    1) "100"
    2) "132097"
    3) "234487"
    4) "253378"
    5) "350917"
    6) "364680"
    127.0.0.1:6379> 
    

      

    发布文章 post_article.php(代码清单 1-7 post_article() 函数

    <?php
    
    require 'redis.php';
    
    // @param object $redis redis对象
    // @param int $user_id  用户编号
    // @param string $title 文章标题
    // @param string $link  文章链接
    function post_article($redis, $user_id, $title, $link) {
    	$article_id = $redis->incr('article:'); // 生成新的文章id
    
    	$voted = 'voted:'.$article_id;
    	$redis->sadd($voted, $user_id); // 将发布文章的用户添加到文章已投票的用户名单中
    	$redis->expire($voted, ONE_WEEK_IN_SECONDS); // 将投票名单的过期时间设置为一周
    
    	$now = time();
    	$article = 'article:'.$article_id;
    	$redis->hmset($article, [
    		'title' => $title,
    		'link' => $link,
    		'poster' => $user_id,
    		'time' => $now,
    		'votes'=> 1
    	]); // 将文章的信息存储到一个散列里
    
    	$redis->zadd('score:', $now + VOTE_SCORE, $article); // 把文章添加到根据评分排序的有序集合中
    	$redis->zadd('time:', $now, $article); // 把文章添加到根据发布时间排序的有序集合中
    
    	return $article_id;
    }
    
    $user_id = isset($_GET['user_id']) ? $_GET['user_id'] : 0;
    $mtid = mt_rand(0,999);
    $title = '文章标题'.$mtid;
    $link = 'http://www.youdomain.com/article/'.$mtid;
    if(post_article($redis, $user_id, $title, $link)) {
    	echo 'success';
    } else {
    	echo 'error';
    }
    

    访问:http://yourdomain/post_article.php

    由于 url 不带参数并且 Redis 中不存在 article: ,因此会有一个 user_id 为 0 的用户发布 article:1

    此时查询 Redis 中时间和分数的有序集合、article:1 的投票用户集合以及 article:1 的散列内容:

    127.0.0.1:6379> zrange time: 0 -1 withscores
    1) "article:100408"
    2) "1460217600"
    3) "article:100635"
    4) "1461772800"
    5) "article:100716"
    6) "1461859200"
    7) "article:1"
    8) "1465105632"
    127.0.0.1:6379> zrange score: 0 -1 withscores
    1) "article:100408"
    2) "1332164495.49"
    3) "article:100635"
    4) "1332174713.47"
    5) "article:100716"
    6) "1332225027.26"
    7) "article:1"
    8) "1465106064"
    127.0.0.1:6379> smembers voted:1
    1) "0"
    127.0.0.1:6379> hgetall article:1
     1) "title"
     2) "xe6x96x87xe7xabxa0xe6xa0x87xe9xa2x9868"
     3) "link"
     4) "http://www.youdomain.com/article/68"
     5) "poster"
     6) "0"
     7) "time"
     8) "1465105632"
     9) "votes"
    10) "1"
    

     

    可以发布多篇文章用于测试。

    get_articles.php 获取文章列表(代码清单1-8 get_article() 函数)

    <?php
    
    require 'redis.php';
    
    define(ARTICLES_PER_PAGE, 5); // 每页5条数据
    
    // @param object $redis redis对象
    // @param int $page 当前页
    // @param string $order 根据$order来排序 
    // @return array $articles 文章信息
    function get_articles($redis, $page, $order = 'score:') {
    	$start = ($page - 1) * ARTICLES_PER_PAGE;
    	$end = $start + ARTICLES_PER_PAGE - 1;
    
    	$ids = $redis->zrevrange($order, $start, $end); // 获取多个文件的序号 按 score 值递减(从大到小)来排列。
    	$articles = [];
    	foreach($ids as $id) {
    		$article_data = $redis->hgetall($id);	
    		$article_data['id'] = $id;
    		$articles[] = $article_data;
    	}
    
    	return $articles;
    }
    
    $page = isset($_GET['page']) ? $_GET['page'] : 1;
    $articles = get_articles($redis, $page);
    
    echo '<pre>';
    print_r($articles);
    

    可以通过 http://yourdomain/get_articles.php 或 http://yourdomain/get_articles.php?page = 2 来获取文章列表,可以得到:

    Array
    (
        [0] => Array
            (
                [title] => 文章标题23
                [link] => http://www.youdomain.com/article/23
                [poster] => 106
                [time] => 1465180517
                [votes] => 1
                [id] => article:8
            )
    
        [1] => Array
            (
                [title] => 文章标题719
                [link] => http://www.youdomain.com/article/719
                [poster] => 105
                [time] => 1465180514
                [votes] => 1
                [id] => article:7
            )
    
        [2] => Array
            (
                [title] => 文章标题811
                [link] => http://www.youdomain.com/article/811
                [poster] => 104
                [time] => 1465180511
                [votes] => 1
                [id] => article:6
            )
    
        [3] => Array
            (
                [title] => 文章标题22
                [link] => http://www.youdomain.com/article/22
                [poster] => 103
                [time] => 1465180508
                [votes] => 1
                [id] => article:5
            )
    
        [4] => Array
            (
                [title] => 文章标题350
                [link] => http://www.youdomain.com/article/350
                [poster] => 102
                [time] => 1465180506
                [votes] => 1
                [id] => article:4
            )
    
    )
    

      

    添加文章至分组以及从分组中移除文章(代码清单:1-9 add_remove_groups() 函数)

    <?php
    
    require 'redis.php';
    
    // @param object $redis
    // @param int $artile_id
    // @param array $to_add
    // @param array $to_remove
    function add_remove_groups($redis, $article_id, $to_add = [], $to_remove = []) {
    	$article = 'article:'.$article_id;
    	
    	// 将文章添加到所属的群组
    	if(! empty($to_add)) {
    		foreach($to_add as $group) {
    			$redis->sadd('group:'.$group, $article);
    		}
    	}
    
    	// 将文章从群组里面移除
    	if(! empty($to_remove)) {
    		foreach($to_remove as $group) {
    			$redis->srem('group'.$group, $article);
    		}
    	}
    }
    

    初始化群组数据 init_data_group.php

    <?php
    
    /* 准备群组数据 */
    
    require 'redis.php';
    
    $article_info = ['article:100408', 'article:100635', 'article:5'];
    
    foreach($article_info as $key => $val) {
    	$redis->sadd('group:programming', $val);
    }
    

      

    代码清单 1-10 get_group_articles 函数 

    function get_group_article($redis, $group, $page, $order = 'score:') {
    	$key = $order.$group; // 为每个群组的每种排列顺序都创建一个键
    	if(! $redis->exists($key)) { // 检查是否已有缓存的排序结果,如果没有的话则进行排序
    		$redis->zInter($key, array('group:'.$group, $order), array(1, 1), $aggregate = 'max'); 
    		$redis->expire($key, 60); // 60s之后redis删除有序集合
    	}
    	return get_articles($redis, $page, $key);
    }
    
    $group = 'programming';
    $page = isset($_GET['page']) ? $_GET['page'] : 1;
    $articles = get_group_article($redis, $group, $page);
    
    echo '<pre>';
    print_r($articles);
    

      

       

    附:

    文章案例来自《Redis 实战》

    phpredis 文档:https://github.com/phpredis/phpredis

    Redis 命令参考:http://doc.redisfans.com/sorted_set/zrange.html

    php-redis 中文文档:http://www.cnblogs.com/weafer/archive/2011/09/21/2184059.html

    redis在PHP中的基本使用案例:http://www.t086.com/article/4901

  • 相关阅读:
    【git hub使用】
    【struct2 第一天】
    【JSP基础 第一天】
    【Java基础学习 day01】
    网站建设 【Django】 【MTV】
    Python-Json字符串和XML解析
    Python-冒泡和快排
    Python-面向对象编程
    练习-字符串编码
    练习-统计文件中单词数量
  • 原文地址:https://www.cnblogs.com/dee0912/p/5444780.html
Copyright © 2020-2023  润新知