快取系統


註冊驅動程式

##事件

#設定

Laravel 為各種後端快取提供豐富且統一的API,其設定資訊位於 

config/cache.php 檔案中。在該檔案中你可以指定應用程式預設使用哪個快取驅動。 Laravel 支援目前流行的後端緩存,例如 Memcached

Redis

快取設定檔還包含各種其他選項,這些選項都會記錄在檔案中,因此請確保閱讀這些選項。預設情況下,Laravel 配置為使用
file
快取驅動,它將序列化的快取物件儲存在檔案系統中。對於大型應用,建議您使用更強大的驅動程序,例如 Memcached 或 Redis。你甚至可以為同一個驅動程式配置多個快取配置。 #########################驅動程式的前提條件#########

資料庫

當使用 database 快取驅動程式時,你需要設定一個表格來存放快取資料。以下是建立快取資料表結構的Schema 宣告範例:

Schema::create('cache', function ($table) {
    $table->string('key')->unique();    
    $table->text('value');    
    $table->integer('expiration');
});

#{tip} 你也可以使用Artisan 指令php artisan cache:table 來生成合適的遷移。

Memcached

使用 Memcached 驅動程式需要安裝 Memcached PECL 擴充包 。你可以把所有的 Memcached 伺服器都列在 config/cache.php 設定檔中:

'memcached' => [ 
   [       
    'host' => '127.0.0.1',        
    'port' => 11211,        
    'weight' => 100    
   ],
 ],

你可以將 host 選項設定為 UNIX socket 路徑。如果你這樣配置了, port 選項應該設定為0

'memcached' => [ 
   [      
     'host' => '/var/run/memcached/memcached.sock',        
     'port' => 0,        
     'weight' => 100   
    ],
],

Redis

在使用Laravel 的Redis在快取之前,你需要透過Composer 安裝predis/predis 擴充包(~1.0) 或使用PECL 安裝PhpRedis PHP 擴充功能。

如需了解更多關於 Redis 的配置,請參考 Laravel Redis 文件。

快取的使用

取得快取實例

Illuminate\Contracts\Cache\FactoryIlluminate\Contracts\Cache\Repository 契約提供了Laravel 快取服務的存取機制。 Factory 契約為你的應用程式定義了存取所有快取驅動的機制。 Repository 契約通常是由你的 cache 設定檔指定的預設快取驅動實現的。

不過,你也可以使用 Cache Facade,我們將在後續的文件中介紹。 Cache Facade 為Laravel 快取契約底層的實作提供了方便又簡潔的方法:

<?php
    namespace App\Http\Controllers;
    use Illuminate\Support\Facades\Cache;
    class UserController extends Controller{   
     /**
     * 展示应用的所有用户列表。
     *
     * @return Response
     */ 
      public function index() 
         {     
            $value = Cache::get('key');    
                //  
          }
     }

存取多個快取儲存

使用Cache Facade,你可以透過store 方法來存取各種快取儲存。傳入store 方法的鍵應該對應cache 設定資訊檔中的stores 設定陣列中所列的儲存之一:

$value = Cache::store('file')->get('foo');
Cache::store('redis')->put('bar', 'baz', 600); 
// 10 分钟

#

從快取中取得資料

Cache Facade 的 get 方法是用來從快取中取得資料的方法。如果該資料在快取中不存在,那麼該方法將傳回 null 。正如你想的那樣,你也可以向 get 方法傳遞第二個參數,用來指定如果查找的資料不存在時你希望返回的預設值:

$value = Cache::get('key');
$value = Cache::get('key', 'default');

你甚至可以傳遞Closure 作為預設值。如果指定的資料在快取中不存在,將傳回 Closure 的結果。傳遞閉包的方法允許你從資料庫或其他外部服務中取得預設值:

$value = Cache::get('key', function () {
    return DB::table(...)->get();
 });

#檢查快取項目是否存在

##has 方法可以用於判斷緩存項目是否存在。如果為nullfalse 則該方法將會傳回false

if (Cache::has('key')) { 
   //
}

遞增與遞減值

incrementdecrement 方法可以用來調整快取中整數項的值。這兩個方法都可以傳入第二個可選參數,這個參數用來指明要遞增或遞減的數量:

Cache::increment('key');
Cache::increment('key', $amount);
Cache::decrement('key');
Cache::decrement('key', $amount);

取得和儲存

#有時你可能想要從快取中取得一個數據,而當請求的快取項目不存在時,程式能為你儲存一個預設值。例如,你可能想從快取中獲取所有用戶,當快取中不存在這些用戶時,程式將從資料庫將這些用戶取出並放入快取。你可以使用

Cache::remember 方法來實作:

$value = Cache::remember('users', $seconds, function () {
    return DB::table('users')->get();
 });

如果快取中不存在你想要的資料時,則傳遞給

remember 方法的閉包 將被執行,然後將其結果傳回並放置到快取中。

你可以使用

rememberForever 方法從快取中取得資料或永久儲存它:

$value = Cache::rememberForever('users', function () {
    return DB::table('users')->get();
});

##取得和刪除

如果你需要從快取中取得到資料之後再刪除它,你可以使用

pull

方法。和get 方法一樣,如果快取不存在,則傳回null

$value = Cache::pull('key');

在快取中儲存資料

你可以使用

Cache

Facade 的put 方法將資料儲存到快取中:

Cache::put('key', 'value', $seconds);
如果快取的過期時間沒有傳遞給

put

方法, 則快取將永久有效:

Cache::put('key', 'value');
除了以整數形式傳遞過期時間的秒數,你還可以傳遞一個

DateTime

# 實例來表示該資料的過期時間:

Cache::put('key', 'value', now()->addMinutes(10));

只儲存沒有的資料

add

方法將只儲存快取中不存在的資料。如果儲存成功,將傳回 true ,否則傳回 false

Cache::add('key', 'value', $seconds);

#

資料永久儲存

forever 方法可用於持久化將資料儲存到快取中。因為這些資料不會過期,所以必須透過forget 方法從快取中手動刪除它們:

Cache::forever('key', 'value');

{tip} 如果你使用Memcached 驅動,當快取資料達到儲存上限時,「永久儲存」 的資料可能會被刪除。

刪除快取中的資料

你可以使用forget 方法從快取中刪除這些資料:

Cache::forget('key');

你也可以透過提供零或負的TTL 值來刪除這些資料:

Cache::put('key', 'value', 0);
Cache::put('key', 'value', -5);

你可以使用flush 方法清空所有的緩存:

Cache::flush();

{note} 清空快取的方法並不會考慮快取前綴,會將快取中的所有內容刪除。因此在清除與其它應用程式共享的快取時,請慎重考慮。

原子鎖定

{note} 要使用這個特性,你的應用必須使用memcacheddynamodbredis 快取驅動程式作為你應用程式的預設快取驅動。此外,所有伺服器必須與同一中央快取伺服器進行通訊。

原子鎖定允許對分散式鎖定進行操作而不必擔心競爭條件。例如, Laravel Forge 使用原子鎖來確保在一台伺服器上每次只有一個遠端任務在執行。你可以使用 Cache::lock 方法來建立和管理鎖定:

use Illuminate\Support\Facades\Cache;
$lock = Cache::lock('foo', 10);
if ($lock->get()) {
    //获取锁定10秒...    
    $lock->release();
}

get 方法也可以接收一個閉包。在閉包執行之後,Laravel 將會自動釋放鎖定:

Cache::lock('foo')->get(function () {
    // 获取无限期锁并自动释放...
});

如果你在請求時鎖定無法使用,你可以控制 Laravel 等待指定的秒數。如果在指定的時間限制內無法取得鎖定,則會拋出Illuminate\Contracts\Cache\LockTimeoutException

use Illuminate\Contracts\Cache\LockTimeoutException;
$lock = Cache::lock('foo', 10);try {
    $lock->block(5);    
    // 等待最多5秒后获取的锁...
   } catch (LockTimeoutException $e) {
       // 无法获取锁...
   } finally {
       optional($lock)->release();
   }
Cache::lock('foo', 10)->block(5, function () {
    // 等待最多5秒后获取的锁...
   });

#管理跨程式的鎖定

有時,你希望在一個進程中取得鎖定並在另一個進程中釋放它。例如,你可以在 Web 請求期間取得鎖,並希望在該請求觸發的佇列作業結束時釋放鎖。在這種情況下,你應該將鎖的作用域「owner token」傳遞給佇列作業,以便作業可以使用給定的token 重新實例化鎖:

// 控制器里面...
$podcast = Podcast::find($id);
if ($lock = Cache::lock('foo', 120)->get()) {
    ProcessPodcast::dispatch($podcast, $lock->owner());
    }
// ProcessPodcast Job 里面...
Cache::restoreLock('foo', $this->owner)->release();

如果你想在不尊重目前鎖的擁有者的情況下釋放鎖定,你可以使用forceRelease 方法:

Cache::lock('foo')->forceRelease();

#####################################################

Cache 輔助函數

除了可以使用 Cache Facade 或 Cache 契約 外,你還可以使用全域輔助函數 cache 來取得和保存快取資料。當cache 函數只接收一個字串參數的時候,它將會傳回給定鍵對應的值:

$value = cache('key');

如果你向函數提供了一組鍵值對和過期時間,它將在指定時間內快取資料:

cache(['key' => 'value'], $seconds);
cache(['key' => 'value'], now()->addMinutes(10));

cache 函數在沒有任何參數的情況下被呼叫時,它會傳回一個Illuminate\Contracts\Cache\ Factory 實作的實例,允許你呼叫其它快取方法:

cache()->remember('users', $seconds, function () {
    return DB::table('users')->get();
 });

{tip} 如果在測試中使用全域輔助函數cache ,你可以使用 Cache::shouldReceive 方法就像測試Facade。

快取標記

{note} 快取標記不支援使用file database 快取驅動。此外,當使用多個快取標記的快取設定為「永久」時,類似 memcached 的快取驅動效能最佳,它會自動清除舊的記錄。

寫入被標記的快取資料

快取標記允許你給快取相關進行標記,以便後續清除這些快取值。你可以透過傳入標記名稱的有序數組來存取標記的快取。例如,我們可以使用標記的同時使用 put 方法來設定快取。

Cache::tags(['people', 'artists'])->put('John', $john, $seconds);
Cache::tags(['people', 'authors'])->put('Anne', $anne, $seconds);

存取被標記的快取資料

若要取得一個被標記的快取數據,請將相同的有序標記數組傳遞給tags 方法,然後呼叫get 方法來取得你要檢索的鍵:

$john = Cache::tags(['people', 'artists'])->get('John');
$anne = Cache::tags(['people', 'authors'])->get('Anne');

移除被標記的快取資料

你可以清空有單一標記或是一組標記的所有快取資料。例如,下面的語句會被標記為 peopleauthors 或兩者都有的快取。所以,AnneJohn 都會從快取中被刪除:

Cache::tags(['people', 'authors'])->flush();

相反,下面的語句只會刪除被標記authors 的緩存,所以Anne 會被刪除,但John 不會:

Cache::tags('authors')->flush();

##增加自訂的快取驅動器

#

編寫驅動程式

要建立自訂的快取驅動,首先需要實作 Illuminate\Contracts\Cache\Store 契約。因此, MongoDB 的快取實作看起來會像這樣:

<?php
    namespace App\Extensions;
    use Illuminate\Contracts\Cache\Store;
    class MongoStore implements Store{ 
       public function get($key) {}    
       public function many(array $keys);    
       public function put($key, $value, $seconds) {}    
       public function putMany(array $values, $seconds);    
       public function increment($key, $value = 1) {}    
       public function decrement($key, $value = 1) {}    
       public function forever($key, $value) {}    
       public function forget($key) {}    
       public function flush() {}    
       public function getPrefix() {}
    }

我們只需要 MongoDB 的連線來實作這些方法。關於如何實作這些方法的實例,可以參閱框架原始碼中的 Illuminate\Cache\MemcachedStore 。一旦完成契約額實現後,就可以像下面這樣完成自訂驅動的註冊了。

Cache::extend('mongo', function ($app) {
    return Cache::repository(new MongoStore);
  });

{tip} 如果你不知道要把快取驅動程式碼放在哪,你可以在 app 目錄下建立一個 Extensions 命名空間。然而,Laravel 並沒有硬性規定應用程式的結構,你可以根據自己的喜好自由組織你的應用程式。

註冊驅動程式

要使用Laravel 來註冊自訂快取驅動,就要在Cache Facade 上使用extend 方法。對Cache::extend 的呼叫可以在新的Laravel 應用程式中自帶的App\Providers\AppServiceProviderboot 方法中完成,或者你也可以建立自己的服務提供者來存放擴展,只是不要忘記在config/app.php 的provider 陣列中註冊服務提供者:

<?php
    namespace App\Providers;use App\Extensions\MongoStore;
    use Illuminate\Support\Facades\Cache;
    use Illuminate\Support\ServiceProvider;
    class CacheServiceProvider extends ServiceProvider{   
     /**
     * 执行服务的注册后引导。
     *
     * @return void
     */   
       public function boot()   
        {      
          Cache::extend('mongo', function ($app) {     
                 return Cache::repository(new MongoStore);        
             });   
          }   
     /**
     * 在容器中注册绑定。
     *
     * @return void
     */    
     public function register()   
      {      
        //   
       }
    }

傳遞給extend 方法的第一個參數是驅動程式的名稱。這將與 config/cache.php 設定檔的 driver 選項相對應。第二個參數是一個應該傳回 Illuminate\Cache\Repository 實例的閉包。這個閉包將傳遞一個 服務容器 的 $app 實例。

一旦你的擴充功能註冊後,需要將 config/cache.php 設定檔中的 driver 選項更新為你的擴充名稱。

#事件

要在每次快取作業時執行程式碼,你可以監聽快取觸發的 事件 。通常,你應該將這些事件監聽器放在 EventServiceProvider 中:

/**
 * 应用的事件监听器映射
 *
 * @var array
 */
 protected $listen = [
     'Illuminate\Cache\Events\CacheHit' => [   
          'App\Listeners\LogCacheHit',   
         ],    
     'Illuminate\Cache\Events\CacheMissed' => [     
        'App\Listeners\LogCacheMissed',  
         ],    
     'Illuminate\Cache\Events\KeyForgotten' => [    
         'App\Listeners\LogKeyForgotten',  
          ],   
      'Illuminate\Cache\Events\KeyWritten' => [ 
          'App\Listeners\LogKeyWritten', 
           ],
        ];
本文章首發在 LearnKu.com 網站上。