redis缓存数据库详解(六):高可用方案

作为一个稀有的Java妹子,所写的所有博客都只是当作自己的笔记,留下证据自己之前是有用心学习的~哈哈哈哈(如果有不对的地方,也请大家指出,不要悄悄咪咪的不告诉我)

概述

redis在实际应用中安装部署的方式有所不同,如果对数据没有持久化的要求,只是为了提高读取数据效率,可能会采用单机模式;但是生产环境中基本上都会要求持久化数据,备份数据,节点失效转移等,那么这时候单节点部署就不能满足了,下面就介绍一下redis的高可用方案。

redis的高可用方案主要有两种:
1.主从复制(Replication-Sentinel模式)
2.集群(Redis-Cluster模式)

主从复制(Replication-Sentinel模式)

Redis Sentinel 是 Redis 高可用 的实现方案。Sentinel 是一个管理多个 Redis 实例的工具,它可以实现对 Redis 的 监控、通知、自动故障转移。下面先对 Redis Sentinel 的 基本概念 进行简单的介绍。

基本名词 逻辑结构 物理结构
Redis数据节点 主节点和从节点 主节点和从节点的进程
主节点(master) Redis主数据库 一个独立的Redis进程
从节点(slave) Redis从数据库 一个独立的Redis进程
Sentinel节点 监控Redis数据节点 一个独立的Sentinel进程
Sentinel节点集合 若干Sentinel节点的抽象组合 若干Sentinel节点进程
Redis Sentinel Redis高可用实现方案 Sentinel节点集合和Redis数据节点进程

1.Redis Sentinel的主要功能

1.从节点分担主节点的读压力
2.Sentinel监控主节点和从节点服务器的状态
3.当被监控的某个 Redis 服务器出现问题,Sentinel 通过 API 脚本 向 管理员 或者其他的 应用程序 发送通知。
4.当 主节点 不能正常工作时,Sentinel 会开始一次 自动的故障转移操作,它会将与 失效主节点 是 主从关系 的其中一个 从节点 升级为新的 主节点,并且将其他的 从节点 指向 新的主节点,然后还会修改配置文件。
5.在 Redis Sentinel 模式下,客户端应用 在初始化时连接的是 Sentinel 节点集合,从中获取 主节点 的信息,因为如果直接连接主节点信息,主节点挂掉后还要修改配置。

2.主观下线和客观下线

默认情况下,每个 Sentinel 节点会以 每秒一次 的频率对 Redis 节点和 其它 的 Sentinel 节点发送 PING 命令,并通过节点的 回复 来判断节点是否在线。

主观下线
主观下线 适用于所有 主节点 和 从节点。如果在 down-after-milliseconds 毫秒内,Sentinel 没有收到 目标节点 的有效回复,则会判定 该节点 为 主观下线。

客观下线
客观下线 只适用于 主节点。如果 主节点 出现故障,Sentinel 节点会通过 sentinel is-master-down-by-addr 命令,向其它 Sentinel 节点询问对该节点的 状态判断。如果超过 个数的节点判定 主节点 不可达,则该 Sentinel 节点会判断 主节点 为 客观下线。

主观下线只是其中一个哨兵节点探测到主节点不可达了,客观下线指当主节点主观下线后,超过指定个数额哨兵也去探测到主节点宕机了。主节点客观下线后就开始了故障转移,从新选举主节点的操作。

3.Redis Sentinel搭建

1.一个稳健的 Redis Sentinel 集群,应该使用至少 三个 Sentinel 实例,并且保证讲这些实例放到 不同的机器 上,甚至不同的 物理区域。
2.Sentinel 无法保证 强一致性。
3.常见的 客户端应用库 都支持 Sentinel。
4.Sentinel 需要通过不断的 测试 和 观察,才能保证高可用。

Redis Sentinel的配置文件

# 哨兵sentinel实例运行的端口,默认26379  
port 26379
# 哨兵sentinel的工作目录
dir ./
 
# 哨兵sentinel监控的redis主节点的 
## ip:主机ip地址
## port:哨兵端口号
## master-name:可以自己命名的主节点名字(只能由字母A-z、数字0-9 、这三个字符".-_"组成。)
## quorum:当这些quorum个数sentinel哨兵认为master主节点失联 那么这时 客观上认为主节点失联了  
# sentinel monitor <master-name> <ip> <redis-port> <quorum>  
sentinel monitor mymaster 127.0.0.1 6379 2
 
# 当在Redis实例中开启了requirepass <foobared>,所有连接Redis实例的客户端都要提供密码。
# sentinel auth-pass <master-name> <password>  
sentinel auth-pass mymaster 123456  
 
# 指定主节点应答哨兵sentinel的最大时间间隔,超过这个时间,哨兵主观上认为主节点下线,默认30秒  
# sentinel down-after-milliseconds <master-name> <milliseconds>
sentinel down-after-milliseconds mymaster 30000  
 
# 指定了在发生failover主备切换时,最多可以有多少个slave同时对新的master进行同步。这个数字越小,完成failover所需的时间就越长;反之,但是如果这个数字越大,就意味着越多的slave因为replication而不可用。可以通过将这个值设为1,来保证每次只有一个slave,处于不能处理命令请求的状态。
# sentinel parallel-syncs <master-name> <numslaves>
sentinel parallel-syncs mymaster 1  
 
# 故障转移的超时时间failover-timeout,默认三分钟,可以用在以下这些方面:
## 1. 同一个sentinel对同一个master两次failover之间的间隔时间。  
## 2. 当一个slave从一个错误的master那里同步数据时开始,直到slave被纠正为从正确的master那里同步数据时结束。  
## 3. 当想要取消一个正在进行的failover时所需要的时间。
## 4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来同步数据了
# sentinel failover-timeout <master-name> <milliseconds>  
sentinel failover-timeout mymaster 180000
 
# 当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本。一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行。
# 对于脚本的运行结果有以下规则:  
## 1. 若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10。
## 2. 若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。  
## 3. 如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。
# sentinel notification-script <master-name> <script-path>  
sentinel notification-script mymaster /var/redis/notify.sh
 
# 这个脚本应该是通用的,能被多次调用,不是针对性的。
# sentinel client-reconfig-script <master-name> <script-path>
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh

3.1 Redis Sentinel的节点规划

这里是模拟,所以是在一台物理机上部署的,生产环境上应该在不同的服务器上部署。特别是哨兵节点和redis节点不同部署在同一物理机上,不然redis挂了,哨兵节点也挂了,就检测不到redis的状态了。

角色 IP地址 端口号
Redis Master 127.0.01 16379
Redis Slave1 127.0.01 26379
Redis Slave2 127.0.01 36379
Redis Sentinel1 127.0.01 16380
Redis Sentinel2 127.0.01 26380
Redis Sentinel3 127.0.01 36380

3.2 Redis-Server的配置管理

1.分别拷贝三份 redis.conf 文件到 /usr/local/redis-sentinel 目录下面。三个配置文件分别对应 master、slave1 和 slave2 三个 Redis 节点的 启动配置。

cp /usr/local/redis-5.0.7/redis.conf /usr/local/redis-sentinel/redis-16379
cp /usr/local/redis-5.0.7/redis.conf /usr/local/redis-sentinel/redis-26379
cp /usr/local/redis-5.0.7/redis.conf /usr/local/redis-sentinel/redis-36379

2.分别修改三份配置文件如下:
主节点:redis-16379.conf

daemonize yes
pidfile /var/run/redis-16379.pid
logfile /var/log/redis/redis-16379.log
port 16379
bind 0.0.0.0
timeout 300
databases 16
dbfilename dump-16379.db
dir ./redis-workdir
masterauth 123456
requirepass 123456

从节点1:redis-26379.conf

daemonize yes
pidfile /var/run/redis-26379.pid
logfile /var/log/redis/redis-26379.log
port 26379
bind 0.0.0.0
timeout 300
databases 16
dbfilename dump-26379.db
dir ./redis-workdir
masterauth 123456
requirepass 123456
replicaof 127.0.0.1 16379

从节点2:redis-36379.conf

daemonize yes
pidfile /var/run/redis-36379.pid
logfile /var/log/redis/redis-36379.log
port 36379
bind 0.0.0.0
timeout 300
databases 16
dbfilename dump-36379.db
dir ./redis-workdir
masterauth 123456
requirepass 123456
replicaof 127.0.0.1 16379
	如果要做 自动故障转移,建议所有的 redis.conf 都设置 masterauth。
	因为自动故障只会重写 主从关系,即 slaveof,不会自动写入 
	masterauth。如果 Redis 原本没有设置密码,则可以忽略

3.按顺序分别启动 16379,26379 和 36379 三个 Redis 节点,启动命令和启动日志如下:

redis-server /usr/local/redis-sentinel/redis-16379.conf
redis-server /usr/local/redis-sentinel/redis-26379.conf
redis-server /usr/local/redis-sentinel/redis-36379.conf

主节点的启动日志如下:
在这里插入图片描述
6282:M 06 Mar 2020 14:19:05.966 * Replica 127.0.0.1:26379 asks for synchronization
6282:M 06 Mar 2020 14:19:08.738 * Replica 127.0.0.1:36379 asks for synchronization
这两行表明从节点从主节点同步信息

从节点启动日志:
在这里插入图片描述
在这里插入图片描述
6285:S 06 Mar 2020 14:19:05.964 * Connecting to MASTER 127.0.0.1:16379
从节点的这句话表明已经连接到主节点上

3.3 Sentinel的配置管理

1.分别拷贝三份 redis-sentinel.conf 文件到 /usr/local/redis-sentinel 目录下面。三个配置文件分别对应 master、slave1 和 slave2 三个 Redis 节点的 哨兵配置。

cp /usr/local/redis-5.0.7/sentinel /usr/local/redis-sentinel/sentinel-16380.conf
cp /usr/local/redis-5.0.7/sentinel /usr/local/redis-sentinel/sentinel-26380.conf
cp /usr/local/redis-5.0.7/sentinel /usr/local/redis-sentinel/sentinel-36380.conf

三个节点的配置信息如下

protected-mode no
bind 0.0.0.0
port 16380
daemonize yes
sentinel monitor mymaster 127.0.0.1 16379 2
sentinel down-after-milliseconds master 5000
sentinel failover-timeout master 180000
sentinel parallel-syncs master 1
sentinel auth-pass master 123456
logfile /var/log/redis/sentinel-16380.log
protected-mode no
bind 0.0.0.0
port 26380
daemonize yes
sentinel monitor mymaster 127.0.0.1 16379 2
sentinel down-after-milliseconds master 5000
sentinel failover-timeout master 180000
sentinel parallel-syncs master 1
sentinel auth-pass master 123456
logfile /var/log/redis/sentinel-26380.log
protected-mode no
bind 0.0.0.0
port 36380
daemonize yes
sentinel monitor mymaster 127.0.0.1 16379 2
sentinel down-after-milliseconds master 5000
sentinel failover-timeout master 180000
sentinel parallel-syncs master 1
sentinel auth-pass master 123456
logfile /var/log/redis/sentinel-26380.log

2.按顺序分别启动 16380,26380 和 36380 三个 Sentinel 节点,启动命令和启动日志如下:

MacBook-Pro:redis-5.0.7 root# tail -200f sentinel-16380.log
8684:X 06 Mar 2020 16:02:11.450 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
8684:X 06 Mar 2020 16:02:11.451 # Redis version=5.0.5, bits=64, commit=00000000, modified=0, pid=8684, just started
8684:X 06 Mar 2020 16:02:11.451 # Configuration loaded
8685:X 06 Mar 2020 16:02:11.453 * Increased maximum number of open files to 10032 (it was originally set to 4864).
8685:X 06 Mar 2020 16:02:11.454 * Running mode=sentinel, port=16380.
8685:X 06 Mar 2020 16:02:11.455 # Sentinel ID is 1188a54e05eed0df0e230bfa1c9cec01929252a2
//这里显示监控的主节点信息
8685:X 06 Mar 2020 16:02:11.455 # +monitor master mymaster 127.0.0.1 16379 quorum 2
//监控的从节点的信息
8685:X 06 Mar 2020 16:02:11.458 * +slave slave 127.0.0.1:26379 127.0.0.1 26379 @ mymaster 127.0.0.1 16379
8685:X 06 Mar 2020 16:02:11.459 * +slave slave 127.0.0.1:36379 127.0.0.1 36379 @ mymaster 127.0.0.1 16379
//当另外两个哨兵节点启动后,也会加入到该哨兵节点的配置中去
8685:X 06 Mar 2020 16:02:17.138 * +sentinel sentinel 9fd6cb78ef74b46530f9ce74c7d7abf53a606c12 127.0.0.1 26380 @ mymaster 127.0.0.1 16379
8685:X 06 Mar 2020 16:02:19.946 * +sentinel sentinel 1a47466bf6142d00039cf6983ba1e860db4cfe00 127.0.0.1 36380 @ mymaster 127.0.0.1 16379
MacBook-Pro:redis-5.0.7 root# tail -200f sentinel-26380.log
8686:X 06 Mar 2020 16:02:15.086 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
8686:X 06 Mar 2020 16:02:15.086 # Redis version=5.0.5, bits=64, commit=00000000, modified=0, pid=8686, just started
8686:X 06 Mar 2020 16:02:15.086 # Configuration loaded
8687:X 06 Mar 2020 16:02:15.089 * Increased maximum number of open files to 10032 (it was originally set to 4864).
8687:X 06 Mar 2020 16:02:15.091 * Running mode=sentinel, port=26380.
8687:X 06 Mar 2020 16:02:15.092 # Sentinel ID is 9fd6cb78ef74b46530f9ce74c7d7abf53a606c12
8687:X 06 Mar 2020 16:02:15.092 # +monitor master mymaster 127.0.0.1 16379 quorum 2
8687:X 06 Mar 2020 16:02:15.094 * +slave slave 127.0.0.1:26379 127.0.0.1 26379 @ mymaster 127.0.0.1 16379
8687:X 06 Mar 2020 16:02:15.095 * +slave slave 127.0.0.1:36379 127.0.0.1 36379 @ mymaster 127.0.0.1 16379
8687:X 06 Mar 2020 16:02:15.503 * +sentinel sentinel 1188a54e05eed0df0e230bfa1c9cec01929252a2 127.0.0.1 16380 @ mymaster 127.0.0.1 16379
8687:X 06 Mar 2020 16:02:19.946 * +sentinel sentinel 1a47466bf6142d00039cf6983ba1e860db4cfe00 127.0.0.1 36380 @ mymaster 127.0.0.1 16379
MacBook-Pro:redis-5.0.7 root# tail -200f sentinel-36380.log
8688:X 06 Mar 2020 16:02:17.917 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
8688:X 06 Mar 2020 16:02:17.917 # Redis version=5.0.5, bits=64, commit=00000000, modified=0, pid=8688, just started
8688:X 06 Mar 2020 16:02:17.917 # Configuration loaded
8689:X 06 Mar 2020 16:02:17.920 * Increased maximum number of open files to 10032 (it was originally set to 4864).
8689:X 06 Mar 2020 16:02:17.921 * Running mode=sentinel, port=36380.
8689:X 06 Mar 2020 16:02:17.922 # Sentinel ID is 1a47466bf6142d00039cf6983ba1e860db4cfe00
8689:X 06 Mar 2020 16:02:17.922 # +monitor master mymaster 127.0.0.1 16379 quorum 2
8689:X 06 Mar 2020 16:02:17.923 * +slave slave 127.0.0.1:26379 127.0.0.1 26379 @ mymaster 127.0.0.1 16379
8689:X 06 Mar 2020 16:02:17.924 * +slave slave 127.0.0.1:36379 127.0.0.1 36379 @ mymaster 127.0.0.1 16379
8689:X 06 Mar 2020 16:02:19.151 * +sentinel sentinel 9fd6cb78ef74b46530f9ce74c7d7abf53a606c12 127.0.0.1 26380 @ mymaster 127.0.0.1 16379
8689:X 06 Mar 2020 16:02:19.554 * +sentinel sentinel 1188a54e05eed0df0e230bfa1c9cec01929252a2 127.0.0.1 16380 @ mymaster 127.0.0.1 16379

4.springboot整合Redis Sentinel

1.配置文件

java.redis.pool.max-total=10
java.redis.pool.max-idle=5
java.redis.pool.min-idle=5
# 多个用逗号隔开,host:port,host2:port2
java.redis.sentinel.nodes=127.0.0.1:16380,127.0.0.1:26380,127.0.0.1:36380
java.redis.sentinel.password=123456
java.redis.sentinel.master-name=mymaster
java.redis.sentinel.pool.max-total=10
java.redis.sentinel.pool.max-idle=5
java.redis.sentinel.pool.min-idle=5

2.配置类

@Slf4j
@Component
public class RedisNodeConfig implements InitializingBean {
    @Value("${java.redis.pool.max-total:10}")
    private Integer redisMaxTotal;
    @Value("${java.redis.pool.min-idle}")
    private Integer redisMaxIdle;
    @Value("${java.redis.pool.min-idle}")
    private Integer redisMinIdle;
    @Value("${java.redis.sentinel.nodes}")
    private String redisSentinelNodes;
    @Value("${java.redis.sentinel.password}")
    private String redisSentinelPassword;
    @Value("${java.redis.sentinel.master-name}")
    private String redisSentinelMasterName;
    @Value("${java.redis.sentinel.pool.max-total}")
    private Integer redisSentinelMaxTotal;
    @Value("${java.redis.sentinel.pool.max-idle}")
    private Integer redisSentinelMaxIdle;
    @Value("${java.redis.sentinel.pool.min-idle}")
    private Integer redisSentinelMinIdle;


    private JedisSentinelPool jedisPool;

    @Override
    public void afterPropertiesSet() throws Exception {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(redisMaxTotal);
        jedisPoolConfig.setMaxIdle(redisMaxIdle);
        jedisPoolConfig.setMinIdle(redisMinIdle);
        String[] hosts = redisSentinelNodes.split(",");
        Set<String> sentinels = new HashSet<>(Arrays.asList(hosts));
        GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
        poolConfig.setMaxTotal(redisSentinelMaxTotal);
        poolConfig.setMaxIdle(redisSentinelMaxIdle);
        poolConfig.setMinIdle(redisSentinelMinIdle);
        jedisPool = new JedisSentinelPool(redisSentinelMasterName, sentinels, jedisPoolConfig);
        if (isConnectSuccess()) {
            // 初始化成功
            log.info("++++++ Init Redis Sentinel Pool SUCCESS! ++++++");
        } else {
            // 初始化失败
            log.info("++++++ Init Redis Sentinel Pool FAILURE! ++++++");
        }
    }
    public boolean isConnectSuccess() {
        Jedis jedis = getRedis();
        String ping = jedis.ping();
        recycleRedis(jedis);
        return ping.equals("PONG");
    }
    /**
     * @return 连接池中的Jedis实例
     */
    public Jedis getRedis() {
        return jedisPool.getResource();
    }
    /**
     * 资源回收
     *
     * @param jedis jedis实例
     */
    public void recycleRedis(Jedis jedis) {
        jedis.close();
    }
    
    /**
     * 销毁Redis连接池
     */
    public void close() {
        if (null != jedisPool) {
            jedisPool.close();
        }
    }
}

3.服务类

@Component
public class RedisNodeService implements InitializingBean {
    @Autowired
    private RedisNodeConfig redisNodeConfig;

    private Jedis jedis;

    @Override
    public void afterPropertiesSet() throws Exception {
        jedis = redisNodeConfig.getRedis();
    }

    public void set(String key,String value){
        jedis.set(key,value);
    }
}

4.测试

@Service
@Slf4j
public class TestService{
    @Autowired
    private RedisNodeService redisNodeService;

    public void testRedisService() {
        redisNodeService.set("testnode","testnode");
    }
}

连接redis的主节点:

127.0.0.1:16379> get testnode
"testnode"

5.模拟主节点失效

1.将主节点进程手动杀掉

MacBook-Pro:redis-5.0.7 root# ps -ef|grep 16379
    0  8208     1   0  3:38下午 ??         0:15.52 redis-server 0.0.0.0:16379
    0  9683  6027   0  4:54下午 ttys000    0:00.01 grep 16379
MacBook-Pro:redis-5.0.7 root# kill -9 8208

2.查看sentinel日志

//判断主节点主观下线
8685:X 06 Mar 2020 16:54:16.817 # +sdown master mymaster 127.0.0.1 16379
8685:X 06 Mar 2020 16:54:16.916 # +new-epoch 1
8685:X 06 Mar 2020 16:54:16.917 # +vote-for-leader 9fd6cb78ef74b46530f9ce74c7d7abf53a606c12 1
//超过2个哨兵节点认同主节点失效,主节点变为客观下线
8685:X 06 Mar 2020 16:54:17.887 # +odown master mymaster 127.0.0.1 16379 #quorum 3/2
8685:X 06 Mar 2020 16:54:17.887 # Next failover delay: I will not start a failover before Fri Mar  6 17:00:17 2020
8685:X 06 Mar 2020 16:54:18.045 # +config-update-from sentinel 9fd6cb78ef74b46530f9ce74c7d7abf53a606c12 127.0.0.1 26380 @ mymaster 127.0.0.1 16379
//开始更换主节点为36379
8685:X 06 Mar 2020 16:54:18.045 # +switch-master mymaster 127.0.0.1 16379 127.0.0.1 36379
//然后把原来的从节点26379和原来的主节点16379都变为36379的从节点
8685:X 06 Mar 2020 16:54:18.046 * +slave slave 127.0.0.1:26379 127.0.0.1 26379 @ mymaster 127.0.0.1 36379
8685:X 06 Mar 2020 16:54:18.046 * +slave slave 127.0.0.1:16379 127.0.0.1 16379 @ mymaster 127.0.0.1 36379
8685:X 06 Mar 2020 16:54:23.092 # +sdown slave 127.0.0.1:16379 127.0.0.1 16379 @ mymaster 127.0.0.1 36379

这时候去看redis的配置文件会发现replicaof 127.0.0.1 16379这一行变为了replicaof 127.0.0.1 36379,sentinel的配置文件中的sentinel monitor mymaster 127.0.0.1 16379 2也变为了sentinel monitor mymaster 127.0.0.1 36379 2

3.因为哨兵模式的节点故障转移,现在主节点已经变为了端口号为36379的服务了,再测试下springboot项目是否能正常连接redis,所有的配置项都没有动,

@Service
@Slf4j
public class TestService{
    @Autowired
    private RedisNodeService redisNodeService;

    public void testRedisService() {
        redisNodeService.set("redis-sentinel","redis-sentinel");
    }
}
127.0.0.1:36379> get redis-sentinel
"redis-sentinel"

集群(Redis-Cluster模式)

主从模式下从节点只能分担读压力,不能分担写压力,而且主节点的写能力和存储能力受单机的限制。集群模式可以做到从节点分担写压力,可以扩充主节点的写能力和存储能力。

具体如何实现还未研究

发布了29 篇原创文章 · 获赞 0 · 访问量 570

猜你喜欢

转载自blog.csdn.net/LLstudyJava/article/details/104689458