【Laravel】Redisのレプリケーション

## はじめに
Laravelで作ったアプリケーションについて、Webサーバ1台構成から、
負荷分散のためにロードバランサーを導入し、Webサーバ2台構成へ変更しました。

元々WebサーバのキャッシュクライアントでRedisを導入していたのですが、
Webサーバの増設に伴い、追加するWebサーバにもRedisを導入する必要が生じます。

また、Redisに格納されているキャッシュデータはWebサーバ毎に差分が出てはいけないため、
それぞれのWebサーバのRedis同士で同期が取れている必要があります。

今回は、元々あるWebサーバのRedisを新しく追加したWebサーバのRedisへレプリケーションする方法について
紹介します。

便宜上、元々のWebサーバをWeb1号機、追加するWebサーバをWeb2号機としています。
また、RedisのレプリケーションについてはWeb1号機をMaster、Web2号機をSlaveとするMaster-Slave構成にしています。

システム構成の変更は下記の図のようになります。

![20210916_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2172312/4bd3318c-4507-fe5b-bb29-75695e8f9ce6.png)


## 環境
- Laravel 6.5
- Redis 6.2.3

## Master・Slaveの疎通確認
Redisの初期設定のままだと、接続設定はそれぞれ127.0.0.1(localhost)のみです。

```
(Master側で)
$ redis-cli -h 192.168.1.y
Could not connect to Redis at 192.168.1.y:6379: Connection refused
not connected>
```

### SlaveからMasterへの接続設定を行う
Master・Slaveともに、/etc/redis/redis.confの接続設定部分を修正します。

```
bind 127.0.0.1 → bind 0.0.0.0
```
redisの再起動をそれぞれ実行します。

```
$ systemctl restart redis.service
```

### 疎通確認
Master・Slaveそれぞれで疎通確認を行います。

```
(Master側で)
$ redis-cli -h 192.168.1.y
192.168.1.y:6379>

(Slave側で)
$ redis-cli -h 192.168.1.x
192.168.1.x:6379>
```

サーバ間の疎通が通っていることを確認します。


## レプリケーション設定
Slaveのredis.confを修正します。

```
slaveof <masterのIP> <masterのredisのport番号>
```
と、記載することで、MasterとSlaveの対応関係を設定できます。

```
slaveof 192.168.1.y 6379
```

redisの再起動をそれぞれ行います。

```
$ systemctl restart redis.service
```


## レプリケーションの確認
### 設定の確認
SlaveでMasterがレプリケーションされているか確認します。

```
(Slave側で)
$ redis-cli
127.0.0.1:6379> INFO
// 割愛
# Replication
role:slave
master_host:192.168.1.x
master_port:6379
```

Replication欄のmaster_hostに、設定したMasterのIPアドレスが記載されていれば、レプリケーション設定ができています。

Master側でも設定を確認します。

```
$ redis-cli
127.0.0.1:6379> INFO
# Replication
role:master
connected_slaves:1
slave0:ip=192.168.1.y,port=6379,state=online,offset=110155,lag=1
```

Replication欄のslave0に、設定したSlaveのIPアドレスが記載されていればOKです。
今回は行っていないですが、Masterに対して複数のSlaveを持たせることも可能です。

### データ同期の確認
Master側で、setしたデータが、Slave側にレプリケーションされているかを確認します。

```
(Master側で)
127.0.0.1:6379> set key1 value1
OK
127.0.0.1:6379> keys *
1) "key1"

(上記実行後、Slave側で)
127.0.0.1:6379> keys *
1) "key1"
```

データの削除も同様に、MasterがSlaveにレプリケーションされます。

```
(Master側で)
127.0.0.1:6379> keys *
1) "key1"
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> keys *
(empty list or set)

(上記実行後、Slave側で)
127.0.0.1:6379> keys *
(empty list or set)
```

なお、Slaveは参照のみのため、Slaveに対してデータの追加や削除を行おうとすると、エラーが出ます。

```
(上記実行後、Slave側で)
127.0.0.1:6379> set key1 value1
(error) READONLY You can't write against a read only slave.
```

キャッシュの参照は、それぞれのRedis(つまりlocalhost)を、追加・更新・削除については、Masterを参照するよう、プログラム側で制御する必要があります。


## Laravel Redisの設定内容の変更
アプリケーション内のキャッシュの設定については、```config/cache.php```に記載されています。

storesキーの中に、```redis```の設定内容が記載されています。

```
'stores' => [

    // 割愛

    'redis' => [
        'driver' => 'redis',
        'connection' => 'cache',
    ],

    // 割愛

],
```

デフォルトではredisの記述は1つ(localhost)だけしかないですが、今回は、読み込み/書き込みで2つ分の設定内容が必要なので、設定内容を下記のように変更します。

```
'stores' => [

    // 割愛

    'redis_read' => [
        'driver' => 'redis',
        'connection' => 'read_cache',
    ],

    'redis_write' => [
        'driver' => 'redis',
        'connection' => 'write_cache',
    ],

    // 割愛

],
```
***connection***に指定している項目は、キャッシュクライアントの接続先を、```config/database.php```内の```driver```の項目（つまり```redis```）の、どの値を用いるか、を記載したものになります。
デフォルトの```config/database.php```は下記のように記載されています。

```config/database/php
'redis' => [

    'client' => env('REDIS_CLIENT', 'phpredis'),

    'default' => [
        'host' => env('REDIS_HOST', '127.0.0.1'),
        'password' => env('REDIS_PASSWORD', null),
        'port' => env('REDIS_PORT', 6379),
        'database' => env('REDIS_DB', 0),
    ],

    'cache' => [
        'host' => env('REDIS_HOST', '127.0.0.1'),
        'password' => env('REDIS_PASSWORD', null),
        'port' => env('REDIS_PORT', 6379),
        'database' => env('REDIS_CACHE_DB', 1),
    ],

],
```

読み込み/書き込みそれぞれの接続情報を分けて記述する必要があるので、下記のように修正します。

```config/database/php
'redis' => [

    'client' => env('REDIS_CLIENT', 'phpredis'),

    'default' => [
        'host' => env('REDIS_HOST', '127.0.0.1'),
        'password' => env('REDIS_PASSWORD', null),
        'port' => env('REDIS_PORT', 6379),
        'database' => env('REDIS_DB', 0),
    ],

    'read_cache' => [
        'url' => env('REDIS_URL'),
        'host' => env('REDIS_HOST', '127.0.0.1'),
        'host' => env('REDIS_HOST_READ', '127.0.0.1'),
        'password' => env('REDIS_PASSWORD', null),
        'port' => env('REDIS_PORT', 6379),
        'database' => env('REDIS_CACHE_DB', 1),
    ],

    'write_cache' => [
        'url' => env('REDIS_URL'),
        'host' => env('REDIS_HOST_WRITE', '127.0.0.1'),
        'password' => env('REDIS_PASSWORD', null),
        'port' => env('REDIS_PORT', 6379),
        'database' => env('REDIS_CACHE_DB', 1),
    ],

],
```

また、```read_cache```と```write_cache```のhost(IPアドレス)については、```.env```ファイルに記載をします。

```
REDIS_HOST_READ=127.0.0.1
REDIS_HOST_WRITE=192.168.1.x
```
読み込みのRedisのIPアドレスは127.0.0.1(localhost)を指定します。つまり、参照するRedisは自身のWebサーバ内のRedisを参照します。  
書き込みのRedisのIPアドレスはMasterのIPアドレスを指定します。こうすることで、Web1号機・Web2号機ともにMasterのRedisに対してデータの書き込みを行うようにすることができます。


## 最後に
Webサーバ2台構成でのRedisのMaster-Slave構成について、そこまで複雑な設定は必要なく、実装することができました。 
レプリケーションだけではなく、クラスタリングなどもできるようなので、機会があれば勉強したいと思います。