簡介 (Introduction)
你的應用程式執行的某些資料檢索或處理任務可能會耗費大量 CPU 資源或需要數秒才能完成。在這種情況下,通常會將檢索到的資料快取一段時間,以便在後續對相同資料的請求中快速檢索。快取的資料通常儲存在非常快速的資料儲存中,例如 Memcached 或 Redis。
幸運的是,Laravel 為各種快取後端提供了富有表現力的統一 API,讓你可以利用它們極快的資料檢索速度來加速你的 Web 應用程式。
設定 (Configuration)
你的應用程式的快取設定檔位於 config/cache.php。在此檔案中,你可以指定預設要在整個應用程式中使用的快取儲存。Laravel 開箱即支援流行的快取後端,如 Memcached、Redis、DynamoDB 和關聯式資料庫。此外,還提供基於檔案的快取驅動程式,而 array 和 null 快取驅動程式則為你的自動化測試提供方便的快取後端。
快取設定檔還包含你可以查看的各種其他選項。預設情況下,Laravel 設定為使用 database 快取驅動程式,它將序列化的快取物件儲存在應用程式的資料庫中。
驅動程式先決條件 (Driver Prerequisites)
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 Cloud 和 Laravel Forge)預設已安裝 PhpRedis 擴充套件。
有關設定 Redis 的更多資訊,請查閱其 Laravel 文件頁面。
DynamoDB
在使用 DynamoDB 快取驅動程式之前,你必須建立一個 DynamoDB 表來儲存所有快取資料。通常,此表應命名為 cache。但是,你應該根據 cache 設定檔中 stores.dynamodb.table 設定值的值來命名表。表名稱也可以透過 DYNAMODB_CACHE_TABLE 環境變數設定。
此表還應該有一個字串分區鍵,其名稱對應於應用程式的 cache 設定檔中 stores.dynamodb.attributes.key 設定項目的值。預設情況下,分區鍵應命名為 key。
通常,DynamoDB 不會主動從表中刪除過期項目。因此,你應該在表上啟用 Time to Live (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 Usage)
取得快取實例 (Obtaining a Cache Instance)
要取得快取儲存實例,你可以使用 Cache facade,這是我們在整個文件中將使用的。Cache facade 提供了方便、簡潔的方式來存取 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 [
// ...
];
}
}
存取多個快取儲存 (Accessing Multiple Cache Stores)
使用 Cache facade,你可以透過 store 方法存取各種快取儲存。傳遞給 store 方法的鍵應該對應於 cache 設定檔中 stores 設定陣列中列出的其中一個儲存:
$value = Cache::store('file')->get('foo');
Cache::store('redis')->put('bar', 'baz', 600); // 10 Minutes
從快取中檢索項目 (Retrieving Items From the Cache)
Cache facade 的 get 方法用於從快取中檢索項目。如果項目不存在於快取中,將返回 null。如果你願意,可以將第二個參數傳遞給 get 方法,指定如果項目不存在時你希望返回的預設值:
$value = Cache::get('key');
$value = Cache::get('key', 'default');
你甚至可以傳遞一個閉包作為預設值。如果指定的項目不存在於快取中,將返回閉包的結果。傳遞閉包允許你延遲從資料庫或其他外部服務檢索預設值:
$value = Cache::get('key', function () {
return DB::table(/* ... */)->get();
});
確定項目是否存在 (Determining Item Existence)
has 方法可用於確定項目是否存在於快取中。如果項目存在但其值為 null,此方法也將返回 false:
if (Cache::has('key')) {
// ...
}
遞增 / 遞減值 (Incrementing / Decrementing Values)
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);
檢索和儲存 (Retrieve & Store)
有時你可能希望從快取中檢索一個項目,但如果請求的項目不存在,也要儲存一個預設值。例如,你可能希望從快取中檢索所有使用者,或者如果他們不存在,從資料庫中檢索他們並將他們新增到快取中。你可以使用 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();
});
在重新驗證時使用過期資料 (Stale While Revalidate)
使用 Cache::remember 方法時,如果快取值已過期,某些使用者可能會遇到緩慢的回應時間。對於某些類型的資料,允許在背景中重新計算快取值的同時提供部分過期的資料可能是有用的,這可以防止某些使用者在計算快取值時遇到緩慢的回應時間。這通常稱為「在重新驗證時使用過期資料」模式,Cache::flexible 方法提供了此模式的實現。
flexible 方法接受一個陣列,指定快取值被視為「新鮮」的時間以及何時變為「過期」。陣列中的第一個值表示快取被視為新鮮的秒數,而第二個值定義了在需要重新計算之前可以作為過期資料提供的時間。
如果在新鮮期間(第一個值之前)發出請求,快取會立即返回而不重新計算。如果在過期期間(兩個值之間)發出請求,過期值會提供給使用者,並且會註冊一個延遲函式來在回應發送給使用者後重新整理快取值。如果在第二個值之後發出請求,快取被視為已過期,並且值會立即重新計算,這可能會導致使用者的回應較慢:
$value = Cache::flexible('users', [5, 10], function () {
return DB::table('users')->get();
});
檢索和刪除 (Retrieve & Delete)
如果你需要從快取中檢索一個項目然後刪除該項目,你可以使用 pull 方法。與 get 方法一樣,如果項目不存在於快取中,將返回 null:
$value = Cache::pull('key');
$value = Cache::pull('key', 'default');
在快取中儲存項目 (Storing Items in the Cache)
你可以使用 Cache facade 上的 put 方法將項目儲存在快取中:
Cache::put('key', 'value', $seconds = 10);
如果沒有將儲存時間傳遞給 put 方法,項目將被無限期儲存:
Cache::put('key', 'value');
除了傳遞秒數作為整數之外,你還可以傳遞一個 DateTime 實例,表示快取項目的所需過期時間:
Cache::put('key', 'value', now()->addMinutes(10));
如果不存在則儲存 (Store If Not Present)
add 方法只會在項目不存在於快取儲存中時將其新增到快取中。如果項目實際上被新增到快取中,該方法將返回 true。否則,該方法將返回 false。add 方法是一個原子操作:
Cache::add('key', 'value', $seconds);
永久儲存項目 (Storing Items Forever)
forever 方法可用於將項目永久儲存在快取中。由於這些項目不會過期,必須使用 forget 方法手動從快取中刪除它們:
Cache::forever('key', 'value');
[!NOTE] 如果你使用 Memcached 驅動程式,當快取達到其大小限制時,「永久」儲存的項目可能會被刪除。
從快取中刪除項目 (Removing Items From the Cache)
你可以使用 forget 方法從快取中刪除項目:
Cache::forget('key');
你也可以透過提供零或負數的過期秒數來刪除項目:
Cache::put('key', 'value', 0);
Cache::put('key', 'value', -5);
你可以使用 flush 方法清除整個快取:
Cache::flush();
[!WARNING] 清除快取不會尊重你設定的快取「前綴」,並且將從快取中刪除所有項目。當清除由其他應用程式共享的快取時,請仔細考慮這一點。
快取記憶化 (Cache Memoization)
Laravel 的 memo 快取驅動程式允許你在單個請求或任務執行期間將解析的快取值暫時儲存在記憶體中。這可以防止在同一執行中重複快取命中,顯著提高效能。
To use the memoized cache, invoke the memo method:
use Illuminate\Support\Facades\Cache;
$value = Cache::memo()->get('key');
The memo method optionally accepts the name of a cache store, which specifies the underlying cache store the memoized driver will decorate:
// Using the default cache store...
$value = Cache::memo()->get('key');
// Using the Redis cache store...
$value = Cache::memo('redis')->get('key');
The first get call for a given key retrieves the value from your cache store, but subsequent calls within the same request or job will retrieve the value from memory:
// Hits the cache...
$value = Cache::memo()->get('key');
// Does not hit the cache, returns memoized value...
$value = Cache::memo()->get('key');
When calling methods that modify cache values (such as put, increment, remember, etc.), the memoized cache automatically forgets the memoized value and delegates the mutating method call to the underlying cache store:
Cache::memo()->put('name', 'Taylor'); // Writes to underlying cache...
Cache::memo()->get('name'); // Hits underlying cache...
Cache::memo()->get('name'); // Memoized, does not hit cache...
Cache::memo()->put('name', 'Tim'); // Forgets memoized value, writes new value...
Cache::memo()->get('name'); // Hits underlying cache again...
快取輔助函式 (The Cache Helper)
除了使用 Cache facade 之外,你還可以使用全域 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();
});
[!NOTE] 當測試對全域
cache函式的呼叫時,你可以使用Cache::shouldReceive方法,就像你在測試 facade 一樣。
快取標籤 (Cache Tags)
[!WARNING] 使用
file、dynamodb或database快取驅動程式時不支援快取標籤。
儲存帶標籤的快取項目 (Storing Tagged Cache Items)
快取標籤允許你在快取中標記相關項目,然後清除所有已分配給定標籤的快取值。你可以透過傳遞有序的標籤名稱陣列來存取帶標籤的快取。例如,讓我們存取一個帶標籤的快取並將一個值 put 到快取中:
use Illuminate\Support\Facades\Cache;
Cache::tags(['people', 'artists'])->put('John', $john, $seconds);
Cache::tags(['people', 'authors'])->put('Anne', $anne, $seconds);
存取帶標籤的快取項目 (Accessing Tagged Cache Items)
透過標籤儲存的項目只有在也提供用於儲存值的標籤時才能存取。要檢索帶標籤的快取項目,請將相同的有序標籤列表傳遞給 tags 方法,然後使用你希望檢索的鍵呼叫 get 方法:
$john = Cache::tags(['people', 'artists'])->get('John');
$anne = Cache::tags(['people', 'authors'])->get('Anne');
刪除帶標籤的快取項目 (Removing Tagged Cache Items)
你可以清除分配給一個標籤或標籤列表的所有項目。例如,以下程式碼將刪除所有帶有 people、authors 或兩者標籤的快取。因此,Anne 和 John 都將從快取中刪除:
Cache::tags(['people', 'authors'])->flush();
相反,下面的程式碼只會刪除帶有 authors 標籤的快取值,因此 Anne 會被刪除,但 John 不會:
Cache::tags('authors')->flush();
原子鎖定 (Atomic Locks)
[!WARNING] 要使用此功能,你的應用程式必須使用
memcached、redis、dynamodb、database、file或array快取驅動程式作為應用程式的預設快取驅動程式。此外,所有伺服器必須與同一個中央快取伺服器通訊。
管理鎖定 (Managing Locks)
原子鎖定允許操作分散鎖定而無需擔心競爭條件。例如,Laravel Cloud 使用原子鎖定來確保一次只有一個遠端任務在伺服器上執行。你可以使用 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 for 10 seconds after waiting a maximum of 5 seconds...
});
跨程序管理鎖定 (Managing Locks Across Processes)
有時,你可能希望在一個程序中獲取鎖定並在另一個程序中釋放它。例如,你可能在 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();
快取故障轉移 (Cache Failover)
failover 快取驅動程式在與快取互動時提供自動故障轉移功能。如果主要快取儲存因任何原因失敗,Laravel 將自動嘗試使用列表中下一個設定的儲存。這對於確保生產環境中的高可用性特別有用,其中快取可靠性至關重要。
要設定故障轉移快取儲存,請指定 failover 驅動程式並提供一個按順序嘗試的儲存名稱陣列。預設情況下,Laravel 在你的應用程式的 config/cache.php 設定檔中包含一個故障轉移設定範例:
'failover' => [
'driver' => 'failover',
'stores' => [
'database',
'array',
],
],
一旦你設定了使用 failover 驅動程式的儲存,你可能會希望在應用程式的 .env 檔案中將故障轉移儲存設定為預設快取儲存:
CACHE_STORE=failover
當快取儲存操作失敗並啟動故障轉移時,Laravel 將分派 Illuminate\Cache\Events\CacheFailedOver 事件,允許你報告或記錄快取儲存已失敗。
新增自訂快取驅動程式 (Adding Custom Cache Drivers)
編寫驅動程式 (Writing the Driver)
要建立我們的自訂快取驅動程式,我們首先需要實現 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 facade 的 extend 方法來完成我們的自訂驅動程式註冊:
Cache::extend('mongo', function (Application $app) {
return Cache::repository(new MongoStore);
});
[!NOTE] 如果你想知道在哪裡放置你的自訂快取驅動程式程式碼,你可以在
app目錄中建立一個Extensions命名空間。但是,請記住,Laravel 沒有固定的應用程式結構,你可以根據你的喜好自由組織你的應用程式。
註冊驅動程式 (Registering the Driver)
要向 Laravel 註冊自訂快取驅動程式,我們將在 Cache facade 上使用 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 選項更新為你的擴充名稱。
事件 (Events)
要在每個快取操作上執行程式碼,你可以監聽快取分派的各種事件:
| 事件名稱 |
|---|
Illuminate\Cache\Events\CacheFlushed |
Illuminate\Cache\Events\CacheFlushing |
Illuminate\Cache\Events\CacheHit |
Illuminate\Cache\Events\CacheMissed |
Illuminate\Cache\Events\ForgettingKey |
Illuminate\Cache\Events\KeyForgetFailed |
Illuminate\Cache\Events\KeyForgotten |
Illuminate\Cache\Events\KeyWriteFailed |
Illuminate\Cache\Events\KeyWritten |
Illuminate\Cache\Events\RetrievingKey |
Illuminate\Cache\Events\RetrievingManyKeys |
Illuminate\Cache\Events\WritingKey |
Illuminate\Cache\Events\WritingManyKeys |
要提高效能,你可以透過在應用程式的 config/cache.php 設定檔中將給定快取儲存的 events 設定選項設定為 false 來停用快取事件:
'database' => [
'driver' => 'database',
// ...
'events' => false,
],