Redis
簡介
Redis 是一個開源的高級鍵值存儲庫。它通常被稱為數據結構服務器,因為鍵可以包含字符串、哈希、列表、集合和有序集合。
在使用 Redis 與 Laravel 之前,我們建議您通過 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'),
],
],
在您的配置文件中定義的每個 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
環境變數的值:
除了預設配置選項外,PhpRedis 還支援以下額外的連線參數:name
、persistent
、persistent_id
、prefix
、read_timeout
、retry_interval
、max_retries
、backoff_algorithm
、backoff_base
、backoff_cap
、timeout
和 context
。您可以將這些選項中的任何一個添加到您的 Redis 伺服器配置中的 config/database.php
配置文件中:
'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_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
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
門面的 connection
方法來獲取到特定 Redis 連線:
$redis = Redis::connection('connection-name');
要獲取預設的 Redis 連線實例,您可以調用 connection
方法而不帶任何額外的引數:
$redis = Redis::connection();
交易
Redis
門面的 transaction
方法提供了一個方便的封裝,用於 Redis 的原生 MULTI
和 EXEC
命令。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 命令。您可以使用 pipeline
方法,而不是為每個命令向 Redis 伺服器發送網路請求。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 的 publish
和 subscribe
命令。這些 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;
});