緩存系統(tǒng)
緩存系統(tǒng)
緩存系統(tǒng)
配置
Laravel 為各種后端緩存提供豐富而統(tǒng)一的 API,其配置信息位于 config/cache.php
文件中。在該文件中你可以指定應(yīng)用默認(rèn)使用哪個緩存驅(qū)動。Laravel 支持當(dāng)前流行的后端緩存,例如 Memcached 和 Redis 。
緩存配置文件還包含各種其他選項,這些選項都記錄在文件中,因此請確保閱讀這些選項。默認(rèn)情況下,Laravel 配置為使用 file
緩存驅(qū)動,它將序列化的緩存對象存儲在文件系統(tǒng)中。對于大型應(yīng)用,建議您使用更強大的驅(qū)動程序,例如 Memcached 或 Redis。你甚至可以為同一個驅(qū)動程序配置多個緩存配置。
驅(qū)動的前提條件
數(shù)據(jù)庫
當(dāng)使用 database
緩存驅(qū)動時,你需要配置一個表來存放緩存數(shù)據(jù)。下面是構(gòu)建緩存數(shù)據(jù)表結(jié)構(gòu)的 Schema
聲明示例:
Schema::create('cache', function ($table) { $table->string('key')->unique(); $table->text('value'); $table->integer('expiration'); });
{tip} 你也可以使用 Artisan 命令
php artisan cache:table
來生成合適的遷移。
Memcached
使用 Memcached 驅(qū)動需要安裝 Memcached PECL 擴展包 。你可以把所有的 Memcached 服務(wù)器都列在 config/cache.php
配置文件中:
'memcached' => [ [ 'host' => '127.0.0.1', 'port' => 11211, 'weight' => 100 ], ],
你可以將 host
選項設(shè)置為 UNIX socket 路徑。如果你這樣配置了, port
選項應(yīng)該設(shè)置為 0
:
'memcached' => [ [ 'host' => '/var/run/memcached/memcached.sock', 'port' => 0, 'weight' => 100 ], ],
Redis
在使用 Laravel 的 Redis 緩存之前,你需要通過 Composer 安裝 predis/predis
擴展包 (~1.0) 或者使用 PECL 安裝 PhpRedis PHP 擴展。
如需了解更多關(guān)于 Redis 的配置,請參考 Laravel Redis 文檔。
緩存的使用
獲取緩存實例
Illuminate\Contracts\Cache\Factory
和 Illuminate\Contracts\Cache\Repository
契約 提供了 Laravel 緩存服務(wù)的訪問機制。 Factory
契約為你的應(yīng)用程序定義了訪問所有緩存驅(qū)動的機制。 Repository
契約通常是由你的 cache
配置文件指定的默認(rèn)緩存驅(qū)動實現(xiàn)的。
不過,你也可以使用 Cache
Facade,我們將在后續(xù)的文檔中介紹。 Cache
Facade 為 Laravel 緩存契約底層的實現(xiàn)提供了方便又簡潔的方法:
<?php namespace App\Http\Controllers; use Illuminate\Support\Facades\Cache; class UserController extends Controller{ /** * 展示應(yīng)用的所有用戶列表。 * * @return Response */ public function index() { $value = Cache::get('key'); // } }
訪問多個緩存存儲
使用 Cache
Facade,你可以通過 store
方法來訪問各種緩存存儲。傳入 store
方法的鍵應(yīng)該對應(yīng) cache
配置信息文件中的 stores
配置數(shù)組中所列的存儲之一:
$value = Cache::store('file')->get('foo'); Cache::store('redis')->put('bar', 'baz', 600); // 10 分鐘
從緩存中獲取數(shù)據(jù)
Cache
Facade 的 get
方法是用來從緩存中獲取數(shù)據(jù)的方法。如果該數(shù)據(jù)在緩存中不存在,那么該方法將返回 null
。正如你想的那樣,你也可以向 get
方法傳遞第二個參數(shù),用來指定如果查找的數(shù)據(jù)不存在時你希望返回的默認(rèn)值:
$value = Cache::get('key'); $value = Cache::get('key', 'default');
你甚至可以傳遞 Closure
作為默認(rèn)值。如果指定的數(shù)據(jù)在緩存中不存在,將返回 Closure
的結(jié)果。傳遞閉包的方法允許你從數(shù)據(jù)庫或其他外部服務(wù)中獲取默認(rèn)值:
$value = Cache::get('key', function () { return DB::table(...)->get(); });
檢查緩存項是否存在
has
方法可以用于判斷緩存項是否存在。如果為 null
或 false
則該方法將會返回 false
:
if (Cache::has('key')) { // }
遞增與遞減值
increment
和 decrement
方法可以用來調(diào)整緩存中整數(shù)項的值。這兩個方法都可以傳入第二個可選參數(shù),這個參數(shù)用來指明要遞增或遞減的數(shù)量:
Cache::increment('key'); Cache::increment('key', $amount); Cache::decrement('key'); Cache::decrement('key', $amount);
獲取和存儲
有時你可能想從緩存中獲取一個數(shù)據(jù),而當(dāng)請求的緩存項不存在時,程序能為你存儲一個默認(rèn)值。例如,你可能想從緩存中獲取所有用戶,當(dāng)緩存中不存在這些用戶時,程序?qū)臄?shù)據(jù)庫將這些用戶取出并放入緩存。你可以使用 Cache::remember
方法來實現(xiàn):
$value = Cache::remember('users', $seconds, function () { return DB::table('users')->get(); });
如果緩存中不存在你想要的數(shù)據(jù)時,則傳遞給 remember
方法的 閉包
將被執(zhí)行,然后將其結(jié)果返回并放置到緩存中。
你可以使用 rememberForever
方法從緩存中獲取數(shù)據(jù)或者永久存儲它:
$value = Cache::rememberForever('users', function () { return DB::table('users')->get(); });
獲取和刪除
如果你需要從緩存中獲取到數(shù)據(jù)之后再刪除它,你可以使用 pull
方法。和 get
方法一樣,如果緩存不存在,則返回 null
:
$value = Cache::pull('key');
在緩存中存儲數(shù)據(jù)
你可以使用 Cache
Facade 的 put
方法將數(shù)據(jù)存儲到緩存中:
Cache::put('key', 'value', $seconds);
如果緩存的過期時間沒有傳遞給 put
方法, 則緩存將永久有效:
Cache::put('key', 'value');
除了以整數(shù)形式傳遞過期時間的秒數(shù),你還可以傳遞一個 DateTime
實例來表示該數(shù)據(jù)的過期時間:
Cache::put('key', 'value', now()->addMinutes(10));
只存儲沒有的數(shù)據(jù)
add
方法將只存儲緩存中不存在的數(shù)據(jù)。如果存儲成功,將返回 true
,否則返回 false
:
Cache::add('key', 'value', $seconds);
數(shù)據(jù)永久存儲
forever
方法可用于持久化將數(shù)據(jù)存儲到緩存中。因為這些數(shù)據(jù)不會過期,所以必須通過 forget
方法從緩存中手動刪除它們:
Cache::forever('key', 'value');
{tip} 如果你使用 Memcached 驅(qū)動,當(dāng)緩存數(shù)據(jù)達到存儲上限時,「永久存儲」 的數(shù)據(jù)可能會被刪除。
刪除緩存中的數(shù)據(jù)
你可以使用 forget
方法從緩存中刪除這些數(shù)據(jù):
Cache::forget('key');
你也可以通過提供零或者負(fù)的 TTL 值刪除這些數(shù)據(jù):
Cache::put('key', 'value', 0); Cache::put('key', 'value', -5);
你可以使用 flush
方法清空所有的緩存:
Cache::flush();
{note} 清空緩存的方法并不會考慮緩存前綴,會將緩存中的所有內(nèi)容刪除。因此在清除與其它應(yīng)用程序共享的緩存時,請慎重考慮。
原子鎖
{note} 要使用該特性,你的應(yīng)用必須使用
memcached
,dynamodb
或redis
緩存驅(qū)動作為你應(yīng)用的默認(rèn)緩存驅(qū)動。此外,所有服務(wù)器必須與同一中央緩存服務(wù)器進行通信。
原子鎖允許對分布式鎖進行操作而不必?fù)?dān)心競爭條件。例如, Laravel Forge 使用原子鎖來確保在一臺服務(wù)器上每次只有一個遠程任務(wù)在執(zhí)行。你可以使用 Cache::lock
方法來創(chuàng)建和管理鎖:
use Illuminate\Support\Facades\Cache; $lock = Cache::lock('foo', 10); if ($lock->get()) { //獲取鎖定10秒... $lock->release(); }
get
方法也可以接收一個閉包。在閉包執(zhí)行之后,Laravel 將會自動釋放鎖:
Cache::lock('foo')->get(function () { // 獲取無限期鎖并自動釋放... });
如果你在請求時鎖無法使用,你可以控制 Laravel 等待指定的秒數(shù)。如果在指定的時間限制內(nèi)無法獲取鎖,則會拋出 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 請求期間獲取鎖,并希望在該請求觸發(fā)的隊列作業(yè)結(jié)束時釋放鎖。在這種情況下,你應(yīng)該將鎖的作用域「owner token」傳遞給隊列作業(yè),以便作業(yè)可以使用給定的 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();
如果你想在不尊重當(dāng)前鎖的所有者的情況下釋放鎖,你可以使用 forceRelease
方法:
Cache::lock('foo')->forceRelease();
Cache 輔助函數(shù)
除了可以使用 Cache
Facade 或 Cache 契約 外,你還可以使用全局輔助函數(shù) cache
來獲取和保存緩存數(shù)據(jù)。當(dāng) cache
函數(shù)只接收一個字符串參數(shù)的時候,它將會返回給定鍵對應(yīng)的值:
$value = cache('key');
如果你向函數(shù)提供了一組鍵值對和過期時間,它將會在指定時間內(nèi)緩存數(shù)據(jù):
cache(['key' => 'value'], $seconds); cache(['key' => 'value'], now()->addMinutes(10));
當(dāng) cache
函數(shù)在沒有任何參數(shù)的情況下被調(diào)用時,它返回一個 Illuminate\Contracts\Cache\Factory
實現(xiàn)的實例,允許你調(diào)用其它緩存方法:
cache()->remember('users', $seconds, function () { return DB::table('users')->get(); });
{tip} 如果在測試中使用全局輔助函數(shù)
cache
,你可以使用Cache::shouldReceive
方法就像 測試 Facade。
緩存標(biāo)記
{note} 緩存標(biāo)記不支持使用
file
和database
緩存驅(qū)動。此外,當(dāng)使用多個緩存標(biāo)記的緩存設(shè)置為「永久」時,類似memcached
的緩存驅(qū)動性能最佳,它會自動清除舊的記錄。
寫入被標(biāo)記的緩存數(shù)據(jù)
緩存標(biāo)記允許你給緩存相關(guān)進行標(biāo)記,以便后續(xù)清除這些緩存值。你可以通過傳入標(biāo)記名稱的有序數(shù)組來訪問標(biāo)記的緩存。例如,我們可以使用標(biāo)記的同時使用 put
方法設(shè)置緩存。
Cache::tags(['people', 'artists'])->put('John', $john, $seconds); Cache::tags(['people', 'authors'])->put('Anne', $anne, $seconds);
訪問被標(biāo)記的緩存數(shù)據(jù)
若要獲取一個被標(biāo)記的緩存數(shù)據(jù),請將相同的有序標(biāo)記數(shù)組傳遞給 tags
方法,然后調(diào)用 get
方法來獲取你要檢索的鍵:
$john = Cache::tags(['people', 'artists'])->get('John'); $anne = Cache::tags(['people', 'authors'])->get('Anne');
移除被標(biāo)記的緩存數(shù)據(jù)
你可以清空有單個標(biāo)記或是一組標(biāo)記的所有緩存數(shù)據(jù)。例如,下面的語句會被標(biāo)記為 people
,authors
或兩者都有的緩存。所以,Anne
和 John
都會從緩存中被刪除:
Cache::tags(['people', 'authors'])->flush();
相反,下面的語句只會刪除被標(biāo)記 authors
的緩存,所以 Anne
會被刪除,但 John
不會:
Cache::tags('authors')->flush();
增加自定義的緩存驅(qū)動
編寫驅(qū)動
要創(chuàng)建自定義的緩存驅(qū)動,首先需要實現(xiàn) Illuminate\Contracts\Cache\Store
契約。因此, MongoDB 的緩存實現(xiàn)看起來會像這樣:
<?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 的連接來實現(xiàn)這些方法。關(guān)于如何實現(xiàn)這些方法的實例,可以參閱框架源代碼中的 Illuminate\Cache\MemcachedStore
。一旦完成契約額實現(xiàn)后,就可以像下面這樣完成自定義驅(qū)動的注冊了。
Cache::extend('mongo', function ($app) { return Cache::repository(new MongoStore); });
{tip} 如果你不知道將緩存驅(qū)動代碼放在哪,你可以在
app
目錄下創(chuàng)建一個Extensions
命名空間。然而,Laravel 并沒有硬性規(guī)定應(yīng)用程序的結(jié)構(gòu),你可以根據(jù)自己的喜好自由組織你的應(yīng)用程序。
注冊驅(qū)動
要使用 Laravel 來注冊自定義緩存驅(qū)動,就要在 Cache
Facade 上使用 extend
方法。 對 Cache::extend
的調(diào)用可以在新的 Laravel 應(yīng)用程序中自帶的 App\Providers\AppServiceProvider
的 boot
方法中完成,或者你也可以創(chuàng)建自己的服務(wù)提供者來存放擴展,只是不要忘記在 config/app.php
的 provider 數(shù)組中注冊服務(wù)提供者:
<?php namespace App\Providers;use App\Extensions\MongoStore; use Illuminate\Support\Facades\Cache; use Illuminate\Support\ServiceProvider; class CacheServiceProvider extends ServiceProvider{ /** * 執(zhí)行服務(wù)的注冊后引導(dǎo)。 * * @return void */ public function boot() { Cache::extend('mongo', function ($app) { return Cache::repository(new MongoStore); }); } /** * 在容器中注冊綁定。 * * @return void */ public function register() { // } }
傳遞給 extend
方法的第一個參數(shù)是驅(qū)動程序的名稱。這將與 config/cache.php
配置文件的 driver
選項相對應(yīng)。第二個參數(shù)是一個應(yīng)該返回 Illuminate\Cache\Repository
實例的閉包。該閉包將傳遞一個 服務(wù)容器 的 $app
實例。
一旦你的擴展程序注冊后,需要將 config/cache.php
配置文件中的 driver
選項更新為你的擴展名稱。
事件
要在每次緩存操作時執(zhí)行代碼,你可以監(jiān)聽緩存觸發(fā)的 事件 。通常,你應(yīng)該將這些事件監(jiān)聽器放在 EventServiceProvider
中:
/** * 應(yīng)用的事件監(jiān)聽器映射 * * @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', ], ];