Redis(主从复制、读写分离、主从切换)架构

Redis的集群方案大致有三种:

1)redis cluster集群方案;
2)master/slave主从方案;
3)哨兵模式来进行主从替换以及故障恢复。

一、sentinel哨兵模式介绍

Sentinel(哨兵)是用于监控redis集群中Master状态的工具,是Redis 的高可用性解决方案,sentinel哨兵模式已经被集成在redis2.4之后的版本中。sentinel是redis高可用的解决方案,sentinel系统可以监视一个或者多个redis master服务,以及这些master服务的所有从服务;当某个master服务下线时,自动将该master下的某个从服务升级为master服务替代已下线的master服务继续处理请求。
 
sentinel可以让redis实现主从复制,当一个集群中的master失效之后,sentinel可以选举出一个新的master用于自动接替master的工作,集群中的其他redis服务器自动指向新的master同步数据。一般建议sentinel采取奇数台,防止某一台sentinel无法连接到master导致误切换。其结构如下:
 
Redis-Sentinel是Redis官方推荐的高可用性(HA)解决方案,当用Redis做Master-slave的高可用方案时,假如master宕机了,Redis本身(包括它的很多客户端)都没有实现自动进行主备切换,而Redis-sentinel本身也是一个独立运行的进程,它能监控多个master-slave集群,发现master宕机后能进行自动切换。Sentinel由一个或多个Sentinel 实例 组成的Sentinel 系统可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器。
例如下图所示:
 
在Server1 掉线后:
升级Server2 为新的主服务器:

Sentinel工作方式(每个Sentinel实例都执行的定时任务)

1)每个Sentinel以每秒钟一次的频率向它所知的Master,Slave以及其他 Sentinel 实例发送一个PING命令。
2)如果一个实例(instance)距离最后一次有效回复PING命令的时间超过 own-after-milliseconds 选项所指定的值,则这个实例会被Sentinel标记为主观下线。 
3)如果一个Master被标记为主观下线,则正在监视这个Master的所有 Sentinel 要以每秒一次的频率确认Master的确进入了主观下线状态。 
4)当有足够数量的Sentinel(大于等于配置文件指定的值)在指定的时间范围内确认Master的确进入了主观下线状态,则Master会被标记为客观下线。
5)在一般情况下,每个Sentinel 会以每10秒一次的频率向它已知的所有Master,Slave发送 INFO 命令。
6)当Master被Sentinel标记为客观下线时,Sentinel 向下线的 Master 的所有Slave发送 INFO命令的频率会从10秒一次改为每秒一次。 
7)若没有足够数量的Sentinel同意Master已经下线,Master的客观下线状态就会被移除。 若 Master重新向Sentinel 的PING命令返回有效回复,Master的主观下线状态就会被移除。

二、redis sentinel 主从切换(failover)的容灾环境部署记录

redis主从复制简单来说:
A)Redis的复制功能是支持多个数据库之间的数据同步。一类是主数据库(master)一类是从数据库(slave),主数据库可以进行读写操作,当发生写操作的时候自动将数据同步到从数据库,而从数据库一般是只读的,并接收主数据库同步过来的数据,一个主数据库可以有多个从数据库,而一个从数据库只能有一个主数据库。
B)通过redis的复制功能可以很好的实现数据库的读写分离,提高服务器的负载能力。主数据库主要进行写操作,而从数据库负责读操作。

Redis主从复制流程简图

 

redis主从复制的大致过程:

1)当一个从数据库启动时,会向主数据库发送sync命令,
2)主数据库接收到sync命令后会开始在后台保存快照(执行rdb操作),并将保存期间接收到的命令缓存起来
3)当快照完成后,redis会将快照文件和所有缓存的命令发送给从数据库。
4)从数据库收到后,会载入快照文件并执行收到的缓存的命令。
注意:redis2.8之前的版本:当主从数据库同步的时候从数据库因为网络原因断开重连后会重新执行上述操作,不支持断点续传。redis2.8之后支持断点续传。
 

本次使用得是Ansible自动部署——>redis(主从+哨兵)

  • 规划:
hostname
ip
           port
server
master.redis
10.0.36.131
56379、46379
ansible+redis(master)(sentinel)
slave1.redis
      10.0.36.132
56379、46379
redis(slave)(sentinel)
slave2.redis
10.0.36.134
56379、46379
redis(slave)(sentinel)
    • redis/sentinel安装地址:/usr/local/redis-5.0.12/
    • redis/sentinel配置文件:/usr/local/redis-5.0.12/{redis.conf,sentinel.conf}
    • redis/sentinel启动文件(systemd管理):systemctl   start/stop/restart  redis.service/sentinel.service
    • redis/sentinel日志文件: /usr/local/redis-5.0.12/var/{redis.log, redis-sentinel.log}
    • redis端口:56379
    • sentinel端口:46379
    • redis密码:xxxxxx
    • 最大内存限制:6G
    • 最大连接数限制:10000
  • 具体配置
1) 在master服务器上 (10.0.36.131)上传Redis.tar.gz---文件宝点提供
2) 解压后,可查看(前提在master服务器安装ansible,且可通外网)
├── host_passwd.txt    #三台服务器的IP及root密码,以空格分隔
├── key.sh             #批量上传ssh公钥脚本
├── redis-hosts        #redis的ansible操作hosts文件
├── redis.yml          #redis安装主yaml文件
└── roles              #利用ansible的roles安装
    └── redis
        ├── defaults
        │   └── main.yml    #默认变量
        ├── files
        │   └── redis-5.0.12.tar.gz    #redis安装包
        ├── handlers
        ├── tasks
        │   └── main.yml        #redis及sentinel安装具体步骤yaml文件
        └── templates
            ├── redis.conf.j2        #redis配置文件模板
            ├── redis.service.j2        #sentinel配置文件模板
            ├── sentinel.conf.j2        #redis启动配置文件模板 
            └── sentinel.service.j2        #sentinel启动文件模板
 3) 操作
# ansible-playbook -i redis-hosts redis.yml -C    
# ansible-playbook -i redis-hosts redis.yml
 
ln -s /usr/local/redis-5.0.12/src/redis-cli /usr/local/bin/
 
 
4) 查看
[root@redis-master ~]# redis-cli -a xxxxxx -h 10.0.36.131 -p 56379 info Replication
# Replication
role:master
connected_slaves:2
slave0:ip=10.0.36.132,port=56379,state=online,offset=1420250,lag=0
slave1:ip=10.0.36.134,port=56379,state=online,offset=1420250,lag=0
master_replid:96a1fd63d0ad9e7903851a82382e32d690667bcc
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:61626
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:61626
[root@redis-master ~]# redis-cli -a xxxxxx -h 10.0.36.131 -p 46379 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=redisMaster,status=ok,address=10.0.36.131:56379,slaves=2,sentinels=3
    • 如果有问题撤回以上操作,重新安装:
# ansible redis -i redis-hosts -m shell -a 'pkill redis'
# ansible redis -i redis-hosts -m shell -a 'pkill sentinel'
# ansible redis -i redis-hosts -m shell -a 'rm -rf /usr/local/redis-5.0.12/'
# ansible redis -i redis-hosts -m shell -a 'rm -rf /usr/lib/systemd/system/{redis.service,sentinel.service}'
 
5) 客户端写入测试数据
客户端连接master节点,写入一条数据
[root@redis-master src]# redis-cli -a xxxxxx -h 10.0.36.131 -p 56379
10.0.36.131:56379> set name kevin;
OK
10.0.36.131:56379> get name
"kevin";
然后客户端再连接任意slave节点,通过get获取上面的那条数据
[root@redis-master src]# redis-cli -a xxxxxx -h 10.0.36.132 -p 56379
10.0.36.132:56379> get name
"kevin;"
10.0.36.132:56379> set name grace;
(error) READONLY You can't write against a read only slave.
10.0.36.132:56379>
[root@redis-master src]# redis-cli -a xxxxxx -h 10.0.36.134 -p 56379
10.0.36.134:56379> get name
"kevin;"
10.0.36.134:56379> set name grace;
(error) READONLY You can't write against a read only slave.
10.0.36.134:56379>

 

由上面测试信息可知,master节点可以写入,可以读取;而slave节点默认只能读取,不能写入!这就实现了主从复制,读写分离了!
 

6) 模拟故障(通过sentinel实现主从切换,sentinel也要部署多台,即集群模式,防止单台sentinel挂掉情况)

1)关掉任意一个slave节点(比如关闭掉slave01节点),所有节点的sentinel都可以检测到,出现如下示例信息
[root@redis-master src]# redis-cli -a xxxxxx -h 10.0.36.132 -p 56379
10.0.36.132:56379> shutdown                             
not connected>
说明:shutdown命令表示关闭redis
 
从上可看出132被sentinel检测到已处于关闭状态,此时再来查看剩余节点的主从信息,它们的角色不会发生变化,只是master上的connected_slaves变为了1。
[root@redis-master src]# redis-cli -a xxxxxx -h 10.0.36.131 -p 56379 info Replication
# Replication
role:master
connected_slaves:1
slave0:ip= 10.0.36.134,port=56379,state=online,offset=219376,lag=1
master_replid:96a1fd63d0ad9e7903851a82382e32d690667bcc
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:219376
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:219376
查看sentinel日志(任意节点上查看),发现132节点已经进入"+sdown"状态
[root@redis-master src]# tail -f /usr/local/redis-5.0.12/var/redis-sentinel.log
2315:X 08 May 18:49:51.429 * Increased maximum number of open files to 10032 (it was originally set to 1024).
2315:X 08 May 18:49:51.431 * Running mode=sentinel, port=46379.
2315:X 08 May 18:49:51.431 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
2315:X 08 May 18:49:51.463 # Sentinel ID is c165761901b5ea3cd2d622bbf13f4c99eb73c1bc
2315:X 08 May 18:49:51.463 # +monitor master redisMaster 10.0.36.131 56739 quorum 2
2315:X 08 May 18:50:11.544 * +slave slave 10.0.36.132:56379 10.0.36.132 56739 @ redisMaster 10.0.36.131 56379
2315:X 08 May 18:50:11.574 * +slave slave 10.0.36.134:56379 10.0.36.134 56739 @ redisMaster 10.0.36.131 56379
2315:X 08 May 18:50:15.088 * +sentinel sentinel e1505ffc65f787871febfde2f27b762f70cddd71 10.0.36.132 46739 @ redisMaster 10.0.36.131 56379
2315:X 08 May 18:50:16.075 * +sentinel sentinel cc25d5f0e37803e888732d63deae3761c9f91e1d 10.0.36.134 46739 @ redisMaster 10.0.36.131 56379
2315:X 08 May 19:06:07.669 # +sdown slave 10.0.36.132:56379 10.0.36.132 56739 @ redisMaster 10.0.36.131 56739 
然后重启上面被关闭的slave节点(即10.0.36.132),所有节点的sentinel都可以检测到,可看出132又被sentinel检测到已处于可用状态,此时再来查看节点的主从信息,它们的角色仍然不会发生变化,master上的connected_slaves又变为了2
[root@redis-slave01 src]# systemctl restart redis.service
[root@redis-master src]# redis-cli -a xxxxxx -h 10.0.36.131 -p 56739 info replication
# Replication
role:master
connected_slaves:2
slave0:ip= 10.0.36.134,port=56379,state=online,offset=268216,lag=0
slave1:ip=10.0.36.132,port=56379,state=online,offset=268070,lag=1
master_replid:96a1fd63d0ad9e7903851a82382e32d690667bcc
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:268216
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:268216
查看sentinel日志(任意节点上查看),发现203节点已经进入"-sdown"状态
[root@redis-master src]# tail -f /usr/local/redis/var/redis-sentinel.log
2315:X 08 May 18:49:51.429 * Increased maximum number of open files to 10032 (it was originally set to 1024).
2315:X 08 May 18:49:51.431 * Running mode=sentinel, port=46379.
2315:X 08 May 18:49:51.431 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
2315:X 08 May 18:49:51.463 # Sentinel ID is c165761901b5ea3cd2d622bbf13f4c99eb73c1bc
2315:X 08 May 18:49:51.463 # +monitor master redisMaster 10.0.36.131 56739 quorum 2
2315:X 08 May 18:50:11.544 * +slave slave 10.0.36.132:56379 10.0.36.132 56739 @ redisMaster 10.0.36.131 56379
2315:X 08 May 18:50:11.574 * +slave slave 10.0.36.134:56379 10.0.36.134 56739 @ redisMaster 10.0.36.131 56379
2315:X 08 May 18:50:15.088 * +sentinel sentinel e1505ffc65f787871febfde2f27b762f70cddd71 10.0.36.132 46739 @ redisMaster 10.0.36.131 56379
2315:X 08 May 18:50:16.075 * +sentinel sentinel cc25d5f0e37803e888732d63deae3761c9f91e1d 10.0.36.134 46739 @ redisMaster 10.0.36.131 56379
2315:X 08 May 19:06:07.669 # +sdown slave 10.0.36.132:56379 10.0.36.132 56739 @ redisMaster 10.0.36.131 56379
2315:X 08 May 19:10:14.965 * +reboot slave 10.0.36.132:56379 10.0.36.132 56739 @ redisMaster 10.0.36.131 56379
2315:X 08 May 19:10:15.020 # -sdown slave 10.0.36.132:56379 10.0.36.132 56739 @ redisMaster 10.0.36.131 56739 
=======================================================================================================
 
2)关掉master节点(即10.0.36.131),待所有节点的sentinel都检测到后(稍等一会,2-3秒钟时间),再来查看两个Slave节点的主从信息,发现其中一个节点的角色通过选举后会成为master节点了!
[root@redis-master src]# redis-cli -a xxxxxx -h 10.0.36.131 -p 56379
10.0.36.131:6379> shutdown
not connected>
查看sentinel日志(任意节点上查看),发现202节点已经进入"+sdown"状态
[root@redis-master src]# tail -f /usr/local/redis-5.0.12/var/redis-sentinel.log
2315:X 08 May 19:17:03.722 # +failover-state-reconf-slaves master redisMaster 10.0.36.131 56379
2315:X 08 May 19:17:03.760 * +slave-reconf-sent slave 10.0.36.132:56379 10.0.36.132 56739 @ redisMaster 10.0.36.131 56379
2315:X 08 May 19:17:04.015 * +slave-reconf-inprog slave 10.0.36.132:56379 10.0.36.132 56739 @ redisMaster 10.0.36.131 56379
2315:X 08 May 19:17:04.459 # -odown master redisMaster 10.0.36.131 56379
2315:X 08 May 19:17:05.047 * +slave-reconf-done slave 10.0.36.132:56379 10.0.36.132 56739 @ redisMaster 10.0.36.131 56379
2315:X 08 May 19:17:05.131 # +failover-end master redisMaster 10.0.36.131 56379
2315:X 08 May 19:17:05.131 # +switch-master redisMaster 10.0.36.131 56739 10.0.36.134 56379
2315:X 08 May 19:17:05.131 * +slave slave 10.0.36.132:56379 10.0.36.132 56739 @ redisMaster 10.0.36.134 56379
2315:X 08 May 19:17:05.131 * +slave slave 10.0.36.131:56379 10.0.36.131 56739 @ redisMaster 10.0.36.134 56379
2315:X 08 May 19:17:15.170 # +sdown slave 10.0.36.131:56379 10.0.36.131 56739 @ redisMaster 10.0.36.134 56739 
[root@redis-slave01 src]# redis-cli -a xxxxxx -h 10.0.36.132 -p 56379 INFO|grep role
role:slave
[root@redis-slave01 src]# redis-cli -a xxxxxx -h 10.0.36.134 -p 56379 INFO|grep role     
role:master
由上可知,当master节点(即10.0.36.131)的redis关闭后,slave02节点(即 10.0.36.134)变成了新的master节点,而slave01(即10.0.36.132)成为了slave02的从节点!
 
10.0.36.134节点此时被选举为master,此时打开的134节点的redis.conf文件,replicaof配置项已被自动删除了。
而132从节点的redis.conf文件中replicaof配置项的值被自动修改为 10.0.36.134 56379。
[root@redis-slave02 src]# cat /usr/local/redis-5.0.12/redis.conf|grep replicaof
[root@redis-slave01 src]# cat /usr/local/redis-5.0.12/redis.conf|grep replicaof
replicaof 10.0.36.134 56739 
 
在这个新master上(即 10.0.36.134)执行诸如set这样的写入操作将被成功执行
[root@redis-slave01 src]# redis-cli -a xxxxxx -h 10.0.36.134 -p 56379
10.0.36.134:56379> set name beijing;
OK
[root@redis-master src]# redis-cli -a xxxxxx -h 10.0.36.132 -p 56379
10.0.36.132:56379> get name
"beijing;"
10.0.36.132:56379> set name tianjin;
(error) READONLY You can't write against a read only slave.
10.0.36.132:56379>
重启10.0.36.131节点的redis,待所有节点的sentinel都检测到后,再来查看所有节点的主从信息,此时 10.0.36.134节点的master角色不会被重新抢占,而10.0.36.131节点的角色会从原来的master变为了slave。
[root@redis-master src]# systemctl restart redis.service
[root@redis-master src]# redis-cli -a xxxxxx -h 10.0.36.131 -p 56379 INFO|grep role
role:slave
[root@redis-master src]# redis-cli -a xxxxxx -h 10.0.36.132 -p 56379 INFO|grep role
role:slave
[root@redis-master src]# redis-cli -a xxxxxx -h 10.0.36.134 -p 56379 INFO|grep role
role:master
 
[root@redis-master src]# redis-cli -a xxxxxx -h 10.0.36.131 -p 56379
10.0.36.131:56379> get name
"beijing;"
此时登录10.0.36.131节点的redis,执行"get name"得到的值为beijing,而不是原来的kevin,因为10.0.36.131节点的redis重启后会自动从新的master中同步数据。
 
此时打开10.0.36.131节点的redis.conf文件,会在末尾找到如下信息:
[root@redis-master src]# cat /usr/local/redis-5.0.12/redis.conf|grep replicaof
slaveof 10.0.36.134 56739 
 
到此,已经验证出了redis sentinel可以自行实现主从的故障切换了!
 
7) 客户端如何连接redis sentinel
 
客户端配置连接的是sentinel信息,比如连接sentinel.conf文件中定义的master名称。在sentinel监听时,当master节点挂了,它会在slave节点中自动选举出新的master节点,而当挂了的老master节点重新恢复后就会成为新的slave节点。对于客户端来说,redis主从切换后它不需要修改连接配置。
 
下面列出几个客户端连接redis sentinel的例子
1)java客户端在jedis中使用redis sentinel哨兵方式连接
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans">
    <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
         <property name="maxTotal" value="1000"/>
         <property name="maxIdle" value="10"/>
         <property name="minIdle" value="1"/>
         <property name="maxWaitMillis" value="30000"/>
         <property name="testOnBorrow" value="true"/>
         <property name="testOnReturn" value="true"/>
         <property name="testWhileIdle" value="true"/>
    </bean>
    <bean id="cacheService" class="sentinel.CacheServiceImpl" destroy-method="destroy">
        <property name="jedisSentinlePool">
            <bean class="redis.clients.jedis.JedisSentinelPool">
                 <constructor-arg index="0" value="mymaster" />
                 <constructor-arg index="1">
                     <set>
                         <value>10.0.36.131:46379</value>
                         <value>10.0.36.132:46379</value>
                         <value>10.0.36.134:46379</value>
                     </set>
                 </constructor-arg>
                 <constructor-arg index="2" ref="jedisPoolConfig" />
            </bean>
        </property>
    </bean>
</beans>

 

 
2)python连接redis sentinel集群(需要安装python redis客户端,即执行"pip install redis")
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import redis
from redis.sentinel import Sentinel
# 连接哨兵服务器(主机名也可以用域名)
sentinel = Sentinel([('10.0.36.131', 46379),
                     ('10.0.36.132', 46379),
                     ('10.0.36.134', 46379)
             ],
                    socket_timeout=0.5)
# 获取主服务器地址
master = sentinel.discover_master('mymaster')
print(master)
# 输出:('10.0.36.131', 46379)
# 获取从服务器地址
slave = sentinel.discover_slaves('mymaster')
print(slave)
# 输出:[('10.0.36.132', 46379), ('10.0.36.134', 46379), ('172.31.0.5', 46379)]
# 获取主服务器进行写入
master = sentinel.master_for('mymaster', socket_timeout=0.5, password='redis_auth_pass', db=15)
w_ret = master.set('foo', 'bar')
# 输出:True
# # 获取从服务器进行读取(默认是round-roubin)
slave = sentinel.slave_for('mymaster', socket_timeout=0.5, password='redis_auth_pass', db=15)
r_ret = slave.get('foo')
print(r_ret)
# # 输出:bar
 
3)Java客户端连接Redis(单sentinel).
下面例子中好的redisMaster和20180408是在sentinel.conf中定义的master名称和连接密码
package com.hiifit.cloudplatform.gaia.test;
import java.util.HashSet;
import java.util.Set;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisSentinelPool;
public class RedisSentinelTest {
    @SuppressWarnings("deprecation")
    public static void main(String[] args) {
        Set<String> sentinels = new HashSet<String>();
        String hostAndPort1 = "10.0.36.134:46379";
        sentinels.add(hostAndPort1);
        String clusterName = "redisMaster";
        String password = "20180408";
        JedisSentinelPool redisSentinelJedisPool = new JedisSentinelPool(clusterName,sentinels,password);
        Jedis jedis = null;
        try {
            jedis = redisSentinelJedisPool.getResource();
            jedis.set("key", "value");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            redisSentinelJedisPool.returnBrokenResource(jedis);
        }
        redisSentinelJedisPool.close();
    }
}

 

 
 
 
 
 
 
 
 
posted @ 2024-03-06 10:13  梦里花落知多少sl  阅读(327)  评论(0编辑  收藏  举报