6.redis sentinel实现高可用读写分离

1. redis sentinel

image

故障转移的基本原理:

  • 多个sentinel发现并确认master有问题
  • 选举出一个sentinel作为领导
  • 选出一个slave称为新的master的slave
  • 通知其他的slave称为新的master的slave
  • 通知客户端主从变化
  • 等待老的master复活称为新的master的slave

也支持多个master-slave结构:

image

2. 安装与配置

  1. 配置开启主从节点
  2. 配置开启sentinel监控主节点(sentinel是特殊的redis)
  3. 实际应该多台机器,但是演示方便,只用一台机器来搭建
  4. 详细配置节点

本地安装的结构图:

image

对于master:redis-7000.conf配置:

image

port 7000
daemonize yes
pidfile /usr/local/redis/data/redis-7000.pid
logfile "7000.log"
dir "/usr/local/redis/data"

对于slave:redis-7001和redis-7002配置:

image

port 7001
daemonize yes
pidfile /usr/local/redis/data/redis-7001.pid
logfile "7001.log"
dir "/usr/local/redis/data"
slaveof 127.0.0.1 7000

启动redis服务:

redis-server ../config/redis-7000.conf

访问7000端口的master redis:

redis-cli -p 7000 info replication

显示他有两个从节点:

# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=7002,state=online,offset=99550,lag=1
slave1:ip=127.0.0.1,port=7001,state=online,offset=99816,lag=0
master_repl_offset:99816
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:2
repl_backlog_histlen:99815

对于sentinel主要配置:

image

master sentinel config:

port 26379
daemonize yes
dir "/usr/local/redis/data"
logfile "26379.log"
sentinel monitor mymaster 127.0.0.1 7000 2
...

启动redis sentinel:

redis-sentinel ../config/redis-sentinel-26379.conf

访问26379 redis sentinel master:

redis-cli -p 26379 info sentinel

显示:

# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
master0:name=mymaster,status=ok,address=127.0.0.1:7000,slaves=2,sentinels=3
查看这六个进程是否都起来了:ps -ef | grep redis

注意,如果上面是配置在虚拟机的话,需要将127.0.0.1改为虚拟机的ip,要不然找不着。

3. 故障转移演练

3.1 java客户端程序

JedisSentinelPool只是一个配置中心,不需要具体连接某个redis,注意它不是代理。

private Logger logger = LoggerFactory.getLogger(AppTest.class);

@Test
public void test4(){
    //哨兵配置,我们访问redis,就通过sentinel来访问
    String masername = "mymaster";
    Set<String> sentinels = new HashSet<>();
    sentinels.add("10.128.24.176:26379");
    sentinels.add("10.128.24.176:26380");
    sentinels.add("10.128.24.176:26381");

    JedisSentinelPool sentinelPool = new JedisSentinelPool(masername,sentinels);

    //一个while死循环,每隔一秒往master塞入一个值,并且日志打印
    while (true){
        Jedis jedis = null;
        try{
            jedis = sentinelPool.getResource();

            int index = new Random().nextInt(100000);
            String key = "k-" + index;
            String value = "v-" + index;
            jedis.set(key,value);
            logger.info("{}  value is {}",key,jedis.get(key));

            TimeUnit.MILLISECONDS.sleep(1000);
        }catch (Exception e){
            logger.error(e.getMessage(),e);
        }finally {
            if(jedis != null){
                jedis.close();
            }
        }
    }
}

maven依赖是:

<!--jedis-->
<dependency>
  <groupId>redis.clients</groupId>
  <artifactId>jedis</artifactId>
  <version>2.9.0</version>
</dependency>
<!--slf4j日志接口-->
<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-api</artifactId>
  <version>1.7.6</version>
</dependency>
<!--logback日志实现-->
<dependency>
  <groupId>ch.qos.logback</groupId>
  <artifactId>logback-classic</artifactId>
  <version>1.1.1</version>
</dependency>

启动程序,发现是正常写入:

16:16:01.424 [main] INFO  com.njupt.swg.AppTest - k-54795  value is v-54795
16:16:02.426 [main] INFO  com.njupt.swg.AppTest - k-55630  value is v-55630
16:16:03.429 [main] INFO  com.njupt.swg.AppTest - k-70642  value is v-70642
16:16:04.430 [main] INFO  com.njupt.swg.AppTest - k-42978  value is v-42978
16:16:05.431 [main] INFO  com.njupt.swg.AppTest - k-96297  value is v-96297
16:16:06.433 [main] INFO  com.njupt.swg.AppTest - k-4220  value is v-4220
16:16:07.435 [main] INFO  com.njupt.swg.AppTest - k-34103  value is v-34103
16:16:08.436 [main] INFO  com.njupt.swg.AppTest - k-9177  value is v-9177
16:16:09.437 [main] INFO  com.njupt.swg.AppTest - k-24389  value is v-24389
16:16:10.439 [main] INFO  com.njupt.swg.AppTest - k-32325  value is v-32325
16:16:11.440 [main] INFO  com.njupt.swg.AppTest - k-68538  value is v-68538
16:16:12.441 [main] INFO  com.njupt.swg.AppTest - k-36233  value is v-36233
16:16:13.443 [main] INFO  com.njupt.swg.AppTest - k-305  value is v-305
16:16:14.444 [main] INFO  com.njupt.swg.AppTest - k-59279  value is v-59279

我们将现在的端口为7000的redis master 给kill掉

kill -9 master的pid

我们会发现:客户端报异常,但是在大概十几秒之后,就继续正常塞值了。原因是服务端的哨兵机制的选举matser需要一定的时间。

4. 三个定时任务

4.1 每10秒每个sentinel对master和slave执行Info

  • 发现slave节点
  • 确认主从关系

image

4.2 每2秒每个sentinel通过master节点的channel交换信息(pub/sub)

  • 通过sentinel:hello进行频道交互
  • 交互对节点的“看法”和自身信息

image

4.3 每1秒每个sentinel对其他sentinel和redis执行ping

  • 心跳监测,失败判定依据

image

5. 主观下线和客观下线

对于之前的Sentinel配置文件中有两条配置:

监控master redis节点,这里是当超过两个sentinel认为master挂了,则认为master挂了。

sentinel monitor
sentinel monitor mymaster 127.0.0.1 6379 2

这里是每秒sentinel都回去Ping周围的master redis,超过30秒没有任何相应,说明其挂了。

sentinel down-after-milliseconds
sentinel down-after-milliseconds mymaster 300000

5.1 主观下线

主观下线:每个sentinel节点对Redis节点失败的“偏见”

这是一种主观下线。因为在复杂的网络环境下,这个sentinel与这个master不通,但是master与其他的sentinel都是通的呢?所以是一种“偏见”

这是依靠的第三种定时:每秒去ping一下周围的sentinel和redis。对于slave redis,可以使用这个主观下线,因为他不需要进行故障转移。

5.2 客观下线

客观下线:所有sentinel节点对master Redis节点失败“达成共识”(超过quorum个则统一)

这是依靠的第二种定时:每两秒,sentinel之间进行“商量”,传递的消息是:sentinel is-master-down-by-addr

对于master redis的下线,必须要达成共识才可以,因为涉及故障转移,仅仅依靠一个sentinel判断是不够的。

6. 领导者选举

原因:只有一个sentinel节点完成故障转移

选举:通过sentinel is-master-down-by-addr命令都希望成为领导者

  • 每个做主观下线的sentinel节点向其他sentinel节点发送命令,要求将它设置为领导者
  • 收到命令的sentinel节点如果没有同意通过其他semtinel节点发送的命令,那么将同意该请求,否则拒绝
  • 如果该sentinel节点发现自己的票数已经超过sentinel集合半数并且超过quorum,那么它将成为领导者。
  • 如果此过程中多个sentinel节点成为了领导者,那么将等待一段时间重新进行选举

7. 故障转移

  • 从slave节点中选出一个“合适的”节点作为新的master节点
  • 对上述的slave节点执行“slaveof no one”命令使其成为master节点
  • 向剩余的slave节点发送命令,让它们成为新master节点的slave节点,复制规则和parallel-syncs参数一样
  • 更新对原来的master节点配置为slave,并保持着对其“关注”,当恢复后命令他去复制新的master节点

那么,如何选择“合适”的slave节点呢?

  • 选择slave-priority(slave节点优先级)最高的slave节点,如果存在则返回,不存在则继续。
  • 选择复制偏移量对打的slave节点(复制得最完整),如果存在则返回,不存在则继续
  • 选择run_id最小的slave节点(最早的节点)

8. 节点下线

主节点下线:sentinel failover

从节点下线要注意读写分离问题。

9. 总结与思考

redis sentinel是redis高可用实现方案:故障发现、故障自动转移、配置中心、客户端通知。

redis sentinel从redis2.8版本才正式生产可用,之前版本不可生产用。

尽可能在不同物理机上部署redis sentinel所有节点。

redis sentinel中的sentinel节点个数应该大于等于3且最好是奇数。

redis sentinel中的数据节点和普通数据节点没有区别。每个sentinel节点在本质上还是一个redis实例,只不过和redis数据节点不同的是,其主要作用是监控redis数据节点

客户端初始化时连接的是sentinel节点集合,不再是具体的redis节点,但sentinel只是配置中心不是代理。

redis sentinel通过三个定时任务实现了sentinel节点对于主节点、从节点、其余sentinel节点的监控。

redis sentinel在对节点做失败判定时分为主观下线和客观下线。

看懂redis sentinel故障转移日志对于redis sentinel以及问题排查非常有用。

redis sentinel实现读写分离高可用可以依赖sentinel节点的消息通知,获取redis数据节点的状态变化。

redis sentinel可以实现高可用的读写分离,高可用体现在故障转移,那么实现高可用的基础就是要有从节点,主从节点还实现了读写分离,减少master的压力。但是如果是从节点下线了,sentinel是不会对其进行故障转移的,并且连接从节点的客户端也无法获取到新的可用从节点,而这些问题在Cluster中都得到了有效的解决。

对于性能提高、容量扩展的时候,这种方式是比较复杂的,比较推荐的是使用集群,就是下面讨论的redis cluster!

猜你喜欢

转载自blog.csdn.net/sunweiguo1/article/details/80303565