redis-sentinelをperlから使う

# この記事の内容
* redisの準備
* redis-sentinelの準備
* perlから利用する方法

を記載します。

sentinelをアプリからどう使うか、がキモです。
sentinelの詳細については、[Redis Sentinel Documentation](https://redis.io/topics/sentinel)を御覧ください。


# はじめに

今回は、1台のサーバに、

* redis 2セット(master / slave)
* sentinel 3セット

を準備します。

OSはCentOS 7系を想定します。

商用では、最低限

* master（redis + sentinel）
* slave（redis + sentinel）
* sentinel（sentinel）

サーバの3台が必要です。
sentinelプロセスは、各サーバで1つずつ動作させます。

# redisのセットアップ

パッケージをインストールします。

```bash
$ sudo yum install redis
```

/etc/redis.confと、/etc/redis-sentinel.confが生成されます。

今回は実験なので、ホームディレクトリ配下にworking directoryを作成します。

```bash
$ cd 
$ mkdir redis-work
$ cd redis-work
```

master/slave用のコンフィグを2つ作ります。
最初は16379をmaster, 16380をslaveにします。
（master / slaveはsentinelにより動的に変更されます）

```bash
$ vim 16379.conf
```

```conf:16379.conf
bind 127.0.0.1
port 16379
daemonize yes

pidfile /tmp/redis_16379.pid

slave-read-only yes
```

```bash
$ vim 16380.conf
```

```conf:16380.conf
bind 127.0.0.1
port 16380
daemonize yes

pidfile /tmp/redis_16380.pid

slaveof 127.0.0.1 16379
slave-read-only yes
```

この設定ファイルを用いて、起動します。

```bash
$ redis-server 16379.conf
$ redis-server 16380.conf
```

正常にmaster / slaveになっているか確認しましょう。

```bash
$ redis-cli -p 16379 info replication
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=16380,state=online,offset=1,lag=1
master_repl_offset:1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:2
repl_backlog_histlen:0

$ redis-cli -p 16380 info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:16379
master_link_status:up
master_last_io_seconds_ago:8
master_sync_in_progress:0
slave_repl_offset:15
slave_priority:100
slave_read_only:1
connected_slaves:0
master_repl_offset:0
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
```

正常に動いていますね。


# redis sentinelのセットアップ

yumでredisをインストールすると、**redis-sentinel**というコマンドも
インストールされます。
実態はただの**redis-serverへのsymlink**です。

つまり動くバイナリは同じものです。

が

redisは起動時のargv[0]をみて、動作モードを変えるため、sentinelを起動するときには、

* redis-sentinelコマンドを使う
* redis-server --sentinel オプションを使う

のどちらかで起動する必要があります。

以上蛇足でした。

設定に入ります。

同じworking directoryで、sentinel用のコンフィグを3つ作ります。
（違うのはportとpidだけです）

```bash
$ cd ~/redis-work/
$ vim sentinel1.conf
```

```conf:sentinel1.conf
bind 127.0.0.1
port 26379
daemonize yes

pidfile /tmp/redis_sentinel1.pid


# sentinel monitor <master-name>  <ip>       <redis-port>  <quorum>
sentinel monitor   servers1       127.0.0.1  16379         2

sentinel down-after-milliseconds servers1 2500
sentinel failover-timeout servers1 10000
```

```bash
$ vim sentinel2.conf
```

```conf:sentinel2.conf
bind 127.0.0.1
port 26380
daemonize yes

pidfile /tmp/redis_sentinel2.pid


# sentinel monitor <master-name>  <ip>       <redis-port>  <quorum>
sentinel monitor   servers1       127.0.0.1  16379         2

sentinel down-after-milliseconds servers1 2500
sentinel failover-timeout servers1 10000
```

```bash
$ vim sentinel3.conf
```

```conf:sentinel3.conf
bind 127.0.0.1
port 26381
daemonize yes

pidfile /tmp/redis_sentinel3.pid


# sentinel monitor <master-name>  <ip>       <redis-port>  <quorum>
sentinel monitor   servers1       127.0.0.1  16379         2

sentinel down-after-milliseconds servers1 2500
sentinel failover-timeout servers1 10000
```

さて、この後起動するのですが、
sentinelはコンフィグを自動的に書き換えますので、
その点は留意しておく必要があります。

```bash
$ redis-sentinel sentinel1.conf
$ redis-sentinel sentinel2.conf
$ redis-sentinel sentinel3.conf
```

ちょっと経つと、3ファイルともコンフィグが書き換わってます。

```bash
$ cat sentinel1.conf
bind 127.0.0.1
port 26379
daemonize yes

pidfile "/tmp/redis_sentinel1.pid"

# sentinel monitor <master-name>  <ip>       <redis-port>  <quorum>
sentinel myid 15c12113c628851836e39a1de9bf579879fd5005

sentinel deny-scripts-reconfig yes
sentinel monitor servers1 127.0.0.1 16379 2
# Generated by CONFIG REWRITE
dir "/home/vagrant/redis-work"
maxclients 4064
sentinel down-after-milliseconds servers1 2500
sentinel failover-timeout servers1 10000
sentinel config-epoch servers1 19
sentinel leader-epoch servers1 19
sentinel known-slave servers1 127.0.0.1 16380
sentinel known-sentinel servers1 127.0.0.1 26381 d00ffb8bb57f4c78f825dba247f0d18d155d2de2
sentinel known-sentinel servers1 127.0.0.1 26380 2b79180a7f9ba77012461d0e522a27d838927ea5
sentinel current-epoch 19
```

こんな感じで、もとの設定ファイルでは、masterの接続先のみを記述したにもかかわらず、
自分以外のsentinelとslaveの情報がconfigに追記されていることがわかります。

これでsentinelの準備は完了です。


# redis-sentinelの利用

今回はperlのRedisモジュールである、[Redis::Fast](https://metacpan.org/pod/Redis::Fast)を用います。
処理速度的にはRedis::Jetが早いのですが、sentinel対応していないので・・・。


ということで、sentinelを使う場合は通常の接続方法と違って、
sentinelサーバの接続先を記載します。

```perl:test-sentinel.pl
use strict;
use warnings;
use 5.020;
use Redis::Fast;

my $redis   = Redis::Fast->new(
    sentinels   => [qw/
        127.0.0.1:26379
        127.0.0.1:26380
        127.0.0.1:26381
    /],
    service     => "servers1",
    sentinels_write_timeout => 1,
    sentinels_read_timeout  => 1,
    sentinels_cnx_timeout   => 0.1,
    reconnect => 10, every  => 300_000
);


my $i   = 0;
my $key = "hoge";
while (1) {
    eval {
        $redis->set($key, $i);
        say $redis->get($key);
    };

    if ($@) {
        warn "$@";
        next;
    }
    $i++;
    sleep(1);
}
```

キモとなるのは、

* new時にsentinelsでsentilelサーバ群の接続先を書くこと
    * redis-serverではない。
* serviceに、sentinelで指定した<master-name>を指定すること
* reconnect（秒数）, every（ミリ秒）を適切に設定すること
    * 設定しないと再接続しない

です。


# 実験

プログラムを動かします。

```bash
$ perl test-sentinel.pl
0
1
2
...
```

別シェルを起動して、現在のmasterをkillします。

```bash
$ redis-cli  -p 26379 SENTINEL get-master-addr-by-name servers1
1) "127.0.0.1"
2) "16379"

$ pkill -f 127.0.0.1:16379 
```

すると、3秒程度で、

```bash
$ redis-cli  -p 26379 SENTINEL get-master-addr-by-name servers1
1) "127.0.0.1"
2) "16380"
```

と、もう方系がmasterに昇格します。
次に、落としたredisを再起動します。

```bash
$ redis-server 16379.conf

$ redis-cli -p 16379 info replication                              
# Replicationn
role:master
connected_slaves:0
master_replid:621891c20bbffd2019a2980976ca212c792ca39f
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
```

このように、最初はmasterで立ち上がってきますが、
ちょっと経って再度チェックすると、

```bash
$  redis-cli -p 16379 info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:16380
master_link_status:up
master_last_io_seconds_ago:1
master_sync_in_progress:0
slave_repl_offset:97189
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:4fc10a2b219a9dcaf13a14cbc41652032733706a
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:97189
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:90363
repl_backlog_histlen:6827
```
このように、slaveとなっています。

このときのプログラムの出力を確認してほしいのですが、
途切れなく数値が続いているはずです。

## 注意点
方系を落とし、落とした方を再起動したあと、
slaveになる前にもう方系をkillすると、うまくfailoverしません。
実験時には、2台のredisがmaster / slave の状態になっていることを確認し、
master側をkillしましょう。


# 終わりに
redis-sentinelは、sentinelサーバがつなぐべきmasterを教えてくれるため、
例えばkeepalivedでVIPをもたせて、アプリ側でそちらにつなぐ、
ようなことは不要です。slaveのmaster化も勝手にやってくれますし。

sentinel対応してくれている、モジュールの開発者様様ですね。