跳到內容

Redis

簡介

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

在 Laravel 中使用 Redis 之前,我們建議您透過 PECL 安裝並使用 PhpRedis PHP 擴充功能。與「使用者層」PHP 套件相比,此擴充功能安裝起來更複雜,但對於大量使用 Redis 的應用程式來說,可能會產生更好的效能。如果您使用 Laravel Sail,則此擴充功能已安裝在您應用程式的 Docker 容器中。

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

composer require predis/predis:^2.0

設定

您可以透過 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'),
],
 
],

除非您定義單一 URL 來代表 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' => 'tcp://127.0.0.1:6379?database=0',
],
 
'cache' => [
'url' => 'tls://user:[email protected]: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 也支援用戶端分片。但是,用戶端分片無法處理容錯移轉;因此,它主要適用於可從另一個主要資料儲存取得的暫時快取資料。

如果您想要使用用戶端分片而不是原生 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_intervaltimeoutcontext。您可以將這些選項的任何一個新增至 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],
],
],

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 Facades 上的各種方法來與 Redis 互動。Redis Facade 支援動態方法,這表示您可以在 Facade 上呼叫任何 Redis 命令,而該命令將直接傳遞到 Redis。在此範例中,我們將透過在 Redis Facade 上呼叫 get 方法來呼叫 Redis GET 命令。

<?php
 
namespace App\Http\Controllers;
 
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Redis;
use Illuminate\View\View;
 
class UserController extends Controller
{
/**
* Show the profile for the given user.
*/
public function show(string $id): View
{
return view('user.profile', [
'user' => Redis::get('user:profile:'.$id)
]);
}
}

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

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

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

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

使用多個 Redis 連線

您應用程式的 config/database.php 設定檔可讓您定義多個 Redis 連線 / 伺服器。您可以使用 Redis Facade 的 connection 方法取得與特定 Redis 連線的連線。

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

若要取得預設 Redis 連線的執行個體,您可以呼叫 connection 方法,而無需任何額外引數。

$redis = Redis::connection();

交易

Redis Facade 的 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);
});
exclamation

當定義 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');
exclamation

請查閱 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
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'redis:subscribe';
 
/**
* The console command description.
*
* @var string
*/
protected $description = 'Subscribe to a Redis channel';
 
/**
* Execute the console command.
*/
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;
});