Redis in Action 文章投票

原書用 Python 與 Redis 進行交互,我用 PHP 來實現。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 服務:html

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

若是顯示:git

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  github

  

redis.phpredis

<?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):dom

[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):post

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) "\xe6\x96\x87\xe7\xab\xa0\xe6\xa0\x87\xe9\xa2\x9868"
 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

相關文章
相關標籤/搜索