今天下午发了一篇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, $fields = [],$pageSize = 10){ $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 为已存在的列表添加值 |
基本上看方法名字就能知道其含义,有空自己去测试下,很快就能熟练运用了。
篇幅又有点长了,里面涉及到了一个SORT SET。下一篇讲解SORT SET。
注:上面提到采集150G图片的采集,其实单机也就跑了2天而已, 并且保存对应关系,看写真列表这个页面就很明显了,如果有感兴趣的我就把全部代码都贴上来。^_^