今天下午发了一篇Redis实战运用之HASH居然被被官方推荐,有了100多个阅读,居然还有4个收藏,意料之外更是给了我一个小惊喜,特别是哪4个收藏的,特别感谢。
今天开始之后争取以后每天分享一篇,虽然我也是个菜鸟,写出来东西可能也有各种问题,望各位批评指正。但至少还是能给一些人一点帮助,也是极好的。
好了废话基本说完了,下面具体说果图网(http://www.guotuw.com)功能点的具体实现: redis安装配置,自己去谷歌。开发语言:PHP 框架:YII2 运行环境:LNMP 。
2、redis数据结构:LIST的运用
简约图解一把:
话说我的果图网里面有大量的美女写真图片,大概150G的图片。假设我现在来抓取我自己这个网站的图片我就会用到LIST。PHP有点基础的人就能知道file_get_contents,fgets,fsockopen,curl_init等相关方法就可以简单的获取到目标网页的源码,然后通过正则、XPATH、phpQuery等等手段匹配到网页中的图片地址,然后呢,放到数据库? 这当然可以,但这不是重点。
此时,我甚至可以把所有图片地址放入一个LIST(分段同时放入多个LIST,也可以算是解决阻塞的一个方法),然后多线程不断的从LIST的一端取出(LPOP,RPOP这两个方法都可以)单个图片链接抓取,抓取失败的图片地址,放到LIST的另一端,程序一直跑就行了,直到全部图片下载完成。
其实吧,以上方法就实现了一个‘队列’,看到这里聪明的你就可以举一反三了。例如抢购、处理订单、消息排行等需要“排队处理“的一些数据就可以用LIST来实现了。
更实在一点的去看我新闻列表其实就是一个链表实现的。完整代码如下:
/*
* 新闻列表页面
*/
public function actionIndex()
{
$page = (int)Yii::$app->request->get('page') ?: 1;
//列表新闻
$result = News::pageNews($page, 10,['id','image','title','content','desc','create_time','view']);
$params['news'] = $result['data'];
$params['pageString'] = $this->createPageString($pagination);//分页不是重点 ^_^
return $this->render('index', $params);
}
//分页获取新闻数据同时缓存数据,这个方法写道新闻的MODEL里面。
public function pageNews($currentPage = 1,$pageSize = 10, $fields = []){
$redisKey = 'newsList';
$exists = Yii::$app->get('redis')->exists($redisKey);
if(intval($exists) == 0){
//我的数据不多一波查询没毛病
$newsIds = News::find()
->select('id')
->where(['status' => NEWS::STATUS_ACTIVVE])
->orderBy(['id' => SORT_DESC])
->asArray()
->all();
if(!empty($newsIds)){
foreach($newsIds as $newsId){
Yii::$app->get('redis')->lPush($redisKey,$newsId['id']);
}
//缓存个12个小时,过期时间或者不过期按照自己的需求来设定即可,redis也可以指定过期时间点 expireAt
Yii::$app->get('redis')->expire($redisKey,12 * 60 * 60);
}
}
//RedisService::pageData()和News::formatNews()下面有具体方法体
$pageNewsData = RedisService::pageData($redisKey,$currentPage,$pageSize);
!empty($pageNewsData['data']) && $pageNewsData['data'] = News::formatNews($pageNewsData['data'],$fields);
return $pageNewsData;
}
/**
* @desc 分页获取LIST、ZSET数据,可做为公共方法
* 上面代码中RedisService::pageData的方法体,这里运用了List的常用方LRANGE,LLEN。
* @param string $redisKey
* @param int $currentPage
* @param int $perPage
* @param string $type
* @param string $sort
* @return mixed
*/
public static function pageData($redisKey = '',$currentPage = 1, $perPage = 10,$type = 'list' ,$sort = 'desc'){
$pagination['currentPage'] = $currentPage;
$pagination['perPage'] = $perPage;
$pagination['totalPage'] = $pagination['totalCount'] = 0;
$rs['data'] = [];
$redis = Yii::$app->redis;
if(strtolower($type) == 'list'){
$pagination['totalCount'] = (int)$redis->Llen($redisKey);
$pagination['totalPage'] = ceil($pagination['totalCount'] / $perPage);
$start = ($currentPage - 1) * $perPage;
$end = $currentPage * $perPage - 1;
$rs['data'] = $redis->LRange($redisKey,$start,$end);
}else if(strtolower($type) == 'zset'){ //SORT SET下一篇讲解
$pagination['totalCount'] = $redis->ZCard($redisKey);
$pagination['totalPage'] = ceil($pagination['totalCount'] / $perPage);
$start = ($currentPage - 1) * $perPage;
$end = $currentPage * $perPage - 1;
if(strtolower($sort) == 'desc'){
$rs['data'] = $redis->ZRevrange($redisKey,$start,$end);
}else{
$rs['data'] = $redis->zRange($redisKey, $start, $end);
}
}
$rs['pagination'] = $pagination;
return $rs;
}
/**
* @desc 批量格式化新闻数据
* @param array $newIDs
* @param array $fields
* @return array
*/
public static function formatNews($newIDs = [],$fields = []){
$rs = [];
if($newIDs){
empty($fields) && $fields = static::$fields;
foreach ($newIDs as $id){
$formatNewInfo = static::getNewsInfoById($id,$fields);
if($formatNewInfo) $rs[] = $formatNewInfo;
}
}
return $rs;
}
//根据ID获取新闻详情,结合上一篇讲解的HASH使用
public static function getNewsInfoById($newId = 0,$fields = []){
$newId = intval($newId);
if($newId < 1) return [];
empty($fields) && $fields = static::$fields;
$newInfo = RedisService::getHash("news:".$newId,$fields);
if(empty($newInfo)){
$_newInfo = News::findOne($newId);
if($_newInfo){
RedisService::setHash("news:".$newId,$_newInfo->attributes);
$newInfo = ArrayHelper::filter($_newInfo->attributes,$fields);
}
}
$newInfo && $newInfo = static::formatNewInfo($newInfo);//格式化新闻数据
return $newInfo;
}
以上代码有点长,不过还是可以看一起。当然这只是最常规基础的用法,先基础,后面自己可以逐步深入。
即使如此,配合之前的Redis实战运用之HASH其实已经基本上实现果图网新闻模块的全部功能啦,新闻列表和新闻详情详情的请求数据基本上都只是从缓存中读取了,缓存也会自动更新了。
/*
* 新闻详情页面 详情分页问题
*/
public function actionInfo()
{
$id = (int)Yii::$app->request->get('id');
$params['newInfo'] = News::getNewsInfoById($id);
if (empty($params['newInfo'])) $this->redirect('site/error');
News::incrementView($id); //添加浏览量
return $this->render('info', $params);
}
至于LIST操作的其他命令,这些操作都常用而且又都非常简单。具体如下:
序号 | 命令及描述 |
---|---|
1 | BLPOP key1 [key2 ] timeout 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 |
2 | BRPOP key1 [key2 ] timeout 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 |
3 | BRPOPLPUSH source destination timeout 从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 |
4 | LINDEX key index 通过索引获取列表中的元素 |
5 | LINSERT key BEFORE|AFTER pivot value 在列表的元素前或者后插入元素 |
6 | LLEN key 获取列表长度 |
7 | LPOP key 移出并获取列表的第一个元素 |
8 | LPUSH key value1 [value2] 将一个或多个值插入到列表头部 |
9 | LPUSHX key value 将一个值插入到已存在的列表头部 |
10 | LRANGE key start stop 获取列表指定范围内的元素 |
11 | LREM key count value 移除列表元素 |
12 | LSET key index value 通过索引设置列表元素的值 |
13 | LTRIM key start stop 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。 |
14 | RPOP key 移除并获取列表最后一个元素 |
15 | RPOPLPUSH source destination 移除列表的最后一个元素,并将该元素添加到另一个列表并返回 |
16 | RPUSH key value1 [value2] 在列表中添加一个或多个值 |
17 | RPUSHX key value 为已存在的列表添加值 |
基本上看方法名字就能知道其含义,有空自己去测试下,很快就能熟练运用了。
注:上面提到采集150G图片的采集,其实单机也就跑了2天而已, 并且保存对应关系,看写真列表这个页面就很明显了,如果有感兴趣的我就把全部代码都贴上来。^_^