在进入正文以前,顺便在此给你们推荐一个Java架构方面的交流学习群:698581634,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系,主要针对Java开发人员提高本身,突破瓶颈,相信你来学习,会有提高和收获。在这个群里会有你须要的内容 朋友们请抓紧时间加入进来吧。redis
本文将要介绍的哨兵,它基于 Redis 主从复制,主要做用即是解决主节点故障恢复的自动化问题,进一步提升系统的高可用性。算法
文章将首先介绍哨兵的做用和架构;而后讲述哨兵系统的部署方法,以及经过客户端访问哨兵系统的方法;而后简要说明哨兵实现的基本原理;最后给出关于哨兵实践的一些建议。(注:文章内容基于 Redis 3.0 版本)安全
哨兵的做用和架构性能优化
哨兵的做用服务器
在介绍哨兵以前,首先从宏观角度回顾一下 Redis 实现高可用相关的技术。网络
它们包括:持久化、复制、哨兵和集群,其主要做用和解决的问题是:架构
复制主要实现了数据的多机备份,以及对于读操做的负载均衡和简单的故障恢复。缺陷:故障恢复没法自动化;写操做没法负载均衡;存储能力受到单机的限制。并发
下面说回哨兵,Redis Sentinel,即 Redis 哨兵,在 Redis 2.8 版本开始引入。哨兵的核心功能是主节点的自动故障转移。负载均衡
下面是 Redis 官方文档对于哨兵功能的描述:运维
其中,监控和自动故障转移功能,使得哨兵能够及时发现主节点故障并完成转移;而配置提供者和通知功能,则须要在与客户端的交互中才能体现。
这里对“客户端”一词在本文的用法作一个说明:在前面的文章中,只要经过 API 访问 Redis 服务器,都会称做客户端,包括 redis-cli、Java 客户端 Jedis 等。
为了便于区分说明,本文中的客户端并不包括 redis-cli,而是比 redis-cli 更加复杂。
redis-cli 使用的是 Redis 提供的底层接口,而客户端则对这些接口、功能进行了封装,以便充分利用哨兵的配置提供者和通知功能。
哨兵的架构
典型的哨兵架构图以下所示:
它由两部分组成,哨兵节点和数据节点:
哨兵系统的部署方法
这一部分将部署一个简单的哨兵系统,包含 1 个主节点、2 个从节点和 3 个哨兵节点。
方便起见:全部这些节点都部署在一台机器上(局域网 IP:192.168.92.128),使用端口号区分;节点的配置尽量简化。
部署主从节点
哨兵系统中的主从节点,与普通的主从节点配置是同样的,并不须要作任何额外配置。
下面分别是主节点(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
节点启动后,链接主节点查看主从状态是否正常,以下图所示:
部署哨兵节点
哨兵节点本质上是特殊的 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;领导者哨兵选举是故障转移阶段的一个操做,在后文原理部分会介绍)。
演示故障转移
哨兵的四个做用中,配置提供者和通知须要客户端的配合,本文将在下一章介绍客户端访问哨兵系统的方法时再详细介绍。
这一小节将演示当主节点发生故障时,哨兵的监控和自动故障转移功能。
(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 了。
小结
哨兵系统的搭建过程,有几点须要注意:
客户端访问哨兵系统
上一小节演示了哨兵的两大做用:监控和自动故障转移。本小节则结合客户端演示哨兵的另外两个做用:配置提供者和通知。
代码示例
在介绍客户端的原理以前,先以 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(); }
客户端原理
Jedis 客户端对哨兵提供了很好的支持。如上述代码所示,咱们只须要向 Jedis 提供哨兵节点集合和 masterName,构造 JedisSentinelPool 对象。
而后即可以像使用普通 Redis 链接池同样来使用了:经过 pool.getResource() 获取链接,执行具体的命令。
在整个过程当中,咱们的代码不须要显式的指定主节点的地址,就能够链接到主节点;代码中对故障转移没有任何体现,就能够在哨兵完成故障转移后自动的切换主节点。
之因此能够作到这一点,是由于在 JedisSentinelPool 的构造器中,进行了相关的工做;主要包括如下两点:
遍历哨兵节点,获取主节点信息:遍历哨兵节点,经过其中一个哨兵节点 + masterName 得到主节点的信息。
该功能是经过调用哨兵节点的 sentinelget-master-addr-by-name 命令实现,该命令示例以下:
一旦得到主节点信息,中止遍历(所以通常来讲遍历到第一个哨兵节点,循环就中止了)。
增长对哨兵的监听:这样当发生故障转移时,客户端即可以收到哨兵的通知,从而完成主节点的切换。
具体作法是:利用 Redis 提供的发布订阅功能,为每个哨兵节点开启一个单独的线程,订阅哨兵节点的 + switch-master 频道,当收到消息时,从新初始化链接池。
小结
经过客户端原理的介绍,咱们能够加深对哨兵功能的理解。
配置提供者:客户端能够经过哨兵节点 + masterName 获取主节点信息,在这里哨兵起到的做用就是配置提供者。
须要注意的是,哨兵只是配置提供者,而不是代理。两者的区别在于:
举一个例子能够很好的理解哨兵的做用是配置提供者,而不是代理。在前面部署的哨兵系统中,将哨兵节点的配置文件进行以下修改:
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 链接,天然没法链接。若是哨兵是代理,这个问题就不会出现了。
通知:哨兵节点在故障转移完成后,会将新的主节点信息发送给客户端,以便客户端及时切换主节点。
哨兵实现的基本原理
前面介绍了哨兵部署、使用的基本方法,本部分介绍哨兵实现的基本原理。
哨兵节点支持的命令
哨兵节点做为运行在特殊模式下的 Redis 节点,其支持的命令与普通的 Redis 节点不一样。
在运维中,咱们能够经过这些命令查询或修改哨兵系统;不过更重要的是,哨兵系统要实现故障发现、故障转移等各类功能,离不开哨兵节点之间的通讯。
而通讯的很大一部分是经过哨兵节点支持的命令来实现的。下面介绍哨兵节点支持的主要命令。
基础查询
经过这些命令,能够查询哨兵系统的拓扑结构、节点信息、配置信息等:
增长/移除对主节点的监控
sentinel monitor mymaster2 192.168.92.128 16379 2:与部署哨兵节点时配置文件中的 sentinel monitor 功能彻底同样,再也不详述。
sentinel remove mymaster2:取消当前哨兵节点对主节点 mymaster2 的监控。
强制故障转移
sentinel failover mymaster:该命令能够强制对 mymaster 执行故障转移,即使当前的主节点运行无缺。
例如,若是当前主节点所在机器即将报废,即可以提早经过failover命令进行故障转移。
基本原理
关于哨兵的原理,关键是了解如下几个概念。
定时任务
每一个哨兵节点维护了 3 个定时任务,定时任务的功能分别以下:
主观下线
在心跳检测的定时任务中,若是其余节点超过必定时间没有回复,哨兵节点就会将其进行主观下线。
顾名思义,主观下线的意思是一个哨兵节点“主观地”判断下线;与主观下线相对应的是客观下线。
客观下线
哨兵节点在对主节点进行主观下线后,会经过 sentinelis-master-down-by-addr 命令询问其余哨兵节点该主节点的状态。
若是判断主节点下线的哨兵数量达到必定数值,则对该主节点进行客观下线。
须要特别注意的是,客观下线是主节点才有的概念;若是从节点和哨兵节点发生故障,被哨兵主观下线后,不会再有后续的客观下线和故障转移操做。
选举领导者哨兵节点
当主节点被判断客观下线之后,各个哨兵节点会进行协商,选举出一个领导者哨兵节点,并由该领导者节点对其进行故障转移操做。
监视该主节点的全部哨兵都有可能被选为领导者,选举使用的算法是 Raft 算法。
Raft 算法的基本思路是先到先得:即在一轮选举中,哨兵 A 向 B 发送成为领导者的申请,若是 B 没有赞成过其余哨兵,则会赞成 A 成为领导者。
选举的具体过程这里不作详细描述,通常来讲,哨兵选择的过程很快,谁先完成客观下线,通常就能成为领导者。
故障转移
选举出的领导者哨兵,开始进行故障转移操做,该操做大致能够分为 3 个步骤:
若是优先级没法区分,则选择复制偏移量最大的从节点;若是仍没法区分,则选择 runid 最小的从节点。
经过上述几个关键概念,能够基本了解哨兵的工做原理。为了更形象的说明,下图展现了领导者哨兵节点的日志,包括从节点启动到完成故障转移。
哨兵配置与实践建议
哨兵配置
下面介绍与哨兵相关的几个配置。
sentinel monitor {masterName} {masterIp} {masterPort}{quorum}
sentinel monitor 是哨兵最核心的配置,在前文讲述部署哨兵节点时已说明,其中:masterName 指定了主节点名称,masterIp 和 masterPort 指定了主节点地址,quorum 是判断主节点客观下线的哨兵数量阈值。
当断定主节点下线的哨兵数量达到 quorum 时,对主节点进行客观下线。建议取值为哨兵数量的一半加 1。
sentinel down-after-milliseconds {masterName} {time}
sentinel down-after-milliseconds 与主观下线的判断有关:哨兵使用 ping 命令对其余节点进行心跳检测。
若是其余节点超过 down-after-milliseconds 配置的时间没有回复,哨兵就会将其进行主观下线,该配置对主节点、从节点和哨兵节点的主观下线断定都有效。
down-after-milliseconds 的默认值是 30000,即 30s;能够根据不一样的网络环境和应用要求来调整。
值越大,对主观下线的断定会越宽松,好处是误判的可能性小,坏处是故障发现和故障转移的时间变长,客户端等待的时间也会变长。
例如,若是应用对可用性要求较高,则能够将值适当调小,当故障发生时尽快完成转移;若是网络环境相对较差,能够适当提升该阈值,避免频繁误判。
sentinel parallel-syncs {masterName} {number}
sentinel parallel-syncs 与故障转移以后从节点的复制有关:它规定了每次向新的主节点发起复制操做的从节点个数。
例如,假设主节点切换完成以后,有 3 个从节点要向新的主节点发起复制;若是 parallel-syncs=1,则从节点会一个一个开始复制;若是 parallel-syncs=3,则 3 个从节点会一块儿开始复制。
parallel-syncs 取值越大,从节点完成复制的时间越快,可是对主节点的网络负载、硬盘负载形成的压力也越大;应根据实际状况设置。
例如,若是主节点的负载较低,而从节点对服务可用的要求较高,能够适量增长 parallel-syncs 取值。parallel-syncs 的默认值是 1。
sentinel failover-timeout {masterName} {time}
sentinel failover-timeout 与故障转移超时的判断有关,可是该参数不是用来判断整个故障转移阶段的超时,而是其几个子阶段的超时。
例如若是主节点晋升从节点时间超过 timeout,或从节点向新的主节点发起复制操做的时间(不包括复制数据的时间)超过 timeout,都会致使故障转移超时失败。
failover-timeout 的默认值是 180000,即 180s;若是超时,则下一次该值会变为原来的 2 倍。
除上述几个参数外,还有一些其余参数,如安全验证相关的参数,这里不作介绍。
实践建议
哨兵节点的数量应不止一个,一方面增长哨兵节点的冗余,避免哨兵自己成为高可用的瓶颈;另外一方面减小对下线的误判。此外,这些不一样的哨兵节点应部署在不一样的物理机上。
哨兵节点的数量应该是奇数,便于哨兵经过投票作出“决策”:领导者选举的决策、客观下线的决策等。
各个哨兵节点的配置应一致,包括硬件、参数等;此外,全部节点都应该使用 ntp 或相似服务,保证时间准确、一致。
哨兵的配置提供者和通知客户端功能,须要客户端的支持才能实现,如前文所说的 Jedis;若是开发者使用的库未提供相应支持,则可能须要开发者本身实现。
当哨兵系统中的节点在 Docker(或其余可能进行端口映射的软件)中部署时,应特别注意端口映射可能会致使哨兵系统没法正常工做。
由于哨兵的工做基于与其余节点的通讯,而 Docker 的端口映射可能致使哨兵没法链接到其余节点。
例如,哨兵之间互相发现,依赖于它们对外宣称的 IP 和 port,若是某个哨兵 A 部署在作了端口映射的 Docker 中,那么其余哨兵使用 A 宣称的 port 没法链接到 A。
总结
本文首先介绍了哨兵的做用:监控、故障转移、配置提供者和通知;而后讲述了哨兵系统的部署方法,以及经过客户端访问哨兵系统的方法;再而后简要说明了哨兵实现的基本原理;最后给出了关于哨兵实践的一些建议。
在主从复制的基础上,哨兵引入了主节点的自动故障转移,进一步提升了 Redis 的高可用性。
可是哨兵的缺陷一样很明显:哨兵没法对从节点进行自动故障转移,在读写分离场景下,从节点故障会致使读服务不可用,须要咱们对从节点作额外的监控、切换操做。
此外,哨兵仍然没有解决写操做没法负载均衡、及存储能力受到单机限制的问题;这些问题的解决须要使用集群,我将在后面的文章中介绍,欢迎关注。