尝试使用 redis-py 访问 Redis Sentinel

太长不看

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.12〜194.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

使用3台Redis Sentinel进行启动。

$ 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。

用redis-py库访问Redis Sentinel

那么,现在让我们从redis-py尝试访问Redis Sentinel。首先进行安装。

$ pip3 install redis

这是redis-py的版本。

$ pip3 freeze
redis==3.3.8

关于从redis-py到Redis Sentinel的访问,可参考以下方案。

哨兵支援

我尝试编写了一个命令行程序。

车载客户端.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命令即可。

        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

设置、获取。

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
广告
将在 10 秒后关闭
bannerAds