摘要:說明本文主要講述使用作為緩存加快頁面訪問速度。何不用來做緩存,等到該達到一定瀏覽頁面后再刷新下,效率也很高。可作緩存系統(tǒng)隊列系統(tǒng)。
說明:本文主要講述使用Redis作為緩存加快頁面訪問速度。同時,作者會將開發(fā)過程中的一些截圖和代碼黏上去,提高閱讀效率。
備注:作者最近在學(xué)習(xí)github上別人的源碼時,發(fā)現(xiàn)好多在計算一篇博客頁面訪問量view_count時都是這么做的:利用Laravel的事件監(jiān)聽器監(jiān)聽IP訪問該post,然后頁面每訪問一次,都刷新一次MySQL(假設(shè)MySQL)中post表的view_count字段,如果短時間內(nèi)大量的IP來訪問,那效率就不是很高了。何不用Redis來做緩存,等到該post達到一定瀏覽頁面后再刷新下MySQL,效率也很高。
開發(fā)環(huán)境:Laravel5.1+MAMP+PHP7+MySQL5.5
Redis依賴包安裝與配置Redis就和MySQL一樣,都是數(shù)據(jù)庫,只不過MySQL是磁盤數(shù)據(jù)庫,數(shù)據(jù)存儲在磁盤里,而Redis是內(nèi)存數(shù)據(jù)庫,數(shù)據(jù)存儲在內(nèi)存里,不持久化的話服務(wù)器斷電數(shù)據(jù)就被抹掉了。Redis數(shù)據(jù)存儲類型比較多,包括:字符串類型、哈希類型、列表類型、集合類型和有序集合類型,而不像MySQL主要只有三類:字符串類型、數(shù)字類型和日期類型。Redis可作緩存系統(tǒng)、隊列系統(tǒng)。
Redis服務(wù)端安裝首先得在主機上裝下Redis服務(wù)端,以MAC為例,Windows/Linux安裝也很多教程:
brew install redis //設(shè)置電腦啟動時也啟動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 //測試是否安裝成功,出現(xiàn)pong,輸入redis-cli進入redis自帶的終端客戶端 redis-cli ping
主機安裝完,就可以在Laravel環(huán)境安裝下PHP的Redis客戶端依賴包:
composer require predis/predis
predis是用PHP語言寫的一個redis客戶端包,Laravel的Redis模塊依賴于這個包。
phpredis是C語言寫的一個PHP擴展,和predis功能差不多,只不過作為擴展效率高些,phpredis可以作為擴展裝進PHP語言中,不過這里沒用到,就不裝了。
推薦Laravel開發(fā)插件三件套,提高開發(fā)效率,可以參考作者寫的Laravel學(xué)習(xí)筆記之Seeder填充數(shù)據(jù)小技巧:
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文件把緩存驅(qū)動設(shè)為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遷移文件再設(shè)計表字段值,包括seeder填充假數(shù)據(jù),可以參考下這篇文章Laravel學(xué)習(xí)筆記之Seeder填充數(shù)據(jù)小技巧,總之表字段如下:
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("內(nèi)容"); $table->text("origin")->comment("文章來源"); $table->integer("comment_count")->unsigned()->comment("評論次數(shù)"); $table->integer("view_count")->unsigned()->comment("瀏覽次數(shù)"); $table->integer("favorite_count")->unsigned()->comment("點贊次數(shù)"); $table->boolean("published")->comment("文章是否發(fā)布"); $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訪問事件類,然后在事件監(jiān)聽器類里做一些邏輯處理如把訪問量存儲在Redis里。Laravel的事件監(jiān)聽這么做:在EventServiceProvider里定義事件和對應(yīng)的監(jiān)聽器,然后輸入指令:
//app/Providers/EventServiceProvider.php protected $listen = [ "AppEventsPostViewCount" => [ "AppListenersPostEventListener", ], ] //指令 php artisan event:generate
在app/Event和app/Listeners會生成事件類和監(jiān)聽器類。
在PostController寫上showPostCache方法:
const modelCacheExpires = 10; public function showPostCache(Request $request, $id) { //Redis緩存中沒有該post,則從數(shù)據(jù)庫中取值,并存入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(); //觸發(fā)瀏覽量計數(shù)器事件 event(new PostViewCount($post, $ip)); return view("browse.post", compact("post")); }
這里Cache上文已經(jīng)配置了以redis作為驅(qū)動,這里取IP,這樣防止同一IP短時間內(nèi)刷新頁面增加瀏覽量,event()或Event::fire()觸發(fā)事件,把$post和$ip作為參數(shù)傳入,然后再定義事件類:
//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}}
然后重點寫下事件監(jiān)聽器邏輯:
class PostEventListener { /** * 同一post最大訪問次數(shù),再刷新數(shù)據(jù)庫 */ const postViewLimit = 30; /** * 同一用戶瀏覽同一post過期時間 */ const ipExpireSec = 300; /** * Create the event listener. * */ public function __construct() { } /** * Handle the event. * 監(jiān)聽用戶瀏覽事件 * @param PostViewCount $event * @return void */ public function handle(PostViewCount $event) { $post = $event->post; $ip = $event->ip; $id = $post->id; //首先判斷下ipExpireSec = 300秒時間內(nèi),同一IP訪問多次,僅僅作為1次訪問量 if($this->ipViewLimit($id, $ip)){ //一個IP在300秒時間內(nèi)訪問第一次時,刷新下該篇post的瀏覽量 $this->updateCacheViewCount($id, $ip); } } /** * 一段時間內(nèi),限制同一IP訪問,防止增加無效瀏覽次數(shù) * @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中有沒有該鍵,該指令時間復(fù)雜度O(1),Set集合類型中值都是唯一 $existsInRedisSet = Redis::command("SISMEMBER", [$ipPostViewKey, $ip]); if(!$existsInRedisSet){ //SADD,集合類型指令,向ipPostViewKey鍵中加一個值ip Redis::command("SADD", [$ipPostViewKey, $ip]); //并給該鍵設(shè)置生命時間,這里設(shè)置300秒,300秒后同一IP訪問就當做是新的瀏覽量了 Redis::command("EXPIRE", [$ipPostViewKey, self::ipExpireSec]); return true; } return false; } /** * 更新DB中post瀏覽次數(shù) * @param $id * @param $count */ public function updateModelViewCount($id, $count) { //訪問量達到300,再進行一次SQL更新 $postModel = Post::find($id); $postModel->view_count += $count; $postModel->save(); } /** * 不同用戶訪問,更新緩存中瀏覽次數(shù) * @param $id * @param $ip */ public function updateCacheViewCount($id, $ip) { $cacheKey = "post:view:".$id; //這里以Redis哈希類型存儲鍵,就和數(shù)組類似,$cacheKey就類似數(shù)組名,$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的瀏覽量鍵抹掉,等著下一次請求重新開始計數(shù) Redis::command("HDEL", [$cacheKey, $ip]); //同時,抹掉post內(nèi)容的緩存鍵,這樣就不用等10分鐘后再更新view_count了, //如該篇post在100秒內(nèi)就達到了30訪問量,就在3分鐘時更新下MySQL,并把緩存抹掉,下一次請求就從MySQL中請求到最新的view_count, //當然,100秒內(nèi)view_count還是緩存的舊數(shù)據(jù),極端情況300秒內(nèi)都是舊數(shù)據(jù),而緩存里已經(jīng)有了29個新增訪問量 //實際上也可以這樣做:在緩存post的時候,可以把view_count多帶帶拿出來存入鍵值里如single_view_count,每一次都是給這個值加1,然后把這個值傳入視圖里 //或者平衡設(shè)置下postViewLimit和ipExpireSec這兩個參數(shù),對于view_count這種實時性要求不高的可以這樣做來著 //加上laravel前綴,因為Cache::remember會自動在每一個key前加上laravel前綴,可以看cache.php中這個字段:"prefix" => "laravel" Redis::command("DEL", ["laravel:post:cache:".$id]); } }else{ //哈希類型指令HSET,和數(shù)組類似,就像$cacheKey[$ip] = 1; Redis::command("HSET", [$cacheKey, $ip, "1"]); } } }
這里推薦下一本Redis入門書《Redis入門指南》(作者也是咱北航的,軟件學(xué)院的,居然比我小一屆,慚愧。。不過俺們也參與寫過書,哈哈,只是參與,呵呵),快的話看個一兩天就能看完,也就基本入門了。還推薦一個Redis客戶端:Redis Desktop Manager,可以在客戶端里看下各個鍵值:
頁面視圖中可以利用上面推薦的barryvdh/laravel-debugbar插件觀察下請求過程產(chǎn)生的數(shù)據(jù)。第一次請求時會有一次query,然后從緩存里取值沒有query了,直到把緩存中view_count刷到MySQL里再有一次query:
It is working!!!
不知道有沒有說清楚,有疑問或者指正的地方請留言交流吧。
總結(jié):研究Redis和Cache模塊的時候,還看到可以利用Model Observer模型觀察器來監(jiān)聽事件自動刷新緩存,晚上在研究下吧,這兩天也順便把Redis數(shù)據(jù)存儲類型總結(jié)下,到時見。
歡迎關(guān)注Laravel-China。
RightCapital招聘Laravel DevOps
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/61802.html
摘要:說明本文主要學(xué)習(xí)下的模型觀察者,把一點點經(jīng)驗分享出來希望對別人能有幫助。模型觀察者這個功能能做很多事情,比如模型更新時發(fā)個通知??偨Y(jié)本篇文章主要學(xué)了下的模型觀察者,發(fā)現(xiàn)這個功能也能使代碼結(jié)構(gòu)更清晰,覺得挺好的。 說明:本文主要學(xué)習(xí)下Laravel的Model Observer模型觀察者,把一點點經(jīng)驗分享出來希望對別人能有幫助。同時,作者會將開發(fā)過程中的一些截圖和代碼黏上去,提高閱讀效率...
摘要:實際上,在中關(guān)閉主要包括兩個過程保存當前到介質(zhì)中在中存入。,學(xué)習(xí)下關(guān)閉的源碼吧先??傊?,關(guān)閉的第二件事就是給添加。通過對的源碼分析可看出共分為三大步啟動操作關(guān)閉??偨Y(jié)本小系列主要學(xué)習(xí)了的源碼,學(xué)習(xí)了的三大步。 說明:在中篇中學(xué)習(xí)了session的CRUD增刪改查操作,本篇主要學(xué)習(xí)關(guān)閉session的相關(guān)源碼。實際上,在Laravel5.3中關(guān)閉session主要包括兩個過程:保存當前U...
摘要:然后中間件使用方法來啟動獲取實例,使用類來管理主要分為兩步獲取實例,主要步驟是通過該實例從存儲介質(zhì)中讀取該次請求所需要的數(shù)據(jù),主要步驟是。 說明:本文主要通過學(xué)習(xí)Laravel的session源碼學(xué)習(xí)Laravel是如何設(shè)計session的,將自己的學(xué)習(xí)心得分享出來,希望對別人有所幫助。Laravel在web middleware中定義了session中間件IlluminateSess...
摘要:說明在上篇中學(xué)習(xí)了的啟動過程,主要分為兩步,一是的實例化,即的實例化二是從存儲介質(zhì)中讀取的數(shù)據(jù)。第二步就是操作,包括對數(shù)據(jù)的增刪改查操作,本文也主要聊下相關(guān)操作源碼。下篇再學(xué)習(xí)下關(guān)閉,到時見。 說明:在上篇中學(xué)習(xí)了session的啟動過程,主要分為兩步,一是session的實例化,即IlluminateSessionStore的實例化;二是從session存儲介質(zhì)redis中讀取id ...
閱讀 3679·2021-11-16 11:41
閱讀 2885·2021-09-23 11:45
閱讀 691·2019-08-30 15:44
閱讀 542·2019-08-30 13:10
閱讀 1963·2019-08-30 12:49
閱讀 3533·2019-08-28 17:51
閱讀 1479·2019-08-26 12:20
閱讀 704·2019-08-23 17:56