Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
3
Help us understand the problem. What is going on with this article?
@charon

redis-pyからRedis Sentinelにアクセスしてみる

More than 1 year has passed since last update.

TL;DR

  • redis-pyはRedis Sentinelに対応したモジュールがあるので、そちらを使ってRedis Sentinelにアクセスが可能
  • Redis Sentinel経由で取得したRedisへの接続はコネクションプールであり、通常のRedisへの操作と同様に利用可能
  • マスターがダウンした際に使っていた接続(コネクションプール)は、フェイルオーバーに追従してくれる

環境

今回の環境は、こちら。

$ cat /etc/redhat-release 
CentOS Linux release 7.6.1810 (Core)


$ uname -a
Linux localhost.localdomain 3.10.0-957.12.2.el7.x86_64 #1 SMP Tue May 14 21:24:32 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux


$ redis-server -v
Redis server v=5.0.6 sha=00000000:0 malloc=jemalloc-5.1.0 bits=64 build=c3d7ebb6b1a2844b


$ redis-sentinel -v
Redis server v=5.0.6 sha=00000000:0 malloc=jemalloc-5.1.0 bits=64 build=c3d7ebb6b1a2844b


$ python -V
Python 3.6.8


$ pip3 freeze
redis==3.3.8

CentOS 7、Redis 5.0.6、Python 3.6.8、redis-py 3.3.8です。

各サーバーは、以下のように用意します。

  • マスター … 192.168.33.10
  • レプリカ … 192.168.33.11
  • Sentinel 1〜3 … 192.168.33.12194.168.33.14
  • クライアント … 192.168.33.15

Redis Sentinelの構築

まずは、Redis Sentinalを構築しましょう。

EPELから、Redisをインストールします。

$ sudo yum install epel-release
$ sudo yum install https://rpms.remirepo.net/enterprise/remi-release-7.rpm
$ sudo yum --enablerepo=remi install redis

まずは、Redisのレプリケーションを構成します。

Redisのマスターは、/etc/redis.confのデフォルト設定から以下のように変更。

※デフォルト設定の全体は、最後に記載します

bind 0.0.0.0

レプリカ側は、/etc/redis.confのデフォルト設定から以下のように変更。

bind 0.0.0.0

replicaof 192.168.33.10 6379

Redisのマスター、レプリカをそれぞれ起動して

$ sudo systemctl start redis

レプリケーションが動作していることを確認。

マスター側。

$ redis-cli -h 192.168.33.10
192.168.33.10:6379> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=192.168.33.11,port=6379,state=online,offset=28,lag=0
master_replid:54810ab6453ffdf12f21ff34157e2e0d655f8a81
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:28
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:28

レプリカ側。

$ redis-cli -h 192.168.33.11
192.168.33.11:6379> info replication
# Replication
role:slave
master_host:192.168.33.10
master_port:6379
master_link_status:up
master_last_io_seconds_ago:6
master_sync_in_progress:0
slave_repl_offset:56
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:54810ab6453ffdf12f21ff34157e2e0d655f8a81
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:56
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:56

続いて、Redis Sentinelを構成します。/etc/redis-sentinel.confのデフォルト設定から、以下のように変更します。

※デフォルト設定の全体は、最後に記載します

sentinel monitor mymaster 192.168.33.10 6379 2

Redis Sentinel起動(3台で行います)。

$ sudo systemctl start redis-sentinel

確認。

$ redis-cli -h 192.168.33.12 -p 26379
192.168.33.12:26379> info sentinel
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=192.168.33.10:6379,slaves=1,sentinels=3

マスター、レプリカがそれぞれひとつ、Sentinelが3つある状態ですね。

redis-pyで、Redis Sentinelにアクセスする

それでは、今度はRedis Sentinelにredis-pyからアクセスしてみます。まずはインストール。

$ pip3 install redis

今回のredis-pyのバージョンです。

$ pip3 freeze
redis==3.3.8

redis-pyからRedis Sentinelへのアクセスですが、以下を参考に

Sentinel support

コマンドラインプログラムを書いてみました。

sentinel_client.py

import re
from redis.exceptions import ReadOnlyError
from redis.sentinel import Sentinel
import sys

try:
    print('start sentinel client')

    sentinels = [('192.168.33.12', 26379), ('192.168.33.13', 26379), ('192.168.33.14', 26379)]
    sentinel = Sentinel(sentinels, decode_responses = True)

    redis = None

    while True:
        if redis != None:
            print('{}: > '.format(redis), end = '', flush = True)
        else:
            print('> ', end = '', flush = True)

        command = sys.stdin.readline().strip()

        if command == 'info master':
            print(sentinel.discover_master('mymaster'))
        elif command == 'info replicas':
            print(sentinel.discover_slaves('mymaster'))
        elif command == 'use master':
            redis = sentinel.master_for('mymaster')
        elif command == 'use replica':
            redis = sentinel.slave_for('mymaster')
        elif command == 'info':
            print(str(redis.info()))
        elif command.startswith('set '):
            tokens = re.split('\s+', command)
            key = tokens[1]
            value = tokens[2]

            try:
                redis.set(key, value)
                print('set {} = {}'.format(key, value))
            except ReadOnlyError as e:
                print('{}: {}'.format(e.__class__.__name__, e))

        elif command.startswith('get '):
            tokens = re.split('\s+', command)
            key = tokens[1]
            print('get[{}] = {}'.format(key, redis.get(key)))
        elif command.startswith('del '):
            tokens = re.split('\s+', command)
            key = tokens[1]

            try:
                redis.delete(key)
                print('del, {}'.format(key))
            except ReadOnlyError as e:
                print('{}: {}'.format(e.__class__.__name__, e))
        elif command == 'exit':
            print('bye bye!!')
            break
        elif not command:
            pass
        else:
            print('unknown command = {}'.format(command))

except KeyboardInterrupt:
    print('bye bye!!')

Sentinelへのアクセスは、各Sentinelプロセスへのアクセス先を使って、Sentinelインスタンスを作成することで行います。

    sentinels = [('192.168.33.12', 26379), ('192.168.33.13', 26379), ('192.168.33.14', 26379)]
    sentinel = Sentinel(sentinels, decode_responses = True)

マスターおよびレプリカへのアクセスは、Sentinel#master_forまたはSentinel#slave_forで行います。

            redis = sentinel.master_for('mymaster')

            redis = sentinel.slave_for('mymaster')

各メソッドの戻り値は、SentinelConnectionPoolのインスタンスです。

あとは、SentinelConnectionPoolに対してRedisコマンドを実行すればOKです。

        elif command.startswith('set '):
            tokens = re.split('\s+', command)
            key = tokens[1]
            value = tokens[2]

            try:
                redis.set(key, value)
                print('set {} = {}'.format(key, value))
            except ReadOnlyError as e:
                print('{}: {}'.format(e.__class__.__name__, e))

        elif command.startswith('get '):
            tokens = re.split('\s+', command)
            key = tokens[1]
            print('get[{}] = {}'.format(key, redis.get(key)))
        elif command.startswith('del '):
            tokens = re.split('\s+', command)
            key = tokens[1]

            try:
                redis.delete(key)
                print('del, {}'.format(key))
            except ReadOnlyError as e:
                print('{}: {}'.format(e.__class__.__name__, e))

あとは、情報取得系のコマンドを…。

        if command == 'info master':
            print(sentinel.discover_master('mymaster'))
        elif command == 'info replicas':
            print(sentinel.discover_slaves('mymaster'))


        elif command == 'info':
            print(str(redis.info()))

discover〜はSentinelにおけるマスター、レプリカの情報で、infoは接続しているRedisの情報です。

では、実行してみます。

$ python3 sentinel_client.py
start sentinel client
> 

マスター、レプリカの情報。

> info master
('192.168.33.10', 6379)
> info replicas
[('192.168.33.11', 6379)]

マスターに接続。

> use master

set、get。

Redis<SentinelConnectionPool<service=mymaster(master)>: > set key1 value1
set key1 = value1
Redis<SentinelConnectionPool<service=mymaster(master)>: > get key1
get[key1] = value1

レプリカにつなぎなおして、データ取得。

Redis<SentinelConnectionPool<service=mymaster(master)>: > use replica
Redis<SentinelConnectionPool<service=mymaster(slave)>: > get key1
get[key1] = value1

レプリカ側では、データの更新は不可です。

Redis<SentinelConnectionPool<service=mymaster(slave)>: > set key2 value2
ReadOnlyError: You can't write against a read only replica.

ここで、再度マスターに接続。

Redis<SentinelConnectionPool<service=mymaster(slave)>: > use master

マスターのRedisを停止してみます。

$ sudo systemctl stop redis

redis-cliで、マスターが切り替わったことを確認します。

$ redis-cli -h 192.168.33.12 -p 26379
192.168.33.12:26379> info sentinel
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=192.168.33.11:6379,slaves=1,sentinels=3

この状態で、先程のマスターにつないだコマンドからデータを取得してみます。

Redis<SentinelConnectionPool<service=mymaster(master)>: > get key1
get[key1] = value1

なんか、動きました…。

このRedisの情報を見てみます。

Redis<SentinelConnectionPool<service=mymaster(master)>: > info
{'redis_version': '5.0.6', 'redis_git_sha1': 0, 'redis_git_dirty': 0, 'redis_build_id': 'c3d7ebb6b1a2844b', 'redis_mode': 'standalone', 'os': 'Linux 3.10.0-957.12.2.el7.x86_64 x86_64', 'arch_bits': 64, 'multiplexing_api': 'epoll', 'atomicvar_api': 'atomic-builtin', 'gcc_version': '4.8.5', 'process_id': 24544, 'run_id': 'ad1e763f073c8fcce092a773f2bbd2c5933d2bf9', 'tcp_port': 6379, 'uptime_in_seconds': 440050, 'uptime_in_days': 5, 'hz': 10, 'configured_hz': 10, 'lru_clock': 9707634, 'executable': '/usr/bin/redis-server', 'config_file': '/etc/redis.conf', 'connected_clients': 7, 'client_recent_max_input_buffer': 2, 'client_recent_max_output_buffer': 0, 'blocked_clients': 0, 'used_memory': 2070088, 'used_memory_human': '1.97M', 'used_memory_rss': 4554752, 'used_memory_rss_human': '4.34M', 'used_memory_peak': 2173256, 'used_memory_peak_human': '2.07M', 'used_memory_peak_perc': '95.25%', 'used_memory_overhead': 2024062, 'used_memory_startup': 791416, 'used_memory_dataset': 46026, 'used_memory_dataset_perc': '3.60%', 'allocator_allocated': 2637720, 'allocator_active': 3035136, 'allocator_resident': 7573504, 'total_system_memory': 510861312, 'total_system_memory_human': '487.20M', 'used_memory_lua': 37888, 'used_memory_lua_human': '37.00K', 'used_memory_scripts': 0, 'used_memory_scripts_human': '0B', 'number_of_cached_scripts': 0, 'maxmemory': 0, 'maxmemory_human': '0B', 'maxmemory_policy': 'noeviction', 'allocator_frag_ratio': 1.15, 'allocator_frag_bytes': 397416, 'allocator_rss_ratio': 2.5, 'allocator_rss_bytes': 4538368, 'rss_overhead_ratio': 0.6, 'rss_overhead_bytes': -3018752, 'mem_fragmentation_ratio': 2.25, 'mem_fragmentation_bytes': 2526672, 'mem_not_counted_for_evict': 0, 'mem_replication_backlog': 1048576, 'mem_clients_slaves': 0, 'mem_clients_normal': 183998, 'mem_aof_buffer': 0, 'mem_allocator': 'jemalloc-5.1.0', 'active_defrag_running': 0, 'lazyfree_pending_objects': 0, 'loading': 0, 'rdb_changes_since_last_save': 0, 'rdb_bgsave_in_progress': 0, 'rdb_last_save_time': 1569988537, 'rdb_last_bgsave_status': 'ok', 'rdb_last_bgsave_time_sec': 0, 'rdb_current_bgsave_time_sec': -1, 'rdb_last_cow_size': 221184, 'aof_enabled': 0, 'aof_rewrite_in_progress': 0, 'aof_rewrite_scheduled': 0, 'aof_last_rewrite_time_sec': -1, 'aof_current_rewrite_time_sec': -1, 'aof_last_bgrewrite_status': 'ok', 'aof_last_write_status': 'ok', 'aof_last_cow_size': 0, 'total_connections_received': 26, 'total_commands_processed': 2737329, 'instantaneous_ops_per_sec': 3, 'total_net_input_bytes': 202595677, 'total_net_output_bytes': 1041974292, 'instantaneous_input_kbps': 0.2, 'instantaneous_output_kbps': 0.54, 'rejected_connections': 0, 'sync_full': 0, 'sync_partial_ok': 0, 'sync_partial_err': 0, 'expired_keys': 0, 'expired_stale_perc': 0.0, 'expired_time_cap_reached_count': 0, 'evicted_keys': 0, 'keyspace_hits': 3, 'keyspace_misses': 2, 'pubsub_channels': 1, 'pubsub_patterns': 0, 'latest_fork_usec': 277, 'migrate_cached_sockets': 0, 'slave_expires_tracked_keys': 0, 'active_defrag_hits': 0, 'active_defrag_misses': 0, 'active_defrag_key_hits': 0, 'active_defrag_key_misses': 0, 'role': 'master', 'connected_slaves': 0, 'master_replid': '56d4bc022b7a089800bfe723201c821f570f2fe4', 'master_replid2': '54810ab6453ffdf12f21ff34157e2e0d655f8a81', 'master_repl_offset': 91797500, 'second_repl_offset': 91786762, 'repl_backlog_active': 1, 'repl_backlog_size': 1048576, 'repl_backlog_first_byte_offset': 90748925, 'repl_backlog_histlen': 1048576, 'used_cpu_sys': 1280.370212, 'used_cpu_user': 41.081586, 'used_cpu_sys_children': 0.004907, 'used_cpu_user_children': 0.0, 'cluster_enabled': 0, 'db0': {'keys': 1, 'expires': 0, 'avg_ttl': 0}}

レプリカがいないことになっていますね。

'connected_slaves': 0

ということは、接続しているのはフェイルオーバーしたレプリカのようです。

この状態で、レプリカに接続してデータを取得してみると、なんと動きます。

Redis<SentinelConnectionPool<service=mymaster(master)>: > use replica
Redis<SentinelConnectionPool<service=mymaster(slave)>: > get key1
get[key1] = value1

discover〜で見てみると、こういう状態です。

Redis<SentinelConnectionPool<service=mymaster(slave)>: > info master
('192.168.33.11', 6379)
Redis<SentinelConnectionPool<service=mymaster(slave)>: > info replicas
[]

となると、この場合はレプリカにつないでも、つなぐべきレプリカがいないのでマスターに接続することになるわけですね。

実際、更新も可能です。

Redis<SentinelConnectionPool<service=mymaster(slave)>: > use replica
Redis<SentinelConnectionPool<service=mymaster(slave)>: > set key2 value2
set key2 = value2

というわけで、マスターを使っている時に接続先がダウンした場合、フェイルオーバーしたマスターに昇格した旧レプリカにつないでくれるということですね(Sentinelが検知するまでの間の挙動にについては、今回は見ていませんが)。

とりあえず、挙動はなんとなくわかった感じです。

付録:EPELからインストールした、RedisおよびRedis Sentinelのデフォルト値

EPELからインストールした、/etc/redis.confのデフォルト値。

$ sudo grep -v '#' /etc/redis.conf | grep -v '^$'
bind 127.0.0.1
protected-mode yes
port 6379
tcp-backlog 511
timeout 0
tcp-keepalive 300
daemonize no
supervised no
pidfile /var/run/redis_6379.pid
loglevel notice
logfile /var/log/redis/redis.log
databases 16
always-show-logo yes
save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb
dir /var/lib/redis
replica-serve-stale-data yes
replica-read-only yes
repl-diskless-sync no
repl-diskless-sync-delay 5
repl-disable-tcp-nodelay no
replica-priority 100
lazyfree-lazy-eviction no
lazyfree-lazy-expire no
lazyfree-lazy-server-del no
replica-lazy-flush no
appendonly no
appendfilename "appendonly.aof"
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-load-truncated yes
aof-use-rdb-preamble yes
lua-time-limit 5000
slowlog-log-slower-than 10000
slowlog-max-len 128
latency-monitor-threshold 0
notify-keyspace-events ""
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-size -2
list-compress-depth 0
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
hll-sparse-max-bytes 3000
stream-node-max-bytes 4096
stream-node-max-entries 100
activerehashing yes
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit replica 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
hz 10
dynamic-hz yes
aof-rewrite-incremental-fsync yes
rdb-save-incremental-fsync yes

EPELからインストールした、/etc/redis-sentinel.confのデフォルト設定。

$ sudo grep -v '#' /etc/redis-sentinel.conf | grep -v '^$'
port 26379
daemonize no
pidfile "/var/run/redis-sentinel.pid"
logfile "/var/log/redis/sentinel.log"
dir "/tmp"
sentinel myid 88cdee281108c92337965c782f60653f4f5d00fe
sentinel deny-scripts-reconfig yes
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel config-epoch mymaster 0
sentinel leader-epoch mymaster 0
protected-mode no
supervised systemd
sentinel known-replica mymaster 192.168.33.11 6379
sentinel known-sentinel mymaster 192.168.33.14 26379 4cdd5e38eccc965513db0d8be46b9eb6614418da
sentinel known-sentinel mymaster 192.168.33.13 26379 22249d49a0b530709b7897cbbb88a2db6da6a1b1
sentinel current-epoch 0
3
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
charon
"CROSS THE RUBICON”
tis
創業50年超のSIerです。

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
3
Help us understand the problem. What is going on with this article?