摘要:说明本文主要讲述使用作为缓存加快页面访问速度。何不用来做缓存,等到该达到一定浏览页面后再刷新下,效率也很高。可作缓存系统队列系统。
说明:本文主要讲述使用Redis作为缓存加快页面访问速度。同时,作者会将开发过程中的一些截图和代码黏上去,提高阅读效率。
备注:作者最近在学习github上别人的源码时,发现好多在计算一篇博客页面访问量view_count时都是这么做的:利用Laravel的事件监听器监听IP访问该post,然后页面每访问一次,都刷新一次MySQL(假设MySQL)中post表的view_count字段,如果短时间内大量的IP来访问,那效率就不是很高了。何不用Redis来做缓存,等到该post达到一定浏览页面后再刷新下MySQL,效率也很高。
开发环境:Laravel5.1+MAMP+PHP7+MySQL5.5
Redis依赖包安装与配置Redis就和MySQL一样,都是数据库,只不过MySQL是磁盘数据库,数据存储在磁盘里,而Redis是内存数据库,数据存储在内存里,不持久化的话服务器断电数据就被抹掉了。Redis数据存储类型比较多,包括:字符串类型、哈希类型、列表类型、集合类型和有序集合类型,而不像MySQL主要只有三类:字符串类型、数字类型和日期类型。Redis可作缓存系统、队列系统。
Redis服务端安装首先得在主机上装下Redis服务端,以MAC为例,Windows/Linux安装也很多教程:
brew install redis //设置电脑启动时也启动redis-server ln -sfv /usr/local/opt/redis/*.plist ~/Library/LaunchAgents //通过launchctl启动redis-server launchctl load ~/Library/LaunchAgents/homebrew.mxcl.redis.plist //或者通过配置文件启动 redis-server /usr/local/etc/redis.conf //停止redis-server launchctl unload ~/Library/LaunchAgents/homebrew.mxcl.redis.plist //卸载redis-server $ brew uninstall redis $ rm ~/Library/LaunchAgents/homebrew.mxcl.redis.plist //测试是否安装成功,出现pong,输入redis-cli进入redis自带的终端客户端 redis-cli ping
主机安装完,就可以在Laravel环境安装下PHP的Redis客户端依赖包:
composer require predis/predis
predis是用PHP语言写的一个redis客户端包,Laravel的Redis模块依赖于这个包。
phpredis是C语言写的一个PHP扩展,和predis功能差不多,只不过作为扩展效率高些,phpredis可以作为扩展装进PHP语言中,不过这里没用到,就不装了。
推荐Laravel开发插件三件套,提高开发效率,可以参考作者写的Laravel学习笔记之Seeder填充数据小技巧:
composer require barryvdh/laravel-debugbar --dev composer require barryvdh/laravel-ide-helper --dev composer require mpociot/laravel-test-factory-helper --dev //config/app.php /** *Develop Plugin */ BarryvdhDebugbarServiceProvider::class, MpociotLaravelTestFactoryHelperTestFactoryHelperServiceProvider::class, BarryvdhLaravelIdeHelperIdeHelperServiceProvider::class,
配置下config/cache.php文件把缓存驱动设为redis,还有redis自身配置在config/database.php文件中:
//config/cache.php //"default" => "redis", "default" => env("CACHE_DRIVER", "file"),//或者改下.env文件 "redis" => [ "driver" => "redis", "connection" => "default",//改为连接的实例,就默认连接"default"实例 ], //config/database.php "redis" => [ "cluster" => false, //就做一个实例,名为"default"实例 "default" => [ "host" => env("REDIS_HOST", "localhost"), "password" => env("REDIS_PASSWORD", null), "port" => env("REDIS_PORT", 6379), "database" => 0, ], ],Redis存储浏览量字段
先做个post表,建个post迁移文件再设计表字段值,包括seeder填充假数据,可以参考下这篇文章Laravel学习笔记之Seeder填充数据小技巧,总之表字段如下:
class CreatePostsTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create("posts", function (Blueprint $table) { $table->increments("id"); $table->integer("category_id")->unsigned()->comment("外键"); $table->string("title")->comment("标题"); $table->string("slug")->unique()->index()->comment("锚点"); $table->string("summary")->comment("概要"); $table->text("content")->comment("内容"); $table->text("origin")->comment("文章来源"); $table->integer("comment_count")->unsigned()->comment("评论次数"); $table->integer("view_count")->unsigned()->comment("浏览次数"); $table->integer("favorite_count")->unsigned()->comment("点赞次数"); $table->boolean("published")->comment("文章是否发布"); $table->timestamps(); $table->foreign("category_id") ->references("id") ->on("categories") ->onUpdate("cascade") ->onDelete("cascade"); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table("posts", function(Blueprint $tabel){ $tabel->dropForeign("posts_category_id_foreign"); }); Schema::drop("posts"); } }
做一个控制器和一个路由:
php artisan make:controller PostController
Route::get("post/{id}", "PostController@showPostCache");
利用Laravel的事件模块,来定义一个IP访问事件类,然后在事件监听器类里做一些逻辑处理如把访问量存储在Redis里。Laravel的事件监听这么做:在EventServiceProvider里定义事件和对应的监听器,然后输入指令:
//app/Providers/EventServiceProvider.php protected $listen = [ "AppEventsPostViewCount" => [ "AppListenersPostEventListener", ], ] //指令 php artisan event:generate
在app/Event和app/Listeners会生成事件类和监听器类。
在PostController写上showPostCache方法:
const modelCacheExpires = 10; public function showPostCache(Request $request, $id) { //Redis缓存中没有该post,则从数据库中取值,并存入Redis中,该键值key="post:cache".$id生命时间10分钟 $post = Cache::remember("post:cache:".$id, self::modelCacheExpires, function () use ($id) { return Post::whereId($id)->first(); }); //获取客户端IP $ip = $request->ip(); //触发浏览量计数器事件 event(new PostViewCount($post, $ip)); return view("browse.post", compact("post")); }
这里Cache上文已经配置了以redis作为驱动,这里取IP,这样防止同一IP短时间内刷新页面增加浏览量,event()或Event::fire()触发事件,把$post和$ip作为参数传入,然后再定义事件类:
//app/Events/PostViewCount.php /** * @var Post */ public $post; /** * @var string */ public $ip; /** * Create a new event instance. * * @param Post $post * @param string $ip */ public function __construct(Post $post, $ip) { $this->post = $post; $this->ip = $ip; }
顺便也把视图简单写下吧:
Bootstrap Template Title:{{$post->title}}
Summary:{{$post->summary}}
Content:{{$post->content}}
然后重点写下事件监听器逻辑:
class PostEventListener { /** * 同一post最大访问次数,再刷新数据库 */ const postViewLimit = 30; /** * 同一用户浏览同一post过期时间 */ const ipExpireSec = 300; /** * Create the event listener. * */ public function __construct() { } /** * Handle the event. * 监听用户浏览事件 * @param PostViewCount $event * @return void */ public function handle(PostViewCount $event) { $post = $event->post; $ip = $event->ip; $id = $post->id; //首先判断下ipExpireSec = 300秒时间内,同一IP访问多次,仅仅作为1次访问量 if($this->ipViewLimit($id, $ip)){ //一个IP在300秒时间内访问第一次时,刷新下该篇post的浏览量 $this->updateCacheViewCount($id, $ip); } } /** * 一段时间内,限制同一IP访问,防止增加无效浏览次数 * @param $id * @param $ip * @return bool */ public function ipViewLimit($id, $ip) { // $ip = "1.1.1.6"; //redis中键值分割都以:来做,可以理解为PHP的命名空间namespace一样 $ipPostViewKey = "post:ip:limit:".$id; //Redis命令SISMEMBER检查集合类型Set中有没有该键,该指令时间复杂度O(1),Set集合类型中值都是唯一 $existsInRedisSet = Redis::command("SISMEMBER", [$ipPostViewKey, $ip]); if(!$existsInRedisSet){ //SADD,集合类型指令,向ipPostViewKey键中加一个值ip Redis::command("SADD", [$ipPostViewKey, $ip]); //并给该键设置生命时间,这里设置300秒,300秒后同一IP访问就当做是新的浏览量了 Redis::command("EXPIRE", [$ipPostViewKey, self::ipExpireSec]); return true; } return false; } /** * 更新DB中post浏览次数 * @param $id * @param $count */ public function updateModelViewCount($id, $count) { //访问量达到300,再进行一次SQL更新 $postModel = Post::find($id); $postModel->view_count += $count; $postModel->save(); } /** * 不同用户访问,更新缓存中浏览次数 * @param $id * @param $ip */ public function updateCacheViewCount($id, $ip) { $cacheKey = "post:view:".$id; //这里以Redis哈希类型存储键,就和数组类似,$cacheKey就类似数组名,$ip为$key.HEXISTS指令判断$key是否存在$cacheKey中 if(Redis::command("HEXISTS", [$cacheKey, $ip])){ //哈希类型指令HINCRBY,就是给$cacheKey[$ip]加上一个值,这里一次访问就是1 $incre_count = Redis::command("HINCRBY", [$cacheKey, $ip, 1]); //redis中这个存储浏览量的值达到30后,就往MySQL里刷下,这样就不需要每一次浏览,来一次query,效率不高 if($incre_count == self::postViewLimit){ $this->updateModelViewCount($id, $incre_count); //本篇post,redis中浏览量刷进MySQL后,把该篇post的浏览量键抹掉,等着下一次请求重新开始计数 Redis::command("HDEL", [$cacheKey, $ip]); //同时,抹掉post内容的缓存键,这样就不用等10分钟后再更新view_count了, //如该篇post在100秒内就达到了30访问量,就在3分钟时更新下MySQL,并把缓存抹掉,下一次请求就从MySQL中请求到最新的view_count, //当然,100秒内view_count还是缓存的旧数据,极端情况300秒内都是旧数据,而缓存里已经有了29个新增访问量 //实际上也可以这样做:在缓存post的时候,可以把view_count多带带拿出来存入键值里如single_view_count,每一次都是给这个值加1,然后把这个值传入视图里 //或者平衡设置下postViewLimit和ipExpireSec这两个参数,对于view_count这种实时性要求不高的可以这样做来着 //加上laravel前缀,因为Cache::remember会自动在每一个key前加上laravel前缀,可以看cache.php中这个字段:"prefix" => "laravel" Redis::command("DEL", ["laravel:post:cache:".$id]); } }else{ //哈希类型指令HSET,和数组类似,就像$cacheKey[$ip] = 1; Redis::command("HSET", [$cacheKey, $ip, "1"]); } } }
这里推荐下一本Redis入门书《Redis入门指南》(作者也是咱北航的,软件学院的,居然比我小一届,惭愧。。不过俺们也参与写过书,哈哈,只是参与,呵呵),快的话看个一两天就能看完,也就基本入门了。还推荐一个Redis客户端:Redis Desktop Manager,可以在客户端里看下各个键值:
页面视图中可以利用上面推荐的barryvdh/laravel-debugbar插件观察下请求过程产生的数据。第一次请求时会有一次query,然后从缓存里取值没有query了,直到把缓存中view_count刷到MySQL里再有一次query:
It is working!!!
不知道有没有说清楚,有疑问或者指正的地方请留言交流吧。
总结:研究Redis和Cache模块的时候,还看到可以利用Model Observer模型观察器来监听事件自动刷新缓存,晚上在研究下吧,这两天也顺便把Redis数据存储类型总结下,到时见。
欢迎关注Laravel-China。
RightCapital招聘Laravel DevOps
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/61802.html
摘要:说明本文主要学习下的模型观察者,把一点点经验分享出来希望对别人能有帮助。模型观察者这个功能能做很多事情,比如模型更新时发个通知。总结本篇文章主要学了下的模型观察者,发现这个功能也能使代码结构更清晰,觉得挺好的。 说明:本文主要学习下Laravel的Model Observer模型观察者,把一点点经验分享出来希望对别人能有帮助。同时,作者会将开发过程中的一些截图和代码黏上去,提高阅读效率...
摘要:实际上,在中关闭主要包括两个过程保存当前到介质中在中存入。,学习下关闭的源码吧先。总之,关闭的第二件事就是给添加。通过对的源码分析可看出共分为三大步启动操作关闭。总结本小系列主要学习了的源码,学习了的三大步。 说明:在中篇中学习了session的CRUD增删改查操作,本篇主要学习关闭session的相关源码。实际上,在Laravel5.3中关闭session主要包括两个过程:保存当前U...
摘要:然后中间件使用方法来启动获取实例,使用类来管理主要分为两步获取实例,主要步骤是通过该实例从存储介质中读取该次请求所需要的数据,主要步骤是。 说明:本文主要通过学习Laravel的session源码学习Laravel是如何设计session的,将自己的学习心得分享出来,希望对别人有所帮助。Laravel在web middleware中定义了session中间件IlluminateSess...
摘要:说明在上篇中学习了的启动过程,主要分为两步,一是的实例化,即的实例化二是从存储介质中读取的数据。第二步就是操作,包括对数据的增删改查操作,本文也主要聊下相关操作源码。下篇再学习下关闭,到时见。 说明:在上篇中学习了session的启动过程,主要分为两步,一是session的实例化,即IlluminateSessionStore的实例化;二是从session存储介质redis中读取id ...
阅读 3674·2021-11-16 11:41
阅读 2879·2021-09-23 11:45
阅读 686·2019-08-30 15:44
阅读 538·2019-08-30 13:10
阅读 1960·2019-08-30 12:49
阅读 3525·2019-08-28 17:51
阅读 1470·2019-08-26 12:20
阅读 697·2019-08-23 17:56