Redis

簡介

Redis 是一個開放原始碼的高階索引鍵/值存放空間。Redis 常被稱作資料結構伺服器,因為索引鍵中可以保存字串 (String)雜湊 (Hash)清單 (List)集合 (Set)有序集合 (Sorted Set)等。

在 Laravel 中使用 Redis 前,我們建議先使用 PECL 安裝 phpredis PHP 擴充程式。比起安裝其他「User-Land (即,非 PHP 官方套件)」提供的 PHP 套件,要安裝 phpredis 比較複雜一點。不過,對於重度使用 Redis 的專案來說,使用 phpredis 的效能會比較好。若使用 Laravel Sail,則該擴充程式已安裝在專案的 Docker Container 裡了。

若無法安裝 phpredis 擴充程式,則可使用 Composer 安裝 predis/predis 套件。Predis 是完全以 PHP 撰寫的 Redis 用戶端。使用 Predis 就不需要安裝其他額外的擴充程式:

1composer require predis/predis
1composer require predis/predis

設定

我們可以在 config/database.php 設定檔中設定專案的 Redis 設定。在該檔案中,可以看到一個 redis 陣列,其中存放的就是專案要使用的 Redis 伺服器:

1'redis' => [
2 
3 'client' => env('REDIS_CLIENT', 'phpredis'),
4 
5 '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 ],
11 
12 '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 ],
18 
19],
1'redis' => [
2 
3 'client' => env('REDIS_CLIENT', 'phpredis'),
4 
5 '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 ],
11 
12 '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 ],
18 
19],

除非使用單一 URL 來代表 Redis 連線,否則該設定檔中所定義的每個 Redis 伺服器都必須有名稱、主機、連接埠:

1'redis' => [
2 
3 'client' => env('REDIS_CLIENT', 'phpredis'),
4 
5 'default' => [
6 'url' => 'tcp://127.0.0.1:6379?database=0',
7 ],
8 
9 'cache' => [
10 'url' => 'tls://user:[email protected]:6380?database=1',
11 ],
12 
13],
1'redis' => [
2 
3 'client' => env('REDIS_CLIENT', 'phpredis'),
4 
5 'default' => [
6 'url' => 'tcp://127.0.0.1:6379?database=0',
7 ],
8 
9 'cache' => [
10 'url' => 'tls://user:[email protected]:6380?database=1',
11 ],
12 
13],

設定連線的 Scheme

預設情況下,Redis 用戶端會使用 tcp Scheme(配置) 來連線到 Redis 伺服器。不過,我們也可以在 Redis 伺服器設定陣列中指定 scheme 設定選項來使用 TLS / SSL 加密:

1'redis' => [
2 
3 'client' => env('REDIS_CLIENT', 'phpredis'),
4 
5 '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 ],
12 
13],
1'redis' => [
2 
3 'client' => env('REDIS_CLIENT', 'phpredis'),
4 
5 '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 ],
12 
13],

叢集

若專案使用 Redis 伺服器叢集 (Cluster),則應在 Redis 設定中的 clusters 索引鍵下定義這些叢集。預設情況下沒有該設定索引鍵,因此我們需要手動在 config/database.php 設定檔中建立該索引鍵:

1'redis' => [
2 
3 'client' => env('REDIS_CLIENT', 'phpredis'),
4 
5 '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 ],
15 
16],
1'redis' => [
2 
3 'client' => env('REDIS_CLIENT', 'phpredis'),
4 
5 '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 ],
15 
16],

預設情況下,叢集會在各個節點間做用戶端分區 (Sharding),讓我們能集區化 (Pool) 節點,並儘量取得更多可用的 RAM。不過,使用用戶端分區將無法處理 Failover(容錯移轉)。因此,這種做法主要只適合用在一些存放時間短的、快取的資料。這些資料應該要能從其他主要的資料存放空間內取得。

若想使用 Redis 原生的叢集功能,而不使用用戶端分區,則我們可以在 config/database.php 設定檔中將 options.cluster 設定值設為 redis

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

Predis

若專案通過 Predis 套件來使用 Redis,則請確定 REDIS_CLIENT 環境變數是否有設為 predis

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

除了預設的 hostportdatabasepassword 等伺服器設定選項外,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 的 config/app.php 設定檔中包含了一個 aliases 陣列,該陣列中定義了所有 Laravel 會註冊的類別別名。預設情況下,該檔案中並未包含 Redis 別名,因為使用 Redis 會與 phpredis 擴充程式的 Redis 類別名稱衝突。在使用 Predis 用戶端時,若想新增使用 Redis 別名,則可將該別名加入到專案 config/app.php 設定檔中的 aliases 陣列中:

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

預設情況下,Laravel 會使用 phpredis 擴充程式來與 Redis 溝通。Laravel 要用來與 Redis 溝通的用戶端是由 redis.client 設定選項來判斷的,一般來說這個設定選項的值就是 REDIS_CLIENT 環境變數值:

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

出了預設的 schemehostportdatabasepassword 等伺服器設定選項外,phpredis 還支援下列其他的連線參數:namepersistentpersistent_idprefixread_timeoutretry_intervaltimeoutcontext 等。我們可以在 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_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 的序列化與壓縮

phpredis 擴充程式可以設定各種各樣的序列化與壓縮演算法。可以在 Redis 設定的 options 陣列內設定這些演算法:

1'redis' => [
2 
3 'client' => env('REDIS_CLIENT', 'phpredis'),
4 
5 'options' => [
6 'serializer' => Redis::SERIALIZER_MSGPACK,
7 'compression' => Redis::COMPRESSION_LZ4,
8 ],
9 
10 // Rest of Redis configuration...
11],
1'redis' => [
2 
3 'client' => env('REDIS_CLIENT', 'phpredis'),
4 
5 'options' => [
6 'serializer' => Redis::SERIALIZER_MSGPACK,
7 'compression' => Redis::COMPRESSION_LZ4,
8 ],
9 
10 // Rest of Redis configuration...
11],

目前所支援的序列化演算法有 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 Facade 支援動態方法,著表示,我們可以在該 Facade 上呼叫任何的 Redis 指令,而該指令會被直接傳到 Redis 上。在這個範例中,我們會在 Redis Facade 上呼叫 get 方法,以呼叫 Redis 的 GET 指令:

1<?php
2 
3namespace App\Http\Controllers;
4 
5use App\Http\Controllers\Controller;
6use Illuminate\Support\Facades\Redis;
7use Illuminate\View\View;
8 
9class UserController extends Controller
10{
11 /**
12 * Show the profile for the given user.
13 */
14 public function show(string $id): View
15 {
16 return view('user.profile', [
17 'user' => Redis::get('user:profile:'.$id)
18 ]);
19 }
20}
1<?php
2 
3namespace App\Http\Controllers;
4 
5use App\Http\Controllers\Controller;
6use Illuminate\Support\Facades\Redis;
7use Illuminate\View\View;
8 
9class UserController extends Controller
10{
11 /**
12 * Show the profile for the given user.
13 */
14 public function show(string $id): View
15 {
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;
2 
3Redis::set('name', 'Taylor');
4 
5$values = Redis::lrange('names', 5, 10);
1use Illuminate\Support\Facades\Redis;
2 
3Redis::set('name', 'Taylor');
4 
5$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 原生 MULTIEXEC 指令的方便包裝。transaction 方法只有一個引數,該引數為一個閉包。傳入的閉包會收到一個 Redis 連線實體,並可在該實體上下任何指令。在該閉包中所下的所有指令,都會被放在單一、不可部分完成 (Atomic) 的 Transaction 內執行:

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

定義 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])
3 
4 if counter > 5 then
5 redis.call("incr", KEYS[2])
6 end
7 
8 return counter
9LUA, 2, 'first-counter', 'second-counter');
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');
exclamation

有關更多在 Redis 上撰寫 Script 的資訊,請參考 Redis 的說明文件

指令管道

有時候,我們會需要執行多個 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});
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});

Pub / Sub

Laravel 中為 Redis 的 publishsubscribe 指令提供了一個方便的介面。使用這兩個 Redis 指令,我們就能在給定的「頻道 (Channel)」上監聽訊息。接著,我們可以在另一個專案內、甚至使用另一個程式語言來 Publish(發佈) 訊息。這樣一來我們就能輕鬆地在不同專案或處理程序間進行溝通。

首先,我們先使用 subscribe 方法來建立一個頻道的 Listener(監聽程式)。我們將這個指令放在一個 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}
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});
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});

使用萬用字元來 Subscribe

使用 psubscribe 方法,我們就能以萬用字元來 Subscribe 頻道。若要從所有頻道中取得所有的訊息,就適合使用這個方法。頻道名稱會以第二個引數傳給所提供的閉包:

1Redis::psubscribe(['*'], function (string $message, string $channel) {
2 echo $message;
3});
4 
5Redis::psubscribe(['users.*'], function (string $message, string $channel) {
6 echo $message;
7});
1Redis::psubscribe(['*'], function (string $message, string $channel) {
2 echo $message;
3});
4 
5Redis::psubscribe(['users.*'], function (string $message, string $channel) {
6 echo $message;
7});
翻譯進度
100% 已翻譯
更新時間:
2023年2月11日 上午10:28:00 [世界標準時間]
翻譯人員:
  • cornch
幫我們翻譯此頁

留言

尚無留言

“Laravel” is a Trademark of Taylor Otwell.
The source documentation is released under MIT license. See laravel/docs on GitHub for details.
The translated documentations are released under MIT license. See cornch/laravel-docs-l10n on GitHub for details.