跳轉至內容

Redis

簡介

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

在 Laravel 中使用 Redis 之前,我們建議您透過 PECL 安裝並使用 PhpRedis PHP 擴充套件。與“使用者態”PHP 軟體包相比,該擴充套件安裝稍顯複雜,但對於大量使用 Redis 的應用程式來說,它能提供更好的效能。如果您正在使用 Laravel Sail,該擴充套件已預裝在您的應用程式 Docker 容器中。

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

1composer require predis/predis

配置

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

1'redis' => [
2 
3 'client' => env('REDIS_CLIENT', 'phpredis'),
4 
5 'options' => [
6 'cluster' => env('REDIS_CLUSTER', 'redis'),
7 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
8 ],
9 
10 'default' => [
11 'url' => env('REDIS_URL'),
12 'host' => env('REDIS_HOST', '127.0.0.1'),
13 'username' => env('REDIS_USERNAME'),
14 'password' => env('REDIS_PASSWORD'),
15 'port' => env('REDIS_PORT', '6379'),
16 'database' => env('REDIS_DB', '0'),
17 ],
18 
19 'cache' => [
20 'url' => env('REDIS_URL'),
21 'host' => env('REDIS_HOST', '127.0.0.1'),
22 'username' => env('REDIS_USERNAME'),
23 'password' => env('REDIS_PASSWORD'),
24 'port' => env('REDIS_PORT', '6379'),
25 'database' => env('REDIS_CACHE_DB', '1'),
26 ],
27 
28],

配置檔案中定義的每個 Redis 伺服器都需要包含名稱、主機和埠,除非您定義了單個 URL 來表示 Redis 連線。

1'redis' => [
2 
3 'client' => env('REDIS_CLIENT', 'phpredis'),
4 
5 'options' => [
6 'cluster' => env('REDIS_CLUSTER', 'redis'),
7 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
8 ],
9 
10 'default' => [
11 'url' => 'tcp://127.0.0.1:6379?database=0',
12 ],
13 
14 'cache' => [
15 'url' => 'tls://user:[email protected]:6380?database=1',
16 ],
17 
18],

配置連線方案

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

1'default' => [
2 'scheme' => 'tls',
3 'url' => env('REDIS_URL'),
4 'host' => env('REDIS_HOST', '127.0.0.1'),
5 'username' => env('REDIS_USERNAME'),
6 'password' => env('REDIS_PASSWORD'),
7 'port' => env('REDIS_PORT', '6379'),
8 'database' => env('REDIS_DB', '0'),
9],

叢集

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

1'redis' => [
2 
3 'client' => env('REDIS_CLIENT', 'phpredis'),
4 
5 'options' => [
6 'cluster' => env('REDIS_CLUSTER', 'redis'),
7 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
8 ],
9 
10 'clusters' => [
11 'default' => [
12 [
13 'url' => env('REDIS_URL'),
14 'host' => env('REDIS_HOST', '127.0.0.1'),
15 'username' => env('REDIS_USERNAME'),
16 'password' => env('REDIS_PASSWORD'),
17 'port' => env('REDIS_PORT', '6379'),
18 'database' => env('REDIS_DB', '0'),
19 ],
20 ],
21 ],
22 
23 // ...
24],

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

Laravel 在使用 Predis 時也支援客戶端分片。然而,客戶端分片無法處理故障轉移;因此,它主要適用於可從其他主資料儲存中獲取的瞬態快取資料。

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

1'redis' => [
2 
3 'client' => env('REDIS_CLIENT', 'phpredis'),
4 
5 'clusters' => [
6 // ...
7 ],
8 
9 // ...
10],

Predis

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

1'redis' => [
2 
3 'client' => env('REDIS_CLIENT', 'predis'),
4 
5 // ...
6],

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

1'default' => [
2 'url' => env('REDIS_URL'),
3 'host' => env('REDIS_HOST', '127.0.0.1'),
4 'username' => env('REDIS_USERNAME'),
5 'password' => env('REDIS_PASSWORD'),
6 'port' => env('REDIS_PORT', '6379'),
7 'database' => env('REDIS_DB', '0'),
8 'read_write_timeout' => 60,
9],

PhpRedis

預設情況下,Laravel 會使用 PhpRedis 擴充套件與 Redis 通訊。Laravel 使用哪個客戶端與 Redis 通訊,取決於 redis.client 配置選項的值,該值通常反映了 REDIS_CLIENT 環境變數的值。

1'redis' => [
2 
3 'client' => env('REDIS_CLIENT', 'phpredis'),
4 
5 // ...
6],

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

1'default' => [
2 'url' => env('REDIS_URL'),
3 'host' => env('REDIS_HOST', '127.0.0.1'),
4 'username' => env('REDIS_USERNAME'),
5 'password' => env('REDIS_PASSWORD'),
6 'port' => env('REDIS_PORT', '6379'),
7 'database' => env('REDIS_DB', '0'),
8 'read_timeout' => 60,
9 'context' => [
10 // 'auth' => ['username', 'secret'],
11 // 'stream' => ['verify_peer' => false],
12 ],
13],

重試和退避配置

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

1'default' => [
2 'url' => env('REDIS_URL'),
3 'host' => env('REDIS_HOST', '127.0.0.1'),
4 'username' => env('REDIS_USERNAME'),
5 'password' => env('REDIS_PASSWORD'),
6 'port' => env('REDIS_PORT', '6379'),
7 'database' => env('REDIS_DB', '0'),
8 'max_retries' => env('REDIS_MAX_RETRIES', 3),
9 'backoff_algorithm' => env('REDIS_BACKOFF_ALGORITHM', 'decorrelated_jitter'),
10 'backoff_base' => env('REDIS_BACKOFF_BASE', 100),
11 'backoff_cap' => env('REDIS_BACKOFF_CAP', 1000),
12],

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

1use Predis\Retry;
2use Predis\Retry\Strategy\ExponentialBackoff;
3 
4'default' => [
5 'url' => env('REDIS_URL'),
6 // ...
7 'retry' => new Retry(
8 new ExponentialBackoff(
9 env('REDIS_BACKOFF_BASE', 100),
10 env('REDIS_BACKOFF_CAP', 1000),
11 true, // Enables jitter
12 ),
13 env('REDIS_MAX_RETRIES', 3)
14 )
15],

Unix 套接字連線

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

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

PhpRedis 序列化與壓縮

PhpRedis 擴充套件還可以配置為使用各種序列化器和壓縮演算法。這些演算法可以透過 Redis 配置中的 options 陣列進行配置。

1'redis' => [
2 
3 'client' => env('REDIS_CLIENT', 'phpredis'),
4 
5 'options' => [
6 'cluster' => env('REDIS_CLUSTER', 'redis'),
7 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
8 'serializer' => Redis::SERIALIZER_MSGPACK,
9 'compression' => Redis::COMPRESSION_LZ4,
10 ],
11 
12 // ...
13],

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

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

與 Redis 互動

您可以透過在 Redis 門面 (facade) 上呼叫各種方法來與 Redis 互動。Redis 門面支援動態方法,這意味著您可以在門面上呼叫任何 Redis 命令,該命令將直接傳遞給 Redis。在此示例中,我們將透過在 Redis 門面上呼叫 get 方法來呼叫 Redis 的 GET 命令。

1<?php
2 
3namespace App\Http\Controllers;
4 
5use Illuminate\Support\Facades\Redis;
6use Illuminate\View\View;
7 
8class UserController extends Controller
9{
10 /**
11 * Show the profile for the given user.
12 */
13 public function show(string $id): View
14 {
15 return view('user.profile', [
16 'user' => Redis::get('user:profile:'.$id)
17 ]);
18 }
19}

如上所述,您可以在 Redis 門面上呼叫任何 Redis 命令。Laravel 使用魔術方法將命令傳遞給 Redis 伺服器。如果 Redis 命令需要引數,則應將這些引數傳遞給門面的相應方法。

1use Illuminate\Support\Facades\Redis;
2 
3Redis::set('name', 'Taylor');
4 
5$values = Redis::lrange('names', 5, 10);

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

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

使用多個 Redis 連線

應用程式的 config/database.php 配置檔案允許您定義多個 Redis 連線/伺服器。您可以使用 Redis 門面的 connection 方法獲取特定 Redis 連線的連線例項。

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

要獲取預設 Redis 連線的例項,您可以呼叫不帶任何附加引數的 connection 方法。

1$redis = Redis::connection();

事務

Redis 門面的 transaction 方法為 Redis 原生的 MULTIEXEC 命令提供了一個便捷的封裝。transaction 方法接受一個閉包作為其唯一引數。此閉包將接收一個 Redis 連線例項,並可以向該例項發出任何它想要的命令。在閉包內發出的所有 Redis 命令都將在單個原子事務中執行。

1use Redis;
2use Illuminate\Support\Facades;
3 
4Facades\Redis::transaction(function (Redis $redis) {
5 $redis->incr('user_visits', 1);
6 $redis->incr('total_visits', 1);
7});

定義 Redis 事務時,您不能從 Redis 連線中檢索任何值。請記住,您的事務是作為單個原子操作執行的,並且在整個閉包執行完其命令之前,該操作不會被執行。

Lua 指令碼

eval 方法提供了另一種在單個原子操作中執行多個 Redis 命令的方法。然而,eval 方法的優點是能夠在操作期間與 Redis 鍵值進行互動和檢查。Redis 指令碼使用 Lua 程式語言 編寫。

eval 方法起初可能看起來有點令人畏懼,但我們將透過一個基本示例來破冰。eval 方法需要幾個引數。首先,您應該將 Lua 指令碼(作為字串)傳遞給該方法。其次,您應該傳遞指令碼與之互動的鍵的數量(作為整數)。第三,您應該傳遞這些鍵的名稱。最後,您可以傳遞指令碼內需要訪問的任何其他附加引數。

在此示例中,我們將遞增一個計數器,檢查其新值,如果第一個計數器的值大於 5,則遞增第二個計數器。最後,我們將返回第一個計數器的值。

1$value = Redis::eval(<<<'LUA'
2 local counter = redis.call("incr", KEYS[1])
3 
4 if counter > 5 then
5 redis.call("incr", KEYS[2])
6 end
7 
8 return counter
9LUA, 2, 'first-counter', 'second-counter');

請查閱 Redis 文件 以獲取有關 Redis 指令碼的更多資訊。

命令流水線 (Pipelining)

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

1use Redis;
2use Illuminate\Support\Facades;
3 
4Facades\Redis::pipeline(function (Redis $pipe) {
5 for ($i = 0; $i < 1000; $i++) {
6 $pipe->set("key:$i", $i);
7 }
8});

釋出 / 訂閱

Laravel 為 Redis 的 publishsubscribe 命令提供了便捷的介面。這些 Redis 命令允許您監聽給定“頻道”上的訊息。您可以從另一個應用程式甚至使用其他程式語言向該頻道釋出訊息,從而實現應用程式和程序之間的輕鬆通訊。

首先,讓我們使用 subscribe 方法設定一個頻道監聽器。我們將此方法呼叫放在一個 Artisan 命令 中,因為呼叫 subscribe 方法會啟動一個長期執行的程序。

1<?php
2 
3namespace App\Console\Commands;
4 
5use Illuminate\Console\Command;
6use Illuminate\Support\Facades\Redis;
7 
8class RedisSubscribe extends Command
9{
10 /**
11 * The name and signature of the console command.
12 *
13 * @var string
14 */
15 protected $signature = 'redis:subscribe';
16 
17 /**
18 * The console command description.
19 *
20 * @var string
21 */
22 protected $description = 'Subscribe to a Redis channel';
23 
24 /**
25 * Execute the console command.
26 */
27 public function handle(): void
28 {
29 Redis::subscribe(['test-channel'], function (string $message) {
30 echo $message;
31 });
32 }
33}

現在我們可以使用 publish 方法向該頻道釋出訊息。

1use Illuminate\Support\Facades\Redis;
2 
3Route::get('/publish', function () {
4 // ...
5 
6 Redis::publish('test-channel', json_encode([
7 'name' => 'Adam Wathan'
8 ]));
9});

萬用字元訂閱

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

1Redis::psubscribe(['*'], function (string $message, string $channel) {
2 echo $message;
3});
4 
5Redis::psubscribe(['users.*'], function (string $message, string $channel) {
6 echo $message;
7});