快取
簡介
您的應用程式執行的一些資料擷取或處理任務可能會佔用大量 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
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 [
// ...
];
}
}
存取多個快取儲存區
使用 Cache
facade,您可以透過 store
方法存取各種快取儲存區。傳遞給 store
方法的鍵 (key) 應該與 cache
設定檔中 stores
設定陣列裡列出的其中一個儲存區相對應:
$value = Cache::store('file')->get('foo');
Cache::store('redis')->put('bar', 'baz', 600); // 10 Minutes
從快取中取得項目
Cache
facade 的 get
方法用於從快取中取得項目。如果項目在快取中不存在,將會回傳 null
。如果您希望,可以傳遞第二個參數給 get
方法,指定當項目不存在時您希望回傳的預設值:
$value = Cache::get('key');
$value = Cache::get('key', 'default');
您甚至可以傳遞一個閉包 (closure) 作為預設值。如果指定的項目在快取中不存在,將會回傳該閉包的結果。傳遞閉包讓您可以延遲從資料庫或其他外部服務中取得預設值:
$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();
});
舊資料重新驗證 (Stale While Revalidate)
當使用 Cache::remember
方法時,如果快取值已過期,一些使用者可能會遇到回應時間變慢的情況。對於某些類型的資料,在背景重新計算快取值的同時,允許提供部分舊資料會很有用,這可以防止某些使用者在計算快取值時遇到回應時間變慢。這通常被稱為「舊資料重新驗證 (stale-while-revalidate)」模式,而 Cache::flexible
方法提供了此模式的實作。
flexible 方法接受一個陣列,該陣列指定快取值被視為「新鮮 (fresh)」的時間長度以及何時變為「舊資料 (stale)」。陣列中的第一個值代表快取被視為新鮮的秒數,而第二個值定義了在重新計算之前可以作為舊資料提供的時間長度。
如果請求是在新鮮期間(第一個值之前)提出,快取將立即回傳而無需重新計算。如果請求是在舊資料期間(兩個值之間)提出,舊資料將提供給使用者,並且會註冊一個 延遲函式 以在回應傳送給使用者後重新整理快取值。如果請求是在第二個值之後提出,則快取被視為已過期,並且會立即重新計算值,這可能會導致使用者回應速度變慢:
$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
facade 上的 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 驅動器,當快取達到其大小限制時,儲存為 forever 的項目可能會被移除。
從快取中移除項目
您可以使用 forget
方法從快取中移除項目:
Cache::forget('key');
您也可以透過提供零或負數的過期秒數來移除項目:
Cache::put('key', 'value', 0);
Cache::put('key', 'value', -5);
您可以使用 flush
方法清除整個快取:
Cache::flush();
⚠️ 警告
清除快取不會遵循您配置的快取“前綴” ,並且會從快取中移除所有項目。當清除由其他應用程式共用的快取時,請仔細考慮這一點。
快取輔助函式
除了使用 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();
});
📌 備註
當測試全域 cache
函式的呼叫時,您可以使用 Cache::shouldReceive
方法,就像您在測試 Facade 一樣。
原子鎖定
⚠️ 警告
若要使用此功能,您的應用程式必須使用 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...
});
跨程序管理鎖定
有時,您可能希望在一個程序中取得鎖定,然後在另一個程序中釋放它。例如,您可以在網頁請求期間取得鎖定,並希望在該請求觸發的佇列任務結束時釋放鎖定。在此情境下,您應該將鎖定的作用域「擁有者憑證」傳遞給佇列任務,以便任務可以使用給定的憑證重新實例化鎖定。
在下面的範例中,如果成功取得鎖定,我們將分派一個佇列任務。此外,我們將透過鎖定的 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
facade 的 extend
方法來完成自訂驅動器的註冊:
Cache::extend('mongo', function (Application $app) {
return Cache::repository(new MongoStore);
});
📌 備註
如果您想知道在哪裡放置自訂快取驅動器程式碼,您可以在 app
目錄中建立一個 Extensions
命名空間。然而,請記住,Laravel 沒有僵化的應用程式結構,您可以根據自己的偏好自由組織應用程式。
註冊驅動器
為了向 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
選項,將其設定為您的擴充功能名稱。
事件
為了在每次快取操作時執行程式碼,您可以監聽由 Cache 分派的各種事件:
事件名稱 |
---|
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,
],