深刻学习Redis(4):哨兵

前言

在 深刻学习Redis(3):主从复制 中曾提到,Redis主从复制的做用有数据热备、负载均衡、故障恢复等;但主从复制存在的一个问题是故障恢复没法自动化。本文将要介绍的哨兵,它基于Redis主从复制,主要做用即是解决主节点故障恢复的自动化问题,进一步提升系统的高可用性。html

文章主要内容以下:首先介绍哨兵的做用和架构;而后讲述哨兵系统的部署方法,以及经过客户端访问哨兵系统的方法;而后简要说明哨兵实现的基本原理;最后给出关于哨兵实践的一些建议。文章内容基于Redis 3.0版本。java

系列文章

深刻学习Redis(1):Redis内存模型redis

深刻学习Redis(2):持久化算法

深刻学习Redis(3):主从复制sql

深刻学习Redis(4):哨兵docker

深刻学习Redis(5):集群安全

目录

1、做用和架构服务器

      1.做用网络

      2. 架构架构

2、部署

      1. 部署主从节点

      2. 部署哨兵节点

      3. 演示故障转移

      4. 总结

3、客户端访问哨兵系统

      1. 代码示例

      2. 客户端原理

      3. 总结

4、基本原理

      1. 哨兵节点支持的命令

      2. 基本原理

5、配置与实践建议

      1. 配置

      2. 实践建议

6、总结

1、做用和架构

1.  做用

在介绍哨兵以前,首先从宏观角度回顾一下Redis实现高可用相关的技术。它们包括:持久化、复制、哨兵和集群,其主要做用和解决的问题是:

  • 持久化:持久化是最简单的高可用方法(有时甚至不被归为高可用的手段),主要做用是数据备份,即将数据存储在硬盘,保证数据不会因进程退出而丢失。
  • 复制:复制是高可用Redis的基础,哨兵和集群都是在复制基础上实现高可用的。复制主要实现了数据的多机备份,以及对于读操做的负载均衡和简单的故障恢复。缺陷:故障恢复没法自动化;写操做没法负载均衡;存储能力受到单机的限制。
  • 哨兵:在复制的基础上,哨兵实现了自动化的故障恢复。缺陷:写操做没法负载均衡;存储能力受到单机的限制。
  • 集群:经过集群,Redis解决了写操做没法负载均衡,以及存储能力受到单机限制的问题,实现了较为完善的高可用方案。

 

下面说回哨兵。

Redis Sentinel,即Redis哨兵,在Redis 2.8版本开始引入。哨兵的核心功能是主节点的自动故障转移。下面是Redis官方文档对于哨兵功能的描述:

  • 监控(Monitoring):哨兵会不断地检查主节点和从节点是否运做正常。
  • 自动故障转移(Automatic failover):当主节点不能正常工做时,哨兵会开始自动故障转移操做,它会将失效主节点的其中一个从节点升级为新的主节点,并让其余从节点改成复制新的主节点。
  • 配置提供者(Configuration provider):客户端在初始化时,经过链接哨兵来得到当前Redis服务的主节点地址。
  • 通知(Notification):哨兵能够将故障转移的结果发送给客户端。

其中,监控和自动故障转移功能,使得哨兵能够及时发现主节点故障并完成转移;而配置提供者和通知功能,则须要在与客户端的交互中才能体现。

这里对“客户端”一词在文章中的用法作一个说明:在前面的文章中,只要经过API访问redis服务器,都会称做客户端,包括redis-cli、Java客户端Jedis等;为了便于区分说明,本文中的客户端并不包括redis-cli,而是比redis-cli更加复杂:redis-cli使用的是redis提供的底层接口,而客户端则对这些接口、功能进行了封装,以便充分利用哨兵的配置提供者和通知功能。

2.  架构

典型的哨兵架构图以下所示:

它由两部分组成,哨兵节点和数据节点:

  • 哨兵节点:哨兵系统由一个或多个哨兵节点组成,哨兵节点是特殊的redis节点,不存储数据。
  • 数据节点:主节点和从节点都是数据节点。

2、部署

这一部分将部署一个简单的哨兵系统,包含1个主节点、2个从节点和3个哨兵节点。方便起见:全部这些节点都部署在一台机器上(局域网IP:192.168.92.128),使用端口号区分;节点的配置尽量简化。

1.  部署主从节点

哨兵系统中的主从节点,与普通的主从节点配置是同样的,并不须要作任何额外配置。下面分别是主节点(port=6379)和2个从节点(port=6380/6381)的配置文件,配置都比较简单,再也不详述。

#redis-6379.conf
port 6379
daemonize yes
logfile "6379.log"
dbfilename "dump-6379.rdb"

#redis-6380.conf
port 6380
daemonize yes
logfile "6380.log"
dbfilename "dump-6380.rdb"
slaveof 192.168.92.128 6379

#redis-6381.conf
port 6381
daemonize yes
logfile "6381.log"
dbfilename "dump-6381.rdb"
slaveof 192.168.92.128 6379

配置完成后,依次启动主节点和从节点:

redis-server redis-6379.conf
redis-server redis-6380.conf
redis-server redis-6381.conf

节点启动后,链接主节点查看主从状态是否正常,以下图所示:

2.  部署哨兵节点

哨兵节点本质上是特殊的Redis节点。

3个哨兵节点的配置几乎是彻底同样的,主要区别在于端口号的不一样(26379/26380/26381),下面以26379节点为例介绍节点的配置和启动方式;配置部分尽可能简化,更多配置会在后面介绍。

#sentinel-26379.conf
port 26379
daemonize yes
logfile "26379.log"
sentinel monitor mymaster 192.168.92.128 6379 2

其中,sentinel monitor mymaster 192.168.92.128 6379 2 配置的含义是:该哨兵节点监控192.168.92.128:6379这个主节点,该主节点的名称是mymaster,最后的2的含义与主节点的故障断定有关:至少须要2个哨兵节点赞成,才能断定主节点故障并进行故障转移。

哨兵节点的启动有两种方式,两者做用是彻底相同的:

redis-sentinel sentinel-26379.conf
redis-server sentinel-26379.conf --sentinel

按照上述方式配置和启动以后,整个哨兵系统就启动完毕了。能够经过redis-cli链接哨兵节点进行验证,以下图所示:能够看出26379哨兵节点已经在监控mymaster主节点(即192.168.92.128:6379),并发现了其2个从节点和另外2个哨兵节点。

此时若是查看哨兵节点的配置文件,会发现一些变化,以26379为例:

其中,dir只是显式声明了数据和日志所在的目录(在哨兵语境下只有日志);known-slave和known-sentinel显示哨兵已经发现了从节点和其余哨兵;带有epoch的参数与配置纪元有关(配置纪元是一个从0开始的计数器,每进行一次领导者哨兵选举,都会+1;领导者哨兵选举是故障转移阶段的一个操做,在后文原理部分会介绍)。

3.  演示故障转移

哨兵的4个做用中,配置提供者和通知须要客户端的配合,本文将在下一章介绍客户端访问哨兵系统的方法时详细介绍。这一小节将演示当主节点发生故障时,哨兵的监控和自动故障转移功能。

(1)首先,使用kill命令杀掉主节点:

(2)若是此时当即在哨兵节点中使用info Sentinel命令查看,会发现主节点尚未切换过来,由于哨兵发现主节点故障并转移,须要一段时间。

(3)一段时间之后,再次在哨兵节点中执行info Sentinel查看,发现主节点已经切换成6380节点。

可是同时能够发现,哨兵节点认为新的主节点仍然有2个从节点,这是由于哨兵在将6380切换成主节点的同时,将6379节点置为其从节点;虽然6379从节点已经挂掉,可是因为哨兵并不会对从节点进行客观下线(其含义将在原理部分介绍),所以认为该从节点一直存在。当6379节点从新启动后,会自动变成6380节点的从节点。下面验证一下。

(4)重启6379节点:能够看到6379节点成为了6380节点的从节点。

(5)在故障转移阶段,哨兵和主从节点的配置文件都会被改写。

对于主从节点,主要是slaveof配置的变化:新的主节点没有了slaveof配置,其从节点则slaveof新的主节点。

对于哨兵节点,除了主从节点信息的变化,纪元(epoch)也会变化,下图中能够看到纪元相关的参数都+1了。

4.  总结

哨兵系统的搭建过程,有几点须要注意:

(1)哨兵系统中的主从节点,与普通的主从节点并无什么区别,故障发现和转移是由哨兵来控制和完成的。

(2)哨兵节点本质上是redis节点。

(3)每一个哨兵节点,只须要配置监控主节点,即可以自动发现其余的哨兵节点和从节点。

(4)在哨兵节点启动和故障转移阶段,各个节点的配置文件会被重写(config rewrite)。

(5)本章的例子中,一个哨兵只监控了一个主节点;实际上,一个哨兵能够监控多个主节点,经过配置多条sentinel monitor便可实现。

3、客户端访问哨兵系统

上一小节演示了哨兵的两大做用:监控和自动故障转移,本小节则结合客户端演示哨兵的另外两个做用:配置提供者和通知。

1.  代码示例

在介绍客户端的原理以前,先以Java客户端Jedis为例,演示一下使用方法:下面代码能够链接咱们刚刚搭建的哨兵系统,并进行各类读写操做(代码中只演示如何链接哨兵,异常处理、资源关闭等未考虑)。

public static void testSentinel() throws Exception {
         String masterName = "mymaster";
         Set<String> sentinels = new HashSet<>();
         sentinels.add("192.168.92.128:26379");
         sentinels.add("192.168.92.128:26380");
         sentinels.add("192.168.92.128:26381");

         JedisSentinelPool pool = new JedisSentinelPool(masterName, sentinels); //初始化过程作了不少工做
         Jedis jedis = pool.getResource();
         jedis.set("key1", "value1");
         pool.close();
}

2.  客户端原理

Jedis客户端对哨兵提供了很好的支持。如上述代码所示,咱们只须要向Jedis提供哨兵节点集合和masterName,构造JedisSentinelPool对象;而后即可以像使用普通redis链接池同样来使用了:经过pool.getResource()获取链接,执行具体的命令。

在整个过程当中,咱们的代码不须要显式的指定主节点的地址,就能够链接到主节点;代码中对故障转移没有任何体现,就能够在哨兵完成故障转移后自动的切换主节点。之因此能够作到这一点,是由于在JedisSentinelPool的构造器中,进行了相关的工做;主要包括如下两点:

(1)遍历哨兵节点,获取主节点信息:遍历哨兵节点,经过其中一个哨兵节点+masterName得到主节点的信息;该功能是经过调用哨兵节点的sentinel get-master-addr-by-name命令实现,该命令示例以下:

一旦得到主节点信息,中止遍历(所以通常来讲遍历到第一个哨兵节点,循环就中止了)。

(2)增长对哨兵的监听:这样当发生故障转移时,客户端即可以收到哨兵的通知,从而完成主节点的切换。具体作法是:利用redis提供的发布订阅功能,为每个哨兵节点开启一个单独的线程,订阅哨兵节点的+switch-master频道,当收到消息时,从新初始化链接池。

3.  总结

经过客户端原理的介绍,能够加深对哨兵功能的理解:

(1)配置提供者:客户端能够经过哨兵节点+masterName获取主节点信息,在这里哨兵起到的做用就是配置提供者。

须要注意的是,哨兵只是配置提供者,而不是代理。两者的区别在于:若是是配置提供者,客户端在经过哨兵得到主节点信息后,会直接创建到主节点的链接,后续的请求(如set/get)会直接发向主节点;若是是代理,客户端的每一次请求都会发向哨兵,哨兵再经过主节点处理请求。

举一个例子能够很好的理解哨兵的做用是配置提供者,而不是代理。在前面部署的哨兵系统中,将哨兵节点的配置文件进行以下修改:

sentinel monitor mymaster 192.168.92.128 6379 2
改成
sentinel monitor mymaster 127.0.0.1 6379 2

而后,将前述客户端代码在局域网的另一台机器上运行,会发现客户端没法链接主节点;这是由于哨兵做为配置提供者,客户端经过它查询到主节点的地址为127.0.0.1:6379,客户端会向127.0.0.1:6379创建redis链接,天然没法链接。若是哨兵是代理,这个问题就不会出现了。

(2)通知:哨兵节点在故障转移完成后,会将新的主节点信息发送给客户端,以便客户端及时切换主节点。

4、基本原理

前面介绍了哨兵部署、使用的基本方法,本部分介绍哨兵实现的基本原理。

1.  哨兵节点支持的命令

哨兵节点做为运行在特殊模式下的redis节点,其支持的命令与普通的redis节点不一样。在运维中,咱们能够经过这些命令查询或修改哨兵系统;不过更重要的是,哨兵系统要实现故障发现、故障转移等各类功能,离不开哨兵节点之间的通讯,而通讯的很大一部分是经过哨兵节点支持的命令来实现的。下面介绍哨兵节点支持的主要命令。

(1)基础查询:经过这些命令,能够查询哨兵系统的拓扑结构、节点信息、配置信息等。

  • info sentinel:获取监控的全部主节点的基本信息
  • sentinel masters:获取监控的全部主节点的详细信息
  • sentinel master mymaster:获取监控的主节点mymaster的详细信息
  • sentinel slaves mymaster:获取监控的主节点mymaster的从节点的详细信息
  • sentinel sentinels mymaster:获取监控的主节点mymaster的哨兵节点的详细信息
  • sentinel get-master-addr-by-name mymaster:获取监控的主节点mymaster的地址信息,前文已有介绍
  • sentinel is-master-down-by-addr:哨兵节点之间能够经过该命令询问主节点是否下线,从而对是否客观下线作出判断

(2)增长/移除对主节点的监控

sentinel monitor mymaster2 192.168.92.128 16379 2:与部署哨兵节点时配置文件中的sentinel monitor功能彻底同样,再也不详述

sentinel remove mymaster2:取消当前哨兵节点对主节点mymaster2的监控

(3)强制故障转移

sentinel failover mymaster:该命令能够强制对mymaster执行故障转移,即使当前的主节点运行无缺;例如,若是当前主节点所在机器即将报废,即可以提早经过failover命令进行故障转移。

2.  基本原理

关于哨兵的原理,关键是了解如下几个概念。

(1)定时任务:每一个哨兵节点维护了3个定时任务。定时任务的功能分别以下:经过向主从节点发送info命令获取最新的主从结构;经过发布订阅功能获取其余哨兵节点的信息;经过向其余节点发送ping命令进行心跳检测,判断是否下线。

(2)主观下线:在心跳检测的定时任务中,若是其余节点超过必定时间没有回复,哨兵节点就会将其进行主观下线。顾名思义,主观下线的意思是一个哨兵节点“主观地”判断下线;与主观下线相对应的是客观下线。

(3)客观下线:哨兵节点在对主节点进行主观下线后,会经过sentinel is-master-down-by-addr命令询问其余哨兵节点该主节点的状态;若是判断主节点下线的哨兵数量达到必定数值,则对该主节点进行客观下线。

须要特别注意的是,客观下线是主节点才有的概念;若是从节点和哨兵节点发生故障,被哨兵主观下线后,不会再有后续的客观下线和故障转移操做。

(4)选举领导者哨兵节点:当主节点被判断客观下线之后,各个哨兵节点会进行协商,选举出一个领导者哨兵节点,并由该领导者节点对其进行故障转移操做。

监视该主节点的全部哨兵都有可能被选为领导者,选举使用的算法是Raft算法;Raft算法的基本思路是先到先得:即在一轮选举中,哨兵A向B发送成为领导者的申请,若是B没有赞成过其余哨兵,则会赞成A成为领导者。选举的具体过程这里不作详细描述,通常来讲,哨兵选择的过程很快,谁先完成客观下线,通常就能成为领导者。

(5)故障转移:选举出的领导者哨兵,开始进行故障转移操做,该操做大致能够分为3个步骤:

  • 在从节点中选择新的主节点:选择的原则是,首先过滤掉不健康的从节点;而后选择优先级最高的从节点(由slave-priority指定);若是优先级没法区分,则选择复制偏移量最大的从节点;若是仍没法区分,则选择runid最小的从节点。
  • 更新主从状态:经过slaveof no one命令,让选出来的从节点成为主节点;并经过slaveof命令让其余节点成为其从节点。
  • 将已经下线的主节点(即6379)设置为新的主节点的从节点,当6379从新上线后,它会成为新的主节点的从节点。

 

经过上述几个关键概念,能够基本了解哨兵的工做原理。为了更形象的说明,下图展现了领导者哨兵节点的日志,包括从节点启动到完成故障转移。

5、配置与实践建议

1.  配置

下面介绍与哨兵相关的几个配置。

(1) sentinel monitor {masterName} {masterIp} {masterPort} {quorum}

sentinel monitor是哨兵最核心的配置,在前文讲述部署哨兵节点时已说明,其中:masterName指定了主节点名称,masterIp和masterPort指定了主节点地址,quorum是判断主节点客观下线的哨兵数量阈值:当断定主节点下线的哨兵数量达到quorum时,对主节点进行客观下线。建议取值为哨兵数量的一半加1。

(2) sentinel down-after-milliseconds {masterName} {time}

sentinel down-after-milliseconds与主观下线的判断有关:哨兵使用ping命令对其余节点进行心跳检测,若是其余节点超过down-after-milliseconds配置的时间没有回复,哨兵就会将其进行主观下线。该配置对主节点、从节点和哨兵节点的主观下线断定都有效。

down-after-milliseconds的默认值是30000,即30s;能够根据不一样的网络环境和应用要求来调整:值越大,对主观下线的断定会越宽松,好处是误判的可能性小,坏处是故障发现和故障转移的时间变长,客户端等待的时间也会变长。例如,若是应用对可用性要求较高,则能够将值适当调小,当故障发生时尽快完成转移;若是网络环境相对较差,能够适当提升该阈值,避免频繁误判。

(3) sentinel parallel-syncs {masterName} {number}

sentinel parallel-syncs与故障转移以后从节点的复制有关:它规定了每次向新的主节点发起复制操做的从节点个数。例如,假设主节点切换完成以后,有3个从节点要向新的主节点发起复制;若是parallel-syncs=1,则从节点会一个一个开始复制;若是parallel-syncs=3,则3个从节点会一块儿开始复制。

parallel-syncs取值越大,从节点完成复制的时间越快,可是对主节点的网络负载、硬盘负载形成的压力也越大;应根据实际状况设置。例如,若是主节点的负载较低,而从节点对服务可用的要求较高,能够适量增长parallel-syncs取值。parallel-syncs的默认值是1。

(4) sentinel failover-timeout {masterName} {time}

sentinel failover-timeout与故障转移超时的判断有关,可是该参数不是用来判断整个故障转移阶段的超时,而是其几个子阶段的超时,例如若是主节点晋升从节点时间超过timeout,或从节点向新的主节点发起复制操做的时间(不包括复制数据的时间)超过timeout,都会致使故障转移超时失败。

failover-timeout的默认值是180000,即180s;若是超时,则下一次该值会变为原来的2倍。

(5)除上述几个参数外,还有一些其余参数,如安全验证相关的参数,这里不作介绍。

2.  实践建议

(1)哨兵节点的数量应不止一个,一方面增长哨兵节点的冗余,避免哨兵自己成为高可用的瓶颈;另外一方面减小对下线的误判。此外,这些不一样的哨兵节点应部署在不一样的物理机上。

(2)哨兵节点的数量应该是奇数,便于哨兵经过投票作出“决策”:领导者选举的决策、客观下线的决策等。

(3)各个哨兵节点的配置应一致,包括硬件、参数等;此外,全部节点都应该使用ntp或相似服务,保证时间准确、一致。

(4)哨兵的配置提供者和通知客户端功能,须要客户端的支持才能实现,如前文所说的Jedis;若是开发者使用的库未提供相应支持,则可能须要开发者本身实现。

(5)当哨兵系统中的节点在docker(或其余可能进行端口映射的软件)中部署时,应特别注意端口映射可能会致使哨兵系统没法正常工做,由于哨兵的工做基于与其余节点的通讯,而docker的端口映射可能致使哨兵没法链接到其余节点。例如,哨兵之间互相发现,依赖于它们对外宣称的IP和port,若是某个哨兵A部署在作了端口映射的docker中,那么其余哨兵使用A宣称的port没法链接到A。

6、总结

本文首先介绍了哨兵的做用:监控、故障转移、配置提供者和通知;而后讲述了哨兵系统的部署方法,以及经过客户端访问哨兵系统的方法;再而后简要说明了哨兵实现的基本原理;最后给出了关于哨兵实践的一些建议。

在主从复制的基础上,哨兵引入了主节点的自动故障转移,进一步提升了Redis的高可用性;可是哨兵的缺陷一样很明显:哨兵没法对从节点进行自动故障转移,在读写分离场景下,从节点故障会致使读服务不可用,须要咱们对从节点作额外的监控、切换操做。

此外,哨兵仍然没有解决写操做没法负载均衡、及存储能力受到单机限制的问题;这些问题的解决须要使用集群,我将在后面的文章中介绍,欢迎关注。

参考文献

https://redis.io/topics/sentinel

http://www.redis.cn/

《Redis开发与运维》

《Redis设计与实现》

 

创做不易,若是文章对你有帮助,就点个赞、评个论呗~

创做不易,若是文章对你有帮助,就点个赞、评个论呗~

创做不易,若是文章对你有帮助,就点个赞、评个论呗~

相关文章
相关标签/搜索