Redis
簡介
Redis is an open source, advanced key-value store. It is often referred to as a data structure server since keys can contain strings, hashes, lists, sets, and sorted sets.
Before using Redis with Laravel, we encourage you to install and use the PhpRedis PHP extension via PECL. The extension is more complex to install compared to "user-land" PHP packages but may yield better performance for applications that make heavy use of Redis. If you are using Laravel Sail, this extension is already installed in your application's Docker container.
If you are unable to install the PhpRedis extension, you may install the predis/predis
package via Composer. Predis is a Redis client written entirely in PHP and does not require any additional extensions:
1composer require predis/predis
1composer require predis/predis
設定
我們可以在 config/database.php
設定檔中設定專案的 Redis 設定。在該檔案中,可以看到一個 redis
陣列,其中存放的就是專案要使用的 Redis 伺服器:
1'redis' => [23 'client' => env('REDIS_CLIENT', 'phpredis'),45 'default' => [6 'host' => env('REDIS_HOST', '127.0.0.1'),7 'password' => env('REDIS_PASSWORD'),8 'port' => env('REDIS_PORT', 6379),9 'database' => env('REDIS_DB', 0),10 ],1112 'cache' => [13 'host' => env('REDIS_HOST', '127.0.0.1'),14 'password' => env('REDIS_PASSWORD'),15 'port' => env('REDIS_PORT', 6379),16 'database' => env('REDIS_CACHE_DB', 1),17 ],1819],
1'redis' => [23 'client' => env('REDIS_CLIENT', 'phpredis'),45 'default' => [6 'host' => env('REDIS_HOST', '127.0.0.1'),7 'password' => env('REDIS_PASSWORD'),8 'port' => env('REDIS_PORT', 6379),9 'database' => env('REDIS_DB', 0),10 ],1112 'cache' => [13 'host' => env('REDIS_HOST', '127.0.0.1'),14 'password' => env('REDIS_PASSWORD'),15 'port' => env('REDIS_PORT', 6379),16 'database' => env('REDIS_CACHE_DB', 1),17 ],1819],
除非使用單一 URL 來代表 Redis 連線,否則該設定檔中所定義的每個 Redis 伺服器都必須有名稱、主機、連接埠:
1'redis' => [23 'client' => env('REDIS_CLIENT', 'phpredis'),45 'default' => [6 'url' => 'tcp://127.0.0.1:6379?database=0',7 ],89 'cache' => [11 ],1213],
1'redis' => [23 'client' => env('REDIS_CLIENT', 'phpredis'),45 'default' => [6 'url' => 'tcp://127.0.0.1:6379?database=0',7 ],89 'cache' => [11 ],1213],
Configuring the Connection Scheme
預設情況下,Redis 用戶端會使用 tcp
Scheme 來連線到 Redis 伺服器。不過,我們也可以在 Redis 伺服器設定陣列中指定 scheme
設定選項來使用 TLS / SSL 加密:
1'redis' => [23 'client' => env('REDIS_CLIENT', 'phpredis'),45 'default' => [6 'scheme' => 'tls',7 'host' => env('REDIS_HOST', '127.0.0.1'),8 'password' => env('REDIS_PASSWORD'),9 'port' => env('REDIS_PORT', 6379),10 'database' => env('REDIS_DB', 0),11 ],1213],
1'redis' => [23 'client' => env('REDIS_CLIENT', 'phpredis'),45 'default' => [6 'scheme' => 'tls',7 'host' => env('REDIS_HOST', '127.0.0.1'),8 'password' => env('REDIS_PASSWORD'),9 'port' => env('REDIS_PORT', 6379),10 'database' => env('REDIS_DB', 0),11 ],1213],
叢集
若專案使用 Redis 伺服器叢集 (Cluster),則應在 Redis 設定中的 clusters
索引鍵下定義這些叢集。預設情況下沒有該設定索引鍵,因此我們需要手動在 config/database.php
設定檔中建立該索引鍵:
1'redis' => [23 'client' => env('REDIS_CLIENT', 'phpredis'),45 'clusters' => [6 'default' => [7 [8 'host' => env('REDIS_HOST', 'localhost'),9 'password' => env('REDIS_PASSWORD'),10 'port' => env('REDIS_PORT', 6379),11 'database' => 0,12 ],13 ],14 ],1516],
1'redis' => [23 'client' => env('REDIS_CLIENT', 'phpredis'),45 'clusters' => [6 'default' => [7 [8 'host' => env('REDIS_HOST', 'localhost'),9 'password' => env('REDIS_PASSWORD'),10 'port' => env('REDIS_PORT', 6379),11 'database' => 0,12 ],13 ],14 ],1516],
預設情況下,叢集會在各個節點間做用戶端分區 (Sharding),讓我們能集區化 (Pool) 節點,並儘量取得更多可用的 RAM。不過,使用用戶端分區將無法處理 Failover。因此,這種做法主要只適合用在一些存放時間短的、快取的資料。這些資料應該要能從其他主要的資料存放空間內取得。
若想使用 Redis 原生的叢集功能,而不使用用戶端分區,則我們可以在 config/database.php
設定檔中將 options.cluster
設定值設為 redis
:
1'redis' => [23 'client' => env('REDIS_CLIENT', 'phpredis'),45 'options' => [6 'cluster' => env('REDIS_CLUSTER', 'redis'),7 ],89 'clusters' => [10 // ...11 ],1213],
1'redis' => [23 'client' => env('REDIS_CLIENT', 'phpredis'),45 'options' => [6 'cluster' => env('REDIS_CLUSTER', 'redis'),7 ],89 'clusters' => [10 // ...11 ],1213],
Predis
若專案通過 Predis 套件來使用 Redis,則請確定 REDIS_CLIENT
環境變數是否有設為 predis
:
1'redis' => [23 'client' => env('REDIS_CLIENT', 'predis'),45 // ...6],
1'redis' => [23 'client' => env('REDIS_CLIENT', 'predis'),45 // ...6],
除了預設的 host
、port
、database
、password
等伺服器設定選項外,Predis 還支援其他的連線參數,這些連線參數可以在每個 Redis 伺服器上定義。若要使用這些其他的設定選項,請將這些選項駕到 config/database.php
設定檔中的 Redis 伺服器設定內:
1'default' => [2 'host' => env('REDIS_HOST', 'localhost'),3 'password' => env('REDIS_PASSWORD'),4 'port' => env('REDIS_PORT', 6379),5 'database' => 0,6 'read_write_timeout' => 60,7],
1'default' => [2 'host' => env('REDIS_HOST', 'localhost'),3 'password' => env('REDIS_PASSWORD'),4 'port' => env('REDIS_PORT', 6379),5 'database' => 0,6 'read_write_timeout' => 60,7],
Redis Facade 的別名
Laravel's config/app.php
configuration file contains an aliases
array which defines all of the class aliases that will be registered by the framework. By default, no Redis
alias is included because it would conflict with the Redis
class name provided by the PhpRedis extension. If you are using the Predis client and would like to add a Redis
alias, you may add it to the aliases
array in your application's config/app.php
configuration file:
1'aliases' => Facade::defaultAliases()->merge([2 'Redis' => Illuminate\Support\Facades\Redis::class,3])->toArray(),
1'aliases' => Facade::defaultAliases()->merge([2 'Redis' => Illuminate\Support\Facades\Redis::class,3])->toArray(),
PhpRedis
By default, Laravel will use the PhpRedis extension to communicate with Redis. The client that Laravel will use to communicate with Redis is dictated by the value of the redis.client
configuration option, which typically reflects the value of the REDIS_CLIENT
environment variable:
1'redis' => [23 'client' => env('REDIS_CLIENT', 'phpredis'),45 // Rest of Redis configuration...6],
1'redis' => [23 'client' => env('REDIS_CLIENT', 'phpredis'),45 // Rest of Redis configuration...6],
In addition to the default scheme
, host
, port
, database
, and password
server configuration options, PhpRedis supports the following additional connection parameters: name
, persistent
, persistent_id
, prefix
, read_timeout
, retry_interval
, timeout
, and context
. You may add any of these options to your Redis server configuration in the config/database.php
configuration file:
1'default' => [2 'host' => env('REDIS_HOST', 'localhost'),3 'password' => env('REDIS_PASSWORD'),4 'port' => env('REDIS_PORT', 6379),5 'database' => 0,6 'read_timeout' => 60,7 'context' => [8 // 'auth' => ['username', 'secret'],9 // 'stream' => ['verify_peer' => false],10 ],11],
1'default' => [2 'host' => env('REDIS_HOST', 'localhost'),3 'password' => env('REDIS_PASSWORD'),4 'port' => env('REDIS_PORT', 6379),5 'database' => 0,6 'read_timeout' => 60,7 'context' => [8 // 'auth' => ['username', 'secret'],9 // 'stream' => ['verify_peer' => false],10 ],11],
PhpRedis Serialization and Compression
The PhpRedis extension may also be configured to use a variety of serializers and compression algorithms. These algorithms can be configured via the options
array of your Redis configuration:
1'redis' => [23 'client' => env('REDIS_CLIENT', 'phpredis'),45 'options' => [6 'serializer' => Redis::SERIALIZER_MSGPACK,7 'compression' => Redis::COMPRESSION_LZ4,8 ],910 // Rest of Redis configuration...11],
1'redis' => [23 'client' => env('REDIS_CLIENT', 'phpredis'),45 'options' => [6 'serializer' => Redis::SERIALIZER_MSGPACK,7 'compression' => Redis::COMPRESSION_LZ4,8 ],910 // Rest of Redis configuration...11],
Currently supported serializers include: Redis::SERIALIZER_NONE
(default), Redis::SERIALIZER_PHP
, Redis::SERIALIZER_JSON
, Redis::SERIALIZER_IGBINARY
, and 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
指令:
1<?php23namespace App\Http\Controllers;45use App\Http\Controllers\Controller;6use Illuminate\Support\Facades\Redis;7use Illuminate\View\View;89class UserController extends Controller10{11 /**12 * Show the profile for the given user.13 */14 public function show(string $id): View15 {16 return view('user.profile', [17 'user' => Redis::get('user:profile:'.$id)18 ]);19 }20}
1<?php23namespace App\Http\Controllers;45use App\Http\Controllers\Controller;6use Illuminate\Support\Facades\Redis;7use Illuminate\View\View;89class UserController extends Controller10{11 /**12 * Show the profile for the given user.13 */14 public function show(string $id): View15 {16 return view('user.profile', [17 'user' => Redis::get('user:profile:'.$id)18 ]);19 }20}
剛才也提到過,我們可以在 Redis
Facade 上呼叫任何的 Redis 指令。Laravel 會使用 Magic Method 來將這些指令傳給 Redis 伺服器。若是有要求引數的 Redis 指令,則我們可以將引數傳給 Facade 上對應的方法:
1use Illuminate\Support\Facades\Redis;23Redis::set('name', 'Taylor');45$values = Redis::lrange('names', 5, 10);
1use Illuminate\Support\Facades\Redis;23Redis::set('name', 'Taylor');45$values = Redis::lrange('names', 5, 10);
或者,我們也可以使用 Redis
Facade 的 command
方法來將指令傳給 Redis 伺服器。command
方法的第一個引數是指令名稱,而第二個引數則是一個陣列:
1$values = Redis::command('lrange', ['name', 5, 10]);
1$values = Redis::command('lrange', ['name', 5, 10]);
使用多個 Redis 連線
在專案的 config/database.php
設定檔中,我們可以定義多個 Redis 連線/伺服器。我們可以使用 Redis
Facade 上的 connection
方法來取得一個特定的 Redis 連線:
1$redis = Redis::connection('connection-name');
1$redis = Redis::connection('connection-name');
若要取得預設的 Redis 連線,可直接呼叫 connection
方法而不帶任何引數:
1$redis = Redis::connection();
1$redis = Redis::connection();
Transaction
Redis
Facade 的 transaction
方法提供了一個 Redis 原生 MULTI
與 EXEC
指令的方便包裝。transaction
方法只有一個引數,該引數為一個閉包。傳入的閉包會收到一個 Redis 連線實體,並可在該實體上下任何指令。在該閉包中所下的所有指令,都會被放在單一、不可部分完成 (Atomic) 的 Transaction 內執行:
1use Redis;2use Illuminate\Support\Facades;34Facades\Redis::transaction(function (Redis $redis) {5 $redis->incr('user_visits', 1);6 $redis->incr('total_visits', 1);7});
1use Redis;2use Illuminate\Support\Facades;34Facades\Redis::transaction(function (Redis $redis) {5 $redis->incr('user_visits', 1);6 $redis->incr('total_visits', 1);7});
定義 Redis Transaction 時,無法從 Redis 連線中取值。請記得,Transaction 是以單一、不可部分完成的動作來執行的,因此這些動作會在整個閉包內的指令都執行完畢後才被執行。
Lua Script
eval
方法提供了另一種以單一、不可部分完成動作執行多個 Redis 指令的方法。不過,使用 eval
方法還有個好處,就是能在動作的期間處理與偵測 Redis 的索引鍵值。Redis Script 使用 Lua 程式語言撰寫。
雖然,一開始,eval
方法可能有點可怕。不過我們會先來看看一個簡單的例子。eval
方法接受多個引數。首先,我們需要先 (以字串形式) 傳入 Lua Script 給該方法。然後。我們需要將該 Script 要處理的索引鍵數量 (以整數形式) 傳入。再來,我們需要傳入這些索引鍵的名稱。最後,我們可以傳入在該 Script 中需要存取的其他引數。
在這個範例中,我們會遞增一個計數器,並取得該計數器的值,判斷該值是否大於 5。如果大於 5,就再遞增另一個計數器。最後,回傳第一個計數器的值:
1$value = Redis::eval(<<<'LUA'2 local counter = redis.call("incr", KEYS[1])34 if counter > 5 then5 redis.call("incr", KEYS[2])6 end78 return counter9LUA, 2, 'first-counter', 'second-counter');
1$value = Redis::eval(<<<'LUA'2 local counter = redis.call("incr", KEYS[1])34 if counter > 5 then5 redis.call("incr", KEYS[2])6 end78 return counter9LUA, 2, 'first-counter', 'second-counter');
有關更多在 Redis 上撰寫 Script 的資訊,請參考 Redis 的說明文件。
指令管道
有時候,我們會需要執行多個 Redis 指令。除了個別以網路連線將每個指令傳給 Redis,我們還可以使用 pipeline
方法。pipeline
方法只有一個引數:一個接收 Redis 實體的閉包。我們可以使用這個 Redis 實體來下指令,下的所有指令會被一次性地傳送給 Redis 伺服器,以減少網路使用。指令會依照所下的順序執行:
1use Redis;2use Illuminate\Support\Facades;34Facades\Redis::pipeline(function (Redis $pipe) {5 for ($i = 0; $i < 1000; $i++) {6 $pipe->set("key:$i", $i);7 }8});
1use Redis;2use Illuminate\Support\Facades;34Facades\Redis::pipeline(function (Redis $pipe) {5 for ($i = 0; $i < 1000; $i++) {6 $pipe->set("key:$i", $i);7 }8});
Pub / Sub
Laravel 中為 Redis 的 publish
與 subscribe
指令提供了一個方便的介面。使用這兩個 Redis 指令,我們就能在給定的「頻道 (Channel)」上監聽訊息。接著,我們可以在另一個專案內、甚至使用另一個程式語言來 Publish 訊息。這樣一來我們就能輕鬆地在不同專案或處理程序間進行溝通。
首先,我們先使用 subscribe
方法來建立一個頻道的 Listener。我們將這個指令放在一個 Artisan 指令內呼叫。因為,呼叫 `subscribe` 方法就代表要開啟一個執行時間較長的處理程序:
1<?php23namespace App\Console\Commands;45use Illuminate\Console\Command;6use Illuminate\Support\Facades\Redis;78class RedisSubscribe extends Command9{10 /**11 * The name and signature of the console command.12 *13 * @var string14 */15 protected $signature = 'redis:subscribe';1617 /**18 * The console command description.19 *20 * @var string21 */22 protected $description = 'Subscribe to a Redis channel';2324 /**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}
1<?php23namespace App\Console\Commands;45use Illuminate\Console\Command;6use Illuminate\Support\Facades\Redis;78class RedisSubscribe extends Command9{10 /**11 * The name and signature of the console command.12 *13 * @var string14 */15 protected $signature = 'redis:subscribe';1617 /**18 * The console command description.19 *20 * @var string21 */22 protected $description = 'Subscribe to a Redis channel';2324 /**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;23Route::get('/publish', function () {4 // ...56 Redis::publish('test-channel', json_encode([7 'name' => 'Adam Wathan'8 ]));9});
1use Illuminate\Support\Facades\Redis;23Route::get('/publish', function () {4 // ...56 Redis::publish('test-channel', json_encode([7 'name' => 'Adam Wathan'8 ]));9});
使用萬用字元來 Subscribe
使用 psubscribe
方法,我們就能以萬用字元來 Subscribe 頻道。若要從所有頻道中取得所有的訊息,就適合使用這個方法。頻道名稱會以第二個引數傳給所提供的閉包:
1Redis::psubscribe(['*'], function (string $message, string $channel) {2 echo $message;3});45Redis::psubscribe(['users.*'], function (string $message, string $channel) {6 echo $message;7});
1Redis::psubscribe(['*'], function (string $message, string $channel) {2 echo $message;3});45Redis::psubscribe(['users.*'], function (string $message, string $channel) {6 echo $message;7});