原始碼地址
https://github.com/lyn510/laravel-auth-use...
摘要
在laravel系統上線執行後我們發現,使用者的每一次訪問,都需要向資料庫請求,驗證其身份。對於使用者較多的線上程式,頻繁訪問users表,成為系統的一個效能門檻。
本文所述方法 採用redis快取auth訪問所需的Eloquent User Model,又採取observer跟蹤更新這個model ,減少在登陸時對模型的頻繁訪問。本教程也含對passport API適配的情況。
實踐驗證,這能顯著減少資料庫負擔,降低系統運維成本,改善使用者訪問體驗。
內容目錄:
閱讀前需求
- 本教程預設使用者已安裝composer
- 本教程預設使用者已為本地環境配置mysql
- 本教程預設使用者已為本地環境配置redis,包括配置predis和安裝redis-cli。
- 本教程預設使用者已有基礎的laravel經驗,知道如何本地serve程式,怎樣在頁面中開啟自己的laravel工程。
前言
隨著網站使用者的增加,運維負擔也不斷加大。因為laravel預設的auth方法會在每次訪問時對資料庫進行請求來獲取user model,對users表的頻繁訪問成為了網站效能的一個瓶頸。
一個常用的辦法,是通過cache,對user model進行快取,避免在每次開啟頁面的時候,都訪問一次資料庫的users表格,有效減少database query。
在laravel討論社群,對這個方法進行實踐的教程非常少。一個比較有用的教程來自 https://paulund.co.uk/laravel-cache-authus... ,但實踐發現這個教程遺漏了通過token獲取快取model的辦法,導致如果單純依靠它,並不能在日常使用中成功快取user model。
實踐中,我們在上述教程的基礎上做了一些延伸,增加了通過token獲取快取model的辦法。另外,增加了將這個辦法擴充至passport配置API後端的部分。
我們將在這個問題上獲得的一些經驗分享,希望給更多的開發者朋友帶來幫助。
為了方便理解,本教程將帶領讀者在全新空白laravel工程上,一步步配置。
內容或有疏漏,懇請指正。
正文
在空白laravel5.7程式裡,配置auth、cache、database等基本內容
首先安裝空白laravel 5.7教程
$ composer create-project --prefer-dist laravel/laravel laravel-auth-user "5.7.*"
安裝auth
$ php artisan make:auth
配置mysql
在本地新建名為laravel-auth-user
的mysql資料庫,修改.env
檔案,和本地mysql資料庫進行連線:
...
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel-cache-auth
DB_USERNAME=root
DB_PASSWORD=
...
資料庫遷移
$ php artisan migrate
serve程式,開啟頁面嘗試本地註冊,順利註冊,可以登陸。
安裝laravel-debuglar,觀察訪問中所進行的database query的數量。
備註:laravel-debuglar是一個非常好用的工具,可以觀察到目前使用了多少個介面、訪問多少次資料庫,指令具體是什麼,耗費時間是多少,在優化時經常使用。這個包強烈建議只安裝在dev環境,否則會有洩漏敏感資料的危險。
$ composer require barryvdh/laravel-debugbar --dev
安裝後,我們會發現,頁面下方出現紅色的debug條目,點開可以檢視當前頁面query數量。在登入狀態下,使用者訪問任何介面,都會顯示database query數量為1,訪問了users表。目前這個訪問是比較快的。但當users表增大時,這個數字會顯著增加。
安裝predis
$ composer require predis/predis
配置redis,修改.env
檔案
...
CACHE_DRIVER=redis
CACHE_PREFIX=laravel-cache-user
...
因為目前程式並沒有使用cache的地方,為了直接測試是否關聯成功,我們後臺登陸檢視。
$ php artisan tinker
在顯示的介面中隨便快取一個內容
>>> Cache::put('data','success',1)
然後獲取這個內容
>>> Cache::get('data')
應該會顯示
>>> Cache::put('data','success',1)
=> null
>>> Cache::get('data')
=> "success"
>>>
這說明cache配置成功。
在上述工程基礎上,增加Cache-User的具體辦法
在上面的內容中,將user model進行cache。
首先,我們要將User Model快取起來放到cache裡,在需要的時候去排程它。
建立檔案:app\Helpers\CacheUser.php
<?php
namespace App\Helpers;
use Cache;
use App\User;
use Auth;
class CacheUser{ //cache-user class
public static function user($id){
if(!$id||$id<=0||!is_numeric($id)){return;} // if $id is not a reasonable integer, return false instead of checking users table
return Cache::remember('cachedUser.'.$id, 30, function() use($id) {
return User::find($id); // cache user instance for 30 minutes
});
}
}
將這個class的簡稱加入列表,方便呼叫。修改config\app.php
'aliases' => [
...
'CacheUser' => App\Helpers\CacheUser::class,
...
接著,我們需要確保,每當UserModel受到修改的時候,這個快取的模型也會同步更新,避免內容失效。這是通過建立observer來實現的。建立檔案app\Observers\UserObserver.php
。
<?php
namespace App\Observers;
use Cache;
use App\User;
/**
* User observer
*/
class UserObserver
{
public function updated(User $user) // whenever there's update of user, renew cached instance
{
Cache::put("cachedUser.{$user->id}", $user, 30);
}
}
將這個observer登記起來,讓它自動執行。
修改app\Providers\AppServiceProvider.php
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\Observers\UserObserver;
use App\User;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* @return void
*/
public function register()
{
//
}
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
User::observe(UserObserver::class);
}
}
最後,我們需要讓Auth方法在檢查的時候,從快取的user而非資料庫users表來獲取需要的模型
新建檔案app\Auth\CacheUserProvider.php
<?php
namespace App\Auth;
use App\User;
use Illuminate\Auth\EloquentUserProvider;
use Illuminate\Contracts\Hashing\Hasher as HasherContract;
use Illuminate\Support\Facades\Cache;
use CacheUser;
/**
* Class CacheUserProvider
* @package App\Auth
*/
class CacheUserProvider extends EloquentUserProvider
{
/**
* CacheUserProvider constructor.
* @param HasherContract $hasher
*/
public function __construct(HasherContract $hasher)
{
parent::__construct($hasher, User::class);
}
/**
* @param mixed $identifier
* @return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function retrieveById($identifier)
{
return CacheUser::user($identifier);
}
public function retrieveByToken($identifier, $token)
{
$model = CacheUser::user($identifier);
if (! $model) {
return null;
}
$rememberToken = $model->getRememberToken();
return $rememberToken && hash_equals($rememberToken, $token) ? $model : null;
}
}
上面兩個方法中,retrieveById
在使用者輸入使用者名稱密碼登入時排程。retrieveByToken
在使用者後續登陸時通過token比對排程。兩個方法的改寫,保證使用者登陸使用的是被快取的使用者模型。
為了將CacheUserProvider註冊到auth中進行呼叫,修改檔案app\Providers\AuthServiceProvider.php
<?php
namespace App\Providers;
use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use App\Auth\CacheUserProvider;
use Illuminate\Support\Facades\Auth;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* @var array
*/
protected $policies = [
'App\Model' => 'App\Policies\ModelPolicy',
];
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Auth::provider('cache-user', function() {
return resolve(CacheUserProvider::class);
});
}
}
接著修改config\auth.php
...
'providers' => [
'users' => [
'driver' => 'cache-user', // modify to use cached user instance
'model' => App\User::class,
],
...
serve頁面,登入狀態下,重新整理後可以發現,query數量變成0,但不影響各種訪問。
如何將它適配Passport
最後講一下如何適配passport,實際上就是普通passport適配的基本教程,參見laravel官方文件。
$ composer require laravel/passport:^7.0
這一步注意,現行passport版本不支援laravel5.7框架,安裝時需指定版本號。
$ php artisan migrate
$ php artisan passport:install
按照官方教程,add the Laravel\Passport\HasApiTokens trait to your App\User model
修改app\User.php
<?php
namespace App;
use Laravel\Passport\HasApiTokens;
...
class User extends Authenticatable
{
use HasApiTokens, Notifiable;
...
註冊api路徑
修改app\Providers\AuthServiceProvider.php
<?php
namespace App\Providers;
use Laravel\Passport\Passport;
use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use App\Auth\CacheUserProvider;
use Illuminate\Support\Facades\Auth;
...
class AuthServiceProvider extends ServiceProvider
{
...
public function boot()
{
$this->registerPolicies();
Auth::provider('cache-user', function() {
return resolve(CacheUserProvider::class);
});
Passport::routes();
}
最後,修改auth預設的方式
修改檔案config\auth.php
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'passport',
'provider' => 'users',
],
],
關於api passport中cache user的具體使用,我們還在摸索,暫時就說到這裡。
結果
在實際使用中,和本教程的區別在於,我們使用redis作為session driver(這部分內容的配置參考官方教程即可)。在實際使用的前後端一體系統中(使用blade介面),增加cache user方法,能顯著減輕對user表的負擔,減少mysql資料庫的負荷。目前我們的前後端分離系統仍在開發階段,上述配置可行,但尚未來得及實踐進入passport API階段之後實際優化效果是什麼。
參考
- laravel 官方教程(5.7版):https://learnku.com/docs/laravel/5.7
- 前人關於cache user的教程:https://paulund.co.uk/laravel-cache-authus...
網路上搜了一下缺少類似的內容,新寫的教程,懇請指正。
第一次在這裡發文,不確定格式是否正確,希望大家喜歡。
本作品採用《CC 協議》,轉載必須註明作者和本文連結