跳到內容

快取

編輯此頁面

使用快取是讓您的應用程式執行更快速的好方法。Symfony 快取元件附帶許多適用於不同儲存方式的轉接器。每個轉接器都為了高效能而開發。

以下範例顯示快取的典型用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
use Symfony\Contracts\Cache\ItemInterface;

// The callable will only be executed on a cache miss.
$value = $pool->get('my_cache_key', function (ItemInterface $item): string {
    $item->expiresAfter(3600);

    // ... do some HTTP request or heavy computations
    $computedValue = 'foobar';

    return $computedValue;
});

echo $value; // 'foobar'

// ... and to remove the cache key
$pool->delete('my_cache_key');

Symfony 支援快取契約和 PSR-6/16 介面。您可以在組件文件中閱讀更多相關資訊。

使用 FrameworkBundle 設定快取

在設定快取元件時,您應該了解一些概念

池 (Pool)
這是您將互動的服務。每個池都會有自己的命名空間和快取項目。池之間永遠不會有衝突。
轉接器 (Adapter)
轉接器是您用來建立池的範本
提供器 (Provider)
提供器是一些轉接器用來連接到儲存體的服務。Redis 和 Memcached 是此類轉接器的範例。如果 DSN 用作提供器,則會自動建立服務。

預設情況下,始終啟用兩個池。它們是 cache.appcache.system。系統快取用於註解、序列化器和驗證等。 cache.app 可在您的程式碼中使用。您可以使用 appsystem 鍵來設定它們使用的轉接器(範本),例如:

1
2
3
4
5
# config/packages/cache.yaml
framework:
    cache:
        app: cache.adapter.filesystem
        system: cache.adapter.system

提示

雖然可以重新設定 system 快取,但建議保持 Symfony 應用於它的預設設定。

快取組件隨附一系列預先設定的轉接器

注意

還有一個特殊的 cache.adapter.system 轉接器。建議將其用於系統快取。此轉接器使用一些邏輯來動態選擇基於您的系統的最佳可能儲存方式(PHP 檔案或 APCu)。

其中一些轉接器可以透過捷徑設定。

1
2
3
4
5
6
7
8
9
10
# config/packages/cache.yaml
framework:
    cache:
        directory: '%kernel.cache_dir%/pools' # Only used with cache.adapter.filesystem

        default_doctrine_dbal_provider: 'doctrine.dbal.default_connection'
        default_psr6_provider: 'app.my_psr6_service'
        default_redis_provider: 'redis://127.0.0.1'
        default_memcached_provider: 'memcached://127.0.0.1'
        default_pdo_provider: 'pgsql:host=localhost'

7.1

在 Symfony 7.1 中引入了使用 DSN 作為 PDO 轉接器的提供器。

建立自訂(命名空間)池

您也可以建立更多自訂池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# config/packages/cache.yaml
framework:
    cache:
        default_memcached_provider: 'memcached://127.0.0.1'

        pools:
            # creates a "custom_thing.cache" service
            # autowireable via "CacheInterface $customThingCache"
            # uses the "app" cache configuration
            custom_thing.cache:
                adapter: cache.app

            # creates a "my_cache_pool" service
            # autowireable via "CacheInterface $myCachePool"
            my_cache_pool:
                adapter: cache.adapter.filesystem

            # uses the default_memcached_provider from above
            acme.cache:
                adapter: cache.adapter.memcached

            # control adapter's configuration
            foobar.cache:
                adapter: cache.adapter.memcached
                provider: 'memcached://user:password@example.com'

            # uses the "foobar.cache" pool as its backend but controls
            # the lifetime and (like all pools) has a separate cache namespace
            short_cache:
                adapter: foobar.cache
                default_lifetime: 60

每個池管理一組獨立的快取鍵:來自不同池的鍵永遠不會衝突,即使它們共享相同的後端也是如此。這是透過使用命名空間為鍵加上前綴來實現的,該命名空間是透過雜湊池的名稱、快取轉接器類別的名稱和可設定種子(預設為專案目錄和編譯容器類別)產生的。

每個自訂池都會成為一個服務,其服務 ID 是池的名稱(例如,custom_thing.cache)。也會為每個池建立自動裝配別名,使用其名稱的駝峰式版本 - 例如,custom_thing.cache 可以透過命名參數 $customThingCache 並使用 CacheInterfacePsr\Cache\CacheItemPoolInterface 進行類型提示來自動注入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
use Symfony\Contracts\Cache\CacheInterface;
// ...

// from a controller method
public function listProducts(CacheInterface $customThingCache): Response
{
    // ...
}

// in a service
public function __construct(private CacheInterface $customThingCache)
{
    // ...
}

提示

如果您需要命名空間與第三方應用程式互通,您可以透過設定 cache.pool 服務標籤的 namespace 屬性來控制自動產生。例如,您可以覆寫轉接器的服務定義

1
2
3
4
5
6
7
8
# config/services.yaml
services:
    # ...

    app.cache.adapter.redis:
        parent: 'cache.adapter.redis'
        tags:
            - { name: 'cache.pool', namespace: 'my_custom_namespace' }

自訂提供器選項

某些提供器具有可以設定的特定選項。RedisAdapter 允許您使用選項 timeoutretry_interval 等建立提供器。若要將這些選項與非預設值一起使用,您需要建立自己的 \Redis 提供器,並在設定池時使用該提供器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# config/packages/cache.yaml
framework:
    cache:
        pools:
            cache.my_redis:
                adapter: cache.adapter.redis
                provider: app.my_custom_redis_provider

services:
    app.my_custom_redis_provider:
        class: \Redis
        factory: ['Symfony\Component\Cache\Adapter\RedisAdapter', 'createConnection']
        arguments:
            - 'redis://127.0.0.1'
            - { retry_interval: 2, timeout: 10 }

建立快取鏈

不同的快取轉接器具有不同的優點和缺點。有些可能非常快速,但最佳化用於儲存小型項目,而有些可能能夠包含大量資料,但速度相當慢。為了獲得兩全其美的效果,您可以使用轉接器鏈。

快取鏈將多個快取池組合為一個。當在快取鏈中儲存項目時,Symfony 會依序將其儲存在所有池中。當檢索項目時,Symfony 會嘗試從第一個池中取得它。如果找不到,它會嘗試下一個池,直到找到該項目或拋出例外狀況。由於此行為,建議以從最快到最慢的順序定義鏈中的轉接器。

如果在池中儲存項目時發生錯誤,Symfony 會將其儲存在其他池中,並且不會拋出例外狀況。稍後,當檢索項目時,Symfony 會自動將該項目儲存在所有遺失的池中。

1
2
3
4
5
6
7
8
9
10
# config/packages/cache.yaml
framework:
    cache:
        pools:
            my_cache_pool:
                default_lifetime: 31536000  # One year
                adapters:
                  - cache.adapter.array
                  - cache.adapter.apcu
                  - {name: cache.adapter.redis, provider: 'redis://user:password@example.com'}

使用快取標籤

在具有許多快取鍵的應用程式中,組織儲存的資料以更有效率地使快取失效可能很有用。實現這一目標的一種方法是使用快取標籤。可以將一個或多個標籤新增到快取項目。可以使用一個函數呼叫使具有相同標籤的所有項目失效

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
use Symfony\Contracts\Cache\ItemInterface;
use Symfony\Contracts\Cache\TagAwareCacheInterface;

class SomeClass
{
    // using autowiring to inject the cache pool
    public function __construct(
        private TagAwareCacheInterface $myCachePool,
    ) {
    }

    public function someMethod(): void
    {
        $value0 = $this->myCachePool->get('item_0', function (ItemInterface $item): string {
            $item->tag(['foo', 'bar']);

            return 'debug';
        });

        $value1 = $this->myCachePool->get('item_1', function (ItemInterface $item): string {
            $item->tag('foo');

            return 'debug';
        });

        // Remove all cache keys tagged with "bar"
        $this->myCachePool->invalidateTags(['bar']);
    }
}

快取轉接器需要實作 TagAwareCacheInterface 才能啟用此功能。這可以透過使用以下設定來新增。

1
2
3
4
5
6
# config/packages/cache.yaml
framework:
    cache:
        pools:
            my_cache_pool:
                adapter: cache.adapter.redis_tag_aware

標籤預設儲存在同一個池中。這在大多數情況下都很好。但有時最好將標籤儲存在不同的池中。這可以透過指定轉接器來實現。

1
2
3
4
5
6
7
8
9
# config/packages/cache.yaml
framework:
    cache:
        pools:
            my_cache_pool:
                adapter: cache.adapter.redis
                tags: tag_pool
            tag_pool:
                adapter: cache.adapter.apcu

注意

TagAwareCacheInterface 介面會自動裝配到 cache.app 服務。

清除快取

若要清除快取,您可以使用 bin/console cache:pool:clear [pool] 命令。這將移除儲存體中的所有項目,您必須重新計算所有值。您也可以將池分組到「快取清除器」中。預設情況下有 3 個快取清除器

  • cache.global_clearer
  • cache.system_clearer
  • cache.app_clearer

全域清除器清除每個池中的所有快取項目。系統快取清除器用於 bin/console cache:clear 命令。應用程式清除器是預設清除器。

查看所有可用的快取池

1
$ php bin/console cache:pool:list

清除一個池

1
$ php bin/console cache:pool:clear my_cache_pool

清除所有自訂池

1
$ php bin/console cache:pool:clear cache.app_clearer

清除所有快取池

1
$ php bin/console cache:pool:clear --all

清除除某些以外的所有快取池

1
$ php bin/console cache:pool:clear --all --exclude=my_cache_pool --exclude=another_cache_pool

清除所有位置的快取

1
$ php bin/console cache:pool:clear cache.global_clearer

依標籤清除快取

1
2
3
4
5
6
7
8
9
10
11
# invalidate tag1 from all taggable pools
$ php bin/console cache:pool:invalidate-tags tag1

# invalidate tag1 & tag2 from all taggable pools
$ php bin/console cache:pool:invalidate-tags tag1 tag2

# invalidate tag1 & tag2 from cache.app pool
$ php bin/console cache:pool:invalidate-tags tag1 tag2 --pool=cache.app

# invalidate tag1 & tag2 from cache1 & cache2 pools
$ php bin/console cache:pool:invalidate-tags tag1 tag2 -p cache1 -p cache2

加密快取

若要使用 libsodium 加密快取,您可以使用 SodiumMarshaller

首先,您需要產生一個安全金鑰,並將其作為 CACHE_DECRYPTION_KEY 新增到您的密鑰儲存

1
$ php -r 'echo base64_encode(sodium_crypto_box_keypair());'

然後,使用此金鑰註冊 SodiumMarshaller 服務

1
2
3
4
5
6
7
8
9
10
11
# config/packages/cache.yaml

# ...
services:
    Symfony\Component\Cache\Marshaller\SodiumMarshaller:
        decorates: cache.default_marshaller
        arguments:
            - ['%env(base64:CACHE_DECRYPTION_KEY)%']
            # use multiple keys in order to rotate them
            #- ['%env(base64:CACHE_DECRYPTION_KEY)%', '%env(base64:OLD_CACHE_DECRYPTION_KEY)%']
            - '@.inner'

危險

這將加密快取項目值,但不加密快取鍵。請注意不要在金鑰中洩漏敏感資料。

在設定多個金鑰時,第一個金鑰將用於讀取和寫入,而其他金鑰僅用於讀取。一旦使用舊金鑰加密的所有快取項目都已過期,您就可以完全移除 OLD_CACHE_DECRYPTION_KEY

非同步計算快取值

快取組件使用機率性提前過期演算法來防止快取衝擊問題。這表示某些快取項目在仍然新鮮時被選定為提前過期。

預設情況下,過期的快取項目會同步計算。但是,您可以透過使用Messenger 組件將值計算委派給背景工作程序來非同步計算它們。在這種情況下,當查詢項目時,會立即傳回其快取值,並透過 Messenger 匯流排分派 EarlyExpirationMessage

當此訊息由訊息消費者處理時,會非同步計算刷新的快取值。下次查詢該項目時,刷新的值將會是新的並傳回。

首先,建立一個將計算項目值的服務

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// src/Cache/CacheComputation.php
namespace App\Cache;

use Symfony\Contracts\Cache\ItemInterface;

class CacheComputation
{
    public function compute(ItemInterface $item): string
    {
        $item->expiresAfter(5);

        // this is just a random example; here you must do your own calculation
        return sprintf('#%06X', mt_rand(0, 0xFFFFFF));
    }
}

此快取值將從控制器、另一個服務等請求。在以下範例中,該值是從控制器請求的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// src/Controller/CacheController.php
namespace App\Controller;

use App\Cache\CacheComputation;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Contracts\Cache\ItemInterface;

class CacheController extends AbstractController
{
    #[Route('/cache', name: 'cache')]
    public function index(CacheInterface $asyncCache): Response
    {
        // pass to the cache the service method that refreshes the item
        $cachedValue = $asyncCache->get('my_value', [CacheComputation::class, 'compute'])

        // ...
    }
}

最後,設定一個新的快取池(例如,稱為 async.cache),它將使用訊息匯流排在工作程序中計算值

1
2
3
4
5
6
7
8
9
10
11
12
# config/packages/framework.yaml
framework:
    cache:
        pools:
            async.cache:
                early_expiration_message_bus: messenger.default_bus

    messenger:
        transports:
            async_bus: '%env(MESSENGER_TRANSPORT_DSN)%'
        routing:
            'Symfony\Component\Cache\Messenger\EarlyExpirationMessage': async_bus

您現在可以啟動消費者

1
$ php bin/console messenger:consume async_bus

就是這樣!現在,每當從此快取池查詢項目時,都會立即傳回其快取值。如果它被選定為提前過期,則會透過匯流排傳送訊息以排程背景計算來刷新該值。

本作品,包括程式碼範例,根據 Creative Commons BY-SA 3.0 授權條款授權。
目錄
    版本