跳到內容

快取

簡介

您的應用程式執行的一些資料檢索或處理任務可能需要消耗大量 CPU 或花費數秒才能完成。在這種情況下,通常會將檢索到的資料快取一段時間,以便在後續請求相同資料時可以快速檢索。快取的資料通常儲存在非常快速的資料儲存區中,例如 MemcachedRedis

幸好,Laravel 為各種快取後端提供了一個富有表現力且統一的 API,讓您可以利用它們極快的資料檢索速度並加快您的 Web 應用程式速度。

設定

您應用程式的快取設定檔位於 config/cache.php。在此檔案中,您可以指定要在整個應用程式中預設使用的快取儲存區。Laravel 支援熱門的快取後端,例如 MemcachedRedisDynamoDB 和關聯式資料庫。此外,還提供基於檔案的快取驅動程式,而 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 ForgeLaravel 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_IDAWS_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')) {
// ...
}

遞增/遞減值

可以使用 incrementdecrement 方法來調整快取中整數項目的值。這兩種方法都接受一個選用的第二個引數,表示要遞增或遞減項目值的量。

// 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。否則,該方法將傳回 falseadd 方法是一個原子操作。

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

永久儲存項目

可以使用 forever 方法將項目永久儲存在快取中。由於這些項目不會過期,因此必須使用 forget 方法從快取中手動移除它們。

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

如果您使用的是 Memcached 驅動程式,則當快取達到其大小限制時,可能會移除「永久」儲存的項目。

從快取中移除項目

您可以使用 forget 方法從快取中移除項目。

Cache::forget('key');

您也可以透過提供零或負數的過期秒數來移除項目。

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

您可以使用 flush 方法清除整個快取。

Cache::flush();
exclamation

清除快取不會考慮您配置的快取「前綴」,並且會從快取中移除所有條目。當清除其他應用程式共用的快取時,請仔細考慮這一點。

快取輔助函數

除了使用 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();
});
lightbulb

當測試全域 cache 函式的呼叫時,您可以像測試外觀一樣使用 Cache::shouldReceive 方法。

原子鎖

exclamation

若要使用此功能,您的應用程式必須使用 memcachedredisdynamodbdatabasefilearray 快取驅動程式作為您應用程式的預設快取驅動程式。此外,所有伺服器都必須與同一個中央快取伺服器進行通訊。

管理鎖

原子鎖允許在不擔心競爭條件的情況下操作分散式鎖定。例如,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);
});
lightbulb

如果您想知道將自訂快取驅動程式程式碼放在哪裡,您可以在 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,
],