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' => [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 還支援以下額外的連線引數:name、persistent、persistent_id、prefix、read_timeout、retry_interval、max_retries、backoff_algorithm、backoff_base、backoff_cap、timeout 和 context。您可以將這些選項中的任何一個新增到 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_interval、max_retries、backoff_algorithm、backoff_base 和 backoff_cap 選項可用於配置 PhpRedis 客戶端如何嘗試重新連線到 Redis 伺服器。支援的退避演算法包括:default、decorrelated_jitter、equal_jitter、exponential、uniform 和 constant。
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 選項並配合以下策略之一進行配置:NoBackoff、EqualBackoff 或 ExponentialBackoff。
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 jitter12 ),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.sock2REDIS_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_PHP、Redis::SERIALIZER_JSON、Redis::SERIALIZER_IGBINARY 和 Redis::SERIALIZER_MSGPACK。
支援的壓縮演算法包括:Redis::COMPRESSION_NONE (預設)、Redis::COMPRESSION_LZF、Redis::COMPRESSION_ZSTD 和 Redis::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): View14 {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 原生的 MULTI 和 EXEC 命令提供了一個便捷的封裝。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 then5 redis.call("incr", KEYS[2])6 end7 8 return counter9LUA, 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 的 publish 和 subscribe 命令提供了便捷的介面。這些 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 string14 */15 protected $signature = 'redis:subscribe';16 17 /**18 * The console command description.19 *20 * @var string21 */22 protected $description = 'Subscribe to a Redis channel';23 24 /**25 * Execute the console command.26 */27 public function handle(): void28 {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});