快取
簡介
您的應用程式執行的一些資料檢索或處理任務可能需要消耗大量 CPU 或花費數秒才能完成。在這種情況下,通常會將檢索到的資料快取一段時間,以便在後續請求相同資料時可以快速檢索。快取的資料通常儲存在非常快速的資料儲存區中,例如 Memcached 或 Redis。
幸好,Laravel 為各種快取後端提供了一個富有表現力且統一的 API,讓您可以利用它們極快的資料檢索速度並加快您的 Web 應用程式速度。
設定
您應用程式的快取設定檔位於 config/cache.php
。在此檔案中,您可以指定要在整個應用程式中預設使用的快取儲存區。Laravel 支援熱門的快取後端,例如 Memcached、Redis、DynamoDB 和關聯式資料庫。此外,還提供基於檔案的快取驅動程式,而 array
和「null」快取驅動程式則為您的自動化測試提供方便的快取後端。
快取設定檔還包含您可以查看的各種其他選項。預設情況下,Laravel 設定為使用 database
快取驅動程式,該驅動程式將序列化的快取物件儲存在您應用程式的資料庫中。
驅動程式先決條件
資料庫
使用 database
快取驅動程式時,您需要一個資料庫表格來包含快取資料。通常,這包含在 Laravel 的預設 0001_01_01_000001_create_cache_table.php
資料庫遷移中;但是,如果您的應用程式不包含此遷移,您可以使用 make:cache-table
Artisan 命令來建立它。
php artisan make:cache-table php artisan migrate
Memcached
使用 Memcached 驅動程式需要安裝 Memcached PECL 套件。您可以在 config/cache.php
設定檔中列出所有 Memcached 伺服器。此檔案已包含 memcached.servers
條目,可讓您開始使用。
'memcached' => [ // ... 'servers' => [ [ 'host' => env('MEMCACHED_HOST', '127.0.0.1'), 'port' => env('MEMCACHED_PORT', 11211), 'weight' => 100, ], ],],
如果需要,您可以將 host
選項設定為 UNIX socket 路徑。如果您執行此操作,則 port
選項應設定為 0
。
'memcached' => [ // ... 'servers' => [ [ 'host' => '/var/run/memcached/memcached.sock', 'port' => 0, 'weight' => 100 ], ],],
Redis
在使用 Laravel 的 Redis 快取之前,您需要透過 PECL 安裝 PhpRedis PHP 擴充功能,或透過 Composer 安裝 predis/predis
套件 (~2.0)。Laravel Sail 已經包含此擴充功能。此外,官方 Laravel 部署平台 (例如 Laravel Forge 和 Laravel Vapor) 預設已安裝 PhpRedis 擴充功能。
有關設定 Redis 的詳細資訊,請參閱其 Laravel 文件頁面。
DynamoDB
在使用 DynamoDB 快取驅動程式之前,您必須建立一個 DynamoDB 表格來儲存所有快取資料。通常,此表格應命名為 cache
。但是,您應該根據 cache
設定檔中 stores.dynamodb.table
設定值的值來命名表格。表格名稱也可以透過 DYNAMODB_CACHE_TABLE
環境變數設定。
此表格還應具有一個字串分割索引鍵,其名稱對應於您應用程式 cache
設定檔中 stores.dynamodb.attributes.key
設定項的值。預設情況下,分割索引鍵應命名為 key
。
通常,DynamoDB 不會主動從表格中移除過期的項目。因此,您應該在表格上啟用存活時間 (TTL)。設定表格的 TTL 設定時,您應該將 TTL 屬性名稱設定為 expires_at
。
接下來,安裝 AWS SDK,以便您的 Laravel 應用程式可以與 DynamoDB 通訊。
composer require aws/aws-sdk-php
此外,您應該確保為 DynamoDB 快取儲存區設定選項提供值。通常,這些選項 (例如 AWS_ACCESS_KEY_ID
和 AWS_SECRET_ACCESS_KEY
) 應在您應用程式的 .env
設定檔中定義。
'dynamodb' => [ 'driver' => 'dynamodb', 'key' => env('AWS_ACCESS_KEY_ID'), 'secret' => env('AWS_SECRET_ACCESS_KEY'), 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 'table' => env('DYNAMODB_CACHE_TABLE', 'cache'), 'endpoint' => env('DYNAMODB_ENDPOINT'),],
MongoDB
如果您使用 MongoDB,則官方 mongodb/laravel-mongodb
套件會提供 mongodb
快取驅動程式,並且可以使用 mongodb
資料庫連線進行設定。MongoDB 支援 TTL 索引,可用於自動清除過期的快取項目。
有關設定 MongoDB 的詳細資訊,請參閱 MongoDB 快取和鎖文件。
快取使用
取得快取實例
若要取得快取儲存區實例,您可以使用 Cache
外觀,這也是我們將在本文件中使用的內容。Cache
外觀提供了方便、簡潔的方式來存取 Laravel 快取合約的底層實作。
<?php namespace App\Http\Controllers; use Illuminate\Support\Facades\Cache; class UserController extends Controller{ /** * Show a list of all users of the application. */ public function index(): array { $value = Cache::get('key'); return [ // ... ]; }}
存取多個快取儲存區
使用 Cache
外觀,您可以透過 store
方法存取各種快取儲存區。傳遞給 store
方法的索引鍵應對應於您 cache
設定檔中 stores
設定陣列中列出的其中一個儲存區。
$value = Cache::store('file')->get('foo'); Cache::store('redis')->put('bar', 'baz', 600); // 10 Minutes
從快取中檢索項目
Cache
外觀的 get
方法用於從快取中檢索項目。如果項目不存在於快取中,則會傳回 null
。如果您願意,您可以將第二個引數傳遞給 get
方法,指定當項目不存在時您希望傳回的預設值。
$value = Cache::get('key'); $value = Cache::get('key', 'default');
您甚至可以將閉包傳遞為預設值。如果指定的項目不存在於快取中,則會傳回閉包的結果。傳遞閉包可讓您延遲從資料庫或其他外部服務檢索預設值。
$value = Cache::get('key', function () { return DB::table(/* ... */)->get();});
判斷項目是否存在
可以使用 has
方法來判斷快取中是否存在項目。如果項目存在但其值為 null
,此方法也會傳回 false
。
if (Cache::has('key')) { // ...}
遞增/遞減值
可以使用 increment
和 decrement
方法來調整快取中整數項目的值。這兩種方法都接受一個選用的第二個引數,表示要遞增或遞減項目值的量。
// Initialize the value if it does not exist...Cache::add('key', 0, now()->addHours(4)); // Increment or decrement the value...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();});
過時資料同時重新驗證
當使用 Cache::remember
方法時,如果快取的值已過期,某些使用者可能會遇到回應時間緩慢的問題。對於某些類型的資料,允許在背景重新計算快取值時提供部分過時的資料可能會很有用,這樣可以防止某些使用者在計算快取值時遇到回應時間緩慢的問題。這通常被稱為「過時資料同時重新驗證」模式,而 Cache::flexible
方法提供了這種模式的實作。
flexible 方法接受一個陣列,指定快取值被認為是「新鮮」的時間長度,以及何時變成「過時」。陣列中的第一個值表示快取被認為是新鮮的秒數,而第二個值定義了在需要重新計算之前,可以作為過時資料提供的時間長度。
如果在新鮮期間(第一個值之前)發出請求,則會立即傳回快取,而無需重新計算。如果在過時期間(兩個值之間)發出請求,則會將過時的值提供給使用者,並且會註冊一個延遲函式,以便在將回應傳送給使用者後刷新快取的值。如果在第二個值之後發出請求,則快取會被視為過期,並且會立即重新計算該值,這可能會導致使用者的回應時間較慢。
$value = Cache::flexible('users', [5, 10], function () { return DB::table('users')->get();});
檢索和刪除
如果您需要從快取中檢索項目,然後刪除該項目,可以使用 pull
方法。與 get
方法一樣,如果快取中不存在該項目,將會傳回 null
。
$value = Cache::pull('key'); $value = Cache::pull('key', 'default');
在快取中儲存項目
您可以使用 Cache
外觀上的 put
方法將項目儲存在快取中。
Cache::put('key', 'value', $seconds = 10);
如果沒有將儲存時間傳遞給 put
方法,則該項目將無限期地儲存。
Cache::put('key', 'value');
您可以傳遞代表快取項目所需過期時間的 DateTime
實例,而不是以整數形式傳遞秒數。
Cache::put('key', 'value', now()->addMinutes(10));
如果不存在則儲存
如果快取儲存區中尚不存在該項目,則 add
方法只會將該項目新增到快取中。如果該項目實際上已新增到快取中,則該方法將傳回 true
。否則,該方法將傳回 false
。add
方法是一個原子操作。
Cache::add('key', 'value', $seconds);
永久儲存項目
可以使用 forever
方法將項目永久儲存在快取中。由於這些項目不會過期,因此必須使用 forget
方法從快取中手動移除它們。
Cache::forever('key', 'value');
如果您使用的是 Memcached 驅動程式,則當快取達到其大小限制時,可能會移除「永久」儲存的項目。
從快取中移除項目
您可以使用 forget
方法從快取中移除項目。
Cache::forget('key');
您也可以透過提供零或負數的過期秒數來移除項目。
Cache::put('key', 'value', 0); Cache::put('key', 'value', -5);
您可以使用 flush
方法清除整個快取。
Cache::flush();
清除快取不會考慮您配置的快取「前綴」,並且會從快取中移除所有條目。當清除其他應用程式共用的快取時,請仔細考慮這一點。
快取輔助函數
除了使用 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();});
當測試全域 cache
函式的呼叫時,您可以像測試外觀一樣使用 Cache::shouldReceive
方法。
原子鎖
若要使用此功能,您的應用程式必須使用 memcached
、redis
、dynamodb
、database
、file
或 array
快取驅動程式作為您應用程式的預設快取驅動程式。此外,所有伺服器都必須與同一個中央快取伺服器進行通訊。
管理鎖
原子鎖允許在不擔心競爭條件的情況下操作分散式鎖定。例如,Laravel Forge 使用原子鎖定來確保一次只在伺服器上執行一個遠端任務。您可以使用 Cache::lock
方法建立和管理鎖定。
use Illuminate\Support\Facades\Cache; $lock = Cache::lock('foo', 10); if ($lock->get()) { // Lock acquired for 10 seconds... $lock->release();}
get
方法也接受閉包。執行閉包後,Laravel 會自動釋放鎖定。
Cache::lock('foo', 10)->get(function () { // Lock acquired for 10 seconds and automatically released...});
如果鎖定在您請求時不可用,您可以指示 Laravel 等待指定的秒數。如果在指定的時間限制內無法取得鎖定,則會拋出 Illuminate\Contracts\Cache\LockTimeoutException
。
use Illuminate\Contracts\Cache\LockTimeoutException; $lock = Cache::lock('foo', 10); try { $lock->block(5); // Lock acquired after waiting a maximum of 5 seconds...} catch (LockTimeoutException $e) { // Unable to acquire lock...} finally { $lock->release();}
可以透過將閉包傳遞給 block
方法來簡化上面的範例。當將閉包傳遞給此方法時,Laravel 將嘗試取得鎖定指定的秒數,並在執行閉包後自動釋放鎖定。
Cache::lock('foo', 10)->block(5, function () { // Lock acquired after waiting a maximum of 5 seconds...});
跨程序管理鎖
有時,您可能希望在一個程序中取得鎖定,並在另一個程序中釋放它。例如,您可能會在 Web 請求期間取得鎖定,並希望在由該請求觸發的已排隊工作的結尾釋放鎖定。在這種情況下,您應該將鎖定範圍內的「擁有者權杖」傳遞給已排隊的工作,以便該工作可以使用給定的權杖重新實例化鎖定。
在下面的範例中,如果成功取得鎖定,我們將分派一個已排隊的工作。此外,我們將透過鎖定的 owner
方法將鎖定的擁有者權杖傳遞給已排隊的工作。
$podcast = Podcast::find($id); $lock = Cache::lock('processing', 120); if ($lock->get()) { ProcessPodcast::dispatch($podcast, $lock->owner());}
在應用程式的 ProcessPodcast
工作中,我們可以使用擁有者權杖還原和釋放鎖定。
Cache::restoreLock('processing', $this->owner)->release();
如果您想要在不考慮其目前擁有者的情況下釋放鎖定,可以使用 forceRelease
方法。
Cache::lock('processing')->forceRelease();
新增自訂快取驅動程式
撰寫驅動程式
若要建立自訂快取驅動程式,我們首先需要實作 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 連線實作每個方法。有關如何實作每個方法的範例,請參閱Laravel 框架原始程式碼中的 Illuminate\Cache\MemcachedStore
。完成實作後,我們可以透過呼叫 Cache
外觀的 extend
方法來完成自訂驅動程式註冊。
Cache::extend('mongo', function (Application $app) { return Cache::repository(new MongoStore);});
如果您想知道將自訂快取驅動程式程式碼放在哪裡,您可以在 app
目錄中建立 Extensions
命名空間。但是,請記住,Laravel 沒有嚴格的應用程式結構,您可以根據自己的喜好自由組織應用程式。
註冊驅動程式
若要向 Laravel 註冊自訂快取驅動程式,我們將使用 Cache
外觀上的 extend
方法。由於其他服務提供者可能會嘗試在其 boot
方法中讀取快取值,因此我們將在 booting
回呼中註冊自訂驅動程式。透過使用 booting
回呼,我們可以確保自訂驅動程式在應用程式的服務提供者上呼叫 boot
方法之前,但在所有服務提供者上呼叫 register
方法之後立即註冊。我們將在應用程式的 App\Providers\AppServiceProvider
類別的 register
方法中註冊我們的 booting
回呼。
<?php namespace App\Providers; use App\Extensions\MongoStore;use Illuminate\Contracts\Foundation\Application;use Illuminate\Support\Facades\Cache;use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider{ /** * Register any application services. */ public function register(): void { $this->app->booting(function () { Cache::extend('mongo', function (Application $app) { return Cache::repository(new MongoStore); }); }); } /** * Bootstrap any application services. */ public function boot(): void { // ... }}
傳遞給 extend
方法的第一個引數是驅動程式的名稱。這將對應於 config/cache.php
組態檔案中的 driver
選項。第二個引數應該是一個閉包,該閉包應該傳回 Illuminate\Cache\Repository
實例。該閉包將會被傳遞一個 $app
實例,它是服務容器的實例。
註冊擴充功能後,請更新應用程式 config/cache.php
組態檔案中的 CACHE_STORE
環境變數或 default
選項,以使用您的擴充功能名稱。
事件
若要在每個快取操作上執行程式碼,您可以監聽快取分派的各種事件。
事件名稱 |
---|
Illuminate\Cache\Events\CacheHit |
Illuminate\Cache\Events\CacheMissed |
Illuminate\Cache\Events\KeyForgotten |
Illuminate\Cache\Events\KeyWritten |
若要提高效能,您可以在應用程式的 config/cache.php
組態檔案中,將給定快取儲存區的 events
組態選項設定為 false
,以停用快取事件。
'database' => [ 'driver' => 'database', // ... 'events' => false,],