跳到主要內容

Redis

📝 此頁面為 Laravel 官方文檔的繁體中文翻譯。查看原始英文版本

Redis

簡介

Redis 是一個開源的進階鍵值儲存。它通常被稱為資料結構伺服器,因為金鑰可以包含字串雜湊列表集合有序集合

在使用 Redis 與 Laravel 之前,我們鼓勵您透過 PECL 安裝並使用 PhpRedis PHP 擴展。該擴展比「使用者端」PHP 套件更複雜,但對於大量使用 Redis 的應用程式可能會產生更好的效能。如果您使用 Laravel Sail,此擴展已安裝在您應用程式的 Docker 容器中。

如果您無法安裝 PhpRedis 擴展,可以透過 Composer 安裝 predis/predis 套件。Predis 是一個完全用 PHP 編寫的 Redis 客戶端,不需要任何額外的擴展:

composer require predis/predis

配置

您可以透過 config/database.php 配置檔案來配置您應用程式的 Redis 設定。在此檔案中,您將看到一個包含您應用程式使用的 Redis 伺服器的 redis 陣列:

'redis' => [

    'client' => env('REDIS_CLIENT', 'phpredis'),

    'options' => [
        'cluster' => env('REDIS_CLUSTER', 'redis'),
        'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
    ],

    'default' => [
        'url' => env('REDIS_URL'),
        'host' => env('REDIS_HOST', '127.0.0.1'),
        'username' => env('REDIS_USERNAME'),
        'password' => env('REDIS_PASSWORD'),
        'port' => env('REDIS_PORT', '6379'),
        'database' => env('REDIS_DB', '0'),
    ],

    'cache' => [
        'url' => env('REDIS_URL'),
        'host' => env('REDIS_HOST', '127.0.0.1'),
        'username' => env('REDIS_USERNAME'),
        'password' => env('REDIS_PASSWORD'),
        'port' => env('REDIS_PORT', '6379'),
        'database' => env('REDIS_CACHE_DB', '1'),
    ],

],

配置檔案中定義的每個 Redis 伺服器都需要有名稱、主機和連接埠,除非您定義一個單一 URL 來表示 Redis 連接:

'redis' => [

    'client' => env('REDIS_CLIENT', 'phpredis'),

    'options' => [
        'cluster' => env('REDIS_CLUSTER', 'redis'),
        'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
    ],

    'default' => [
        'url' => 'tcp://127.0.0.1:6379?database=0',
    ],

    'cache' => [
        'url' => 'tls://user:password@127.0.0.1:6380?database=1',
    ],

],

配置連接方案

預設情況下,Redis 客戶端在連接到 Redis 伺服器時將使用 tcp 方案;但是,您可以透過在 Redis 伺服器的配置陣列中指定 scheme 配置選項來使用 TLS / SSL 加密:

'default' => [
    'scheme' => 'tls',
    'url' => env('REDIS_URL'),
    'host' => env('REDIS_HOST', '127.0.0.1'),
    'username' => env('REDIS_USERNAME'),
    'password' => env('REDIS_PASSWORD'),
    'port' => env('REDIS_PORT', '6379'),
    'database' => env('REDIS_DB', '0'),
],

叢集

如果您的應用程式正在使用 Redis 伺服器叢集,您應該在 Redis 配置的 clusters 鍵中定義這些叢集。此配置鍵預設不存在,因此您需要在應用程式的 config/database.php 配置檔案中建立它:

'redis' => [

    'client' => env('REDIS_CLIENT', 'phpredis'),

    'options' => [
        'cluster' => env('REDIS_CLUSTER', 'redis'),
        'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
    ],

    'clusters' => [
        'default' => [
            [
                'url' => env('REDIS_URL'),
                'host' => env('REDIS_HOST', '127.0.0.1'),
                'username' => env('REDIS_USERNAME'),
                'password' => env('REDIS_PASSWORD'),
                'port' => env('REDIS_PORT', '6379'),
                'database' => env('REDIS_DB', '0'),
            ],
        ],
    ],

    // ...
],

預設情況下,Laravel 將使用原生 Redis 叢集,因為 options.cluster 配置值設定為 redis。Redis 叢集是一個很好的預設選項,因為它可以優雅地處理故障轉移。

Laravel 在使用 Predis 時還支援客戶端分片。但是,客戶端分片無法處理故障轉移;因此,它主要適用於可從另一個主要資料儲存中取得的暫時性快取資料。

如果您希望使用客戶端分片而不是原生 Redis 叢集,可以從應用程式的 config/database.php 配置檔案中移除 options.cluster 配置值:

'redis' => [

    'client' => env('REDIS_CLIENT', 'phpredis'),

    'clusters' => [
        // ...
    ],

    // ...
],

Predis

如果您希望應用程式透過 Predis 套件與 Redis 互動,應確保 REDIS_CLIENT 環境變數的值為 predis

'redis' => [

    'client' => env('REDIS_CLIENT', 'predis'),

    // ...
],

除了預設配置選項外,Predis 還支援可為每個 Redis 伺服器定義的額外連接參數。要使用這些額外的配置選項,請將它們添加到您應用程式 config/database.php 配置檔案中的 Redis 伺服器配置中:

'default' => [
    'url' => env('REDIS_URL'),
    'host' => env('REDIS_HOST', '127.0.0.1'),
    'username' => env('REDIS_USERNAME'),
    'password' => env('REDIS_PASSWORD'),
    'port' => env('REDIS_PORT', '6379'),
    'database' => env('REDIS_DB', '0'),
    'read_write_timeout' => 60,
],

PhpRedis

預設情況下,Laravel 將使用 PhpRedis 擴展與 Redis 通訊。Laravel 用於與 Redis 通訊的客戶端由 redis.client 配置選項的值決定,該值通常反映 REDIS_CLIENT 環境變數的值:

'redis' => [

    'client' => env('REDIS_CLIENT', 'phpredis'),

    // ...
],

除了預設配置選項外,PhpRedis 還支援以下額外連接參數:namepersistentpersistent_idprefixread_timeoutretry_intervalmax_retriesbackoff_algorithmbackoff_basebackoff_captimeoutcontext。您可以將這些選項中的任何一個添加到 config/database.php 配置檔案中的 Redis 伺服器配置中:

'default' => [
    'url' => env('REDIS_URL'),
    'host' => env('REDIS_HOST', '127.0.0.1'),
    'username' => env('REDIS_USERNAME'),
    'password' => env('REDIS_PASSWORD'),
    'port' => env('REDIS_PORT', '6379'),
    'database' => env('REDIS_DB', '0'),
    'read_timeout' => 60,
    'context' => [
        // 'auth' => ['username', 'secret'],
        // 'stream' => ['verify_peer' => false],
    ],
],

重試和退避配置

retry_intervalmax_retriesbackoff_algorithmbackoff_basebackoff_cap 選項可用於配置 PhpRedis 客戶端應如何嘗試重新連接到 Redis 伺服器。支援以下退避演算法:defaultdecorrelated_jitterequal_jitterexponentialuniformconstant

'default' => [
    'url' => env('REDIS_URL'),
    'host' => env('REDIS_HOST', '127.0.0.1'),
    'username' => env('REDIS_USERNAME'),
    'password' => env('REDIS_PASSWORD'),
    'port' => env('REDIS_PORT', '6379'),
    'database' => env('REDIS_DB', '0'),
    'max_retries' => env('REDIS_MAX_RETRIES', 3),
    'backoff_algorithm' => env('REDIS_BACKOFF_ALGORITHM', 'decorrelated_jitter'),
    'backoff_base' => env('REDIS_BACKOFF_BASE', 100),
    'backoff_cap' => env('REDIS_BACKOFF_CAP', 1000),
],

Predis 3.4.0 及更高版本支援透過 Retry 類別進行內建重試和退避配置。使用以下策略之一配置 retry 選項:NoBackoffEqualBackoffExponentialBackoff

use Predis\Retry;
use Predis\Retry\Strategy\ExponentialBackoff;

'default' => [
    'url' => env('REDIS_URL'),
    // ...
    'retry' => new Retry(
        new ExponentialBackoff(
            env('REDIS_BACKOFF_BASE', 100),
            env('REDIS_BACKOFF_CAP', 1000),
            true, // 啟用抖動
        ),
        env('REDIS_MAX_RETRIES', 3)
    )
],

Unix Socket 連接

Redis 連接也可以配置為使用 Unix socket 而不是 TCP。這可以透過消除與應用程式在同一伺服器上的 Redis 實例的連接的 TCP 開銷來提高效能。要配置 Redis 使用 Unix socket,請將 REDIS_HOST 環境變數設定為 Redis socket 的路徑,並將 REDIS_PORT 環境變數設定為 0

REDIS_HOST=/run/redis/redis.sock
REDIS_PORT=0

PhpRedis 序列化和壓縮

PhpRedis 擴展還可以配置為使用多種序列化和壓縮演算法。這些演算法可以透過 Redis 配置的 options 陣列來配置:

'redis' => [

    'client' => env('REDIS_CLIENT', 'phpredis'),

    'options' => [
        'cluster' => env('REDIS_CLUSTER', 'redis'),
        'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
        'serializer' => Redis::SERIALIZER_MSGPACK,
        'compression' => Redis::COMPRESSION_LZ4,
    ],

    // ...
],

目前支援的序列化器包括:Redis::SERIALIZER_NONE(預設)、Redis::SERIALIZER_PHPRedis::SERIALIZER_JSONRedis::SERIALIZER_IGBINARYRedis::SERIALIZER_MSGPACK

支援的壓縮演算法包括:Redis::COMPRESSION_NONE(預設)、Redis::COMPRESSION_LZFRedis::COMPRESSION_ZSTDRedis::COMPRESSION_LZ4

與 Redis 互動

您可以透過在 Redis外觀上呼叫各種方法來與 Redis 互動。Redis 外觀支援動態方法,這意味著您可以在外觀上呼叫任何 Redis 命令,該命令將被直接傳遞給 Redis。在此範例中,我們將透過在 Redis 外觀上呼叫 get 方法來呼叫 Redis GET 命令:

<?php

namespace App\Http\Controllers;

use Illuminate\Support\Facades\Redis;
use Illuminate\View\View;

class UserController extends Controller
{
    /**
     * 顯示給定使用者的個人資料。
     */
    public function show(string $id): View
    {
        return view('user.profile', [
            'user' => Redis::get('user:profile:'.$id)
        ]);
    }
}

如上所述,您可以在 Redis 外觀上呼叫任何 Redis 命令。Laravel 使用魔法方法將命令傳遞給 Redis 伺服器。如果 Redis 命令需要參數,您應該將它們傳遞給外觀的對應方法:

use Illuminate\Support\Facades\Redis;

Redis::set('name', 'Taylor');

$values = Redis::lrange('names', 5, 10);

或者,您可以使用 Redis 外觀的 command 方法將命令傳遞給伺服器,該方法接受命令名稱作為第一個參數,值陣列作為第二個參數:

$values = Redis::command('lrange', ['name', 5, 10]);

使用多個 Redis 連接

您應用程式的 config/database.php 配置檔案允許您定義多個 Redis 連接/伺服器。您可以使用 Redis 外觀的 connection 方法來取得特定 Redis 連接的連接:

$redis = Redis::connection('connection-name');

要取得預設 Redis 連接的實例,您可以在不附加任何額外參數的情況下呼叫 connection 方法:

$redis = Redis::connection();

交易

Redis 外觀的 transaction 方法提供了 Redis 原生 MULTIEXEC 命令的便利包裝器。transaction 方法接受一個閉包作為其唯一參數。此閉包將接收一個 Redis 連接實例,並可以向該實例發出它想要的任何命令。在閉包內發出的所有 Redis 命令將在單個原子交易中執行:

use Redis;
use Illuminate\Support\Facades;

\Facades\Redis::transaction(function (Redis $redis) {
    $redis->incr('user_visits', 1);
    $redis->incr('total_visits', 1);
});

[!WARNING] 定義 Redis 交易時,您不能從 Redis 連接中檢索任何值。請記住,您的交易將作為單個原子操作執行,該操作在您的整個閉包完成執行其命令之前不會執行。

Lua 腳本

eval 方法提供了在單個原子操作中執行多個 Redis 命令的另一種方法。但是,eval 方法的好處是能夠在該操作期間與 Redis 鍵值互動並檢查它們。Redis 腳本是用 Lua 程式語言編寫的。

eval 方法一開始可能有點可怕,但我們將探索一個基本範例來打破僵局。eval 方法需要幾個參數。首先,您應該將 Lua 腳本(作為字串)傳遞給方法。其次,您應該傳遞腳本互動的金鑰數量(作為整數)。第三,您應該傳遞這些金鑰的名稱。最後,您可以傳遞您需要在腳本中存取的任何其他額外參數。

在此範例中,我們將增加一個計數器,檢查其新值,然後如果第一個計數器的值大於五,則增加第二個計數器。最後,我們將傳回第一個計數器的值:

$value = Redis::eval(<<<'LUA'
    local counter = redis.call("incr", KEYS[1])

    if counter > 5 then
        redis.call("incr", KEYS[2])
    end

    return counter
LUA, 2, 'first-counter', 'second-counter');

[!WARNING] 請查閱 Redis 文檔以了解更多關於 Redis 腳本的資訊。

管線命令

有時您可能需要執行數十個 Redis 命令。與其為每個命令向 Redis 伺服器發出網路請求,不如使用 pipeline 方法。pipeline 方法接受一個參數:一個接收 Redis 實例的閉包。您可以向此 Redis 實例發出所有命令,它們將同時發送到 Redis 伺服器,以減少向伺服器的網路請求。命令仍將按發出順序執行:

use Redis;
use Illuminate\Support\Facades;

\Facades\Redis::pipeline(function (Redis $pipe) {
    for ($i = 0; $i < 1000; $i++) {
        $pipe->set("key:$i", $i);
    }
});

發佈 / 訂閱

Laravel 提供了 Redis publishsubscribe 命令的便利介面。這些 Redis 命令允許您在給定的「頻道」上監聽訊息。您可以從另一個應用程式或甚至使用另一種程式語言將訊息發佈到頻道,從而實現應用程式和程序之間的輕鬆通訊。

首先,讓我們使用 subscribe 方法設置一個頻道監聽器。我們將把此方法呼叫放在 Artisan 命令中,因為呼叫 subscribe 方法會開始一個長時間運行的過程:

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\Redis;

class RedisSubscribe extends Command
{
    /**
     * 控制台命令的名稱和簽名。
     *
     * @var string
     */
    protected $signature = 'redis:subscribe';

    /**
     * 控制台命令描述。
     *
     * @var string
     */
    protected $description = '訂閱 Redis 頻道';

    /**
     * 執行控制台命令。
     */
    public function handle(): void
    {
        Redis::subscribe(['test-channel'], function (string $message) {
            echo $message;
        });
    }
}

現在我們可以使用 publish 方法向頻道發佈訊息:

use Illuminate\Support\Facades\Redis;

Route::get('/publish', function () {
    // ...

    Redis::publish('test-channel', json_encode([
        'name' => 'Adam Wathan'
    ]));
});

萬用字元訂閱

使用 psubscribe 方法,您可以訂閱萬用字元頻道,這對於捕獲所有頻道上的所有訊息很有用。頻道名稱將作為第二個參數傳遞給提供的閉包:

Redis::psubscribe(['*'], function (string $message, string $channel) {
    echo $message;
});

Redis::psubscribe(['users.*'], function (string $message, string $channel) {
    echo $message;
});