Redis 的 Sentinel 系统用于管理多个 Redis 服务器(instance), 该系统执行如下三个任务:html
Redis Sentinel 是一个分布式系统, 你能够在一个架构中运行多个 Sentinel 进程(progress), 这些进程使用流言协议(gossip protocols)来接收关于主服务器是否下线的信息, 并使用投票协议(agreement protocols)来决定是否执行自动故障迁移, 以及选择哪一个从服务器做为新的主服务器。事实上,你能够用zk实现一样的功能。java
虽然 Redis Sentinel 释出为一个单独的可执行文件 redis-sentinel , 但实际上它只是一个运行在特殊模式下的 Redis 服务器, 你能够在启动一个普通 Redis 服务器时经过给定 –sentinel 选项来启动 Redis Sentinel 。redis
目前 Sentinel 系统是 Redis 的 unstable 分支的一部分, 你必须到 Redis 项目的 Github 页面 克隆一份 unstable 分支, 而后经过编译来得到 Sentinel 系统。算法
Sentinel 程序能够在编译后的 src 文档中发现, 它是一个命名为 redis-sentinel 的程序。安全
你也能够经过下一节介绍的方法, 让 redis-server 程序运行在 Sentinel 模式之下。服务器
另外, 一个新版本的 Sentinel 已经包含在了 Redis 2.8.0 版本的释出文件中。网络
对于 redis-sentinel 程序, 你能够用如下命令来启动 Sentinel 系统:架构
redis-sentinel /path/to/sentinel.conf
对于 redis-server 程序, 你能够用如下命令来启动一个运行在 Sentinel 模式下的 Redis 服务器:分布式
redis-server /path/to/sentinel.conf --sentinel
两种方法均可以启动一个 Sentinel 实例。spa
启动 Sentinel 实例必须指定相应的配置文件, 系统会使用配置文件来保存 Sentinel 的当前状态, 并在 Sentinel 重启时经过载入配置文件来进行状态还原。
若是启动 Sentinel 时没有指定相应的配置文件, 或者指定的配置文件不可写(not writable), 那么 Sentinel 会拒绝启动。
Redis 源码中包含了一个名为 sentinel.conf 的文件, 这个文件是一个带有详细注释的 Sentinel 配置文件示例。
运行一个 Sentinel 所需的最少配置以下所示:
sentinel monitor mymaster 127.0.0.1 6379 2 sentinel down-after-milliseconds mymaster 60000 sentinel failover-timeout mymaster 180000 sentinel parallel-syncs mymaster 1 sentinel monitor resque 192.168.1.3 6380 4 sentinel down-after-milliseconds resque 10000 sentinel failover-timeout resque 180000 sentinel parallel-syncs resque 5
第一行配置指示 Sentinel 去监视一个名为 mymaster 的主服务器, 这个主服务器的 IP 地址为 127.0.0.1 , 端口号为 6379 , 而将这个主服务器判断为失效至少须要 2 个 Sentinel 赞成 (只要赞成 Sentinel 的数量不达标,自动故障迁移就不会执行)。
不过要注意, 不管你设置要多少个 Sentinel 赞成才能判断一个服务器失效, 一个 Sentinel 都须要得到系统中多数(majority) Sentinel 的支持, 才能发起一次自动故障迁移, 并预留一个给定的配置纪元 (configuration Epoch ,一个配置纪元就是一个新主服务器配置的版本号)。
换句话说, 在只有少数(minority) Sentinel 进程正常运做的状况下, Sentinel 是不能执行自动故障迁移的。
其余选项的基本格式以下:
sentinel <选项的名字> <主服务器的名字> <选项的值>
各个选项的功能以下:
若是服务器在给定的毫秒数以内, 没有返回 Sentinel 发送的 PING 命令的回复, 或者返回一个错误, 那么 Sentinel 将这个服务器标记为主观下线(subjectively down,简称 SDOWN )。
不过只有一个 Sentinel 将服务器标记为主观下线并不必定会引发服务器的自动故障迁移: 只有在足够数量的 Sentinel 都将一个服务器标记为主观下线以后, 服务器才会被标记为客观下线(objectively down, 简称 ODOWN ), 这时自动故障迁移才会执行。
将服务器标记为客观下线所需的 Sentinel 数量由对主服务器的配置决定。
若是从服务器被设置为容许使用过时数据集(参见对 redis.conf 文件中对 slave-serve-stale-data 选项的说明), 那么你可能不但愿全部从服务器都在同一时间向新的主服务器发送同步请求, 由于尽管复制过程的绝大部分步骤都不会阻塞从服务器, 但从服务器在载入主服务器发来的 RDB 文件时, 仍然会形成从服务器在一段时间内不能处理命令请求: 若是所有从服务器一块儿对新的主服务器进行同步, 那么就可能会形成全部从服务器在短期内所有不可用的状况出现。
你能够经过将这个值设为 1 来保证每次只有一个从服务器处于不能处理命令请求的状态。
本文档剩余的内容将对 Sentinel 系统的其余选项进行介绍, 示例配置文件 sentinel.conf 也对相关的选项进行了完整的注释。
前面说过, Redis 的 Sentinel 中关于下线(down)有两个不一样的概念:
若是一个服务器没有在 master-down-after-milliseconds 选项所指定的时间内, 对向它发送 PING 命令的 Sentinel 返回一个有效回复(valid reply), 那么 Sentinel 就会将这个服务器标记为主观下线。
服务器对 PING 命令的有效回复能够是如下三种回复的其中一种:
若是服务器返回除以上三种回复以外的其余回复, 又或者在指定时间内没有回复 PING 命令, 那么 Sentinel 认为服务器返回的回复无效(non-valid)。
注意, 一个服务器必须在 master-down-after-milliseconds 毫秒内, 一直返回无效回复才会被 Sentinel 标记为主观下线。
举个例子, 若是 master-down-after-milliseconds 选项的值为 30000 毫秒(30 秒), 那么只要服务器能在每 29 秒以内返回至少一次有效回复, 这个服务器就仍然会被认为是处于正常状态的。
从主观下线状态切换到客观下线状态并无使用严格的法定人数算法(strong quorum algorithm), 而是使用了流言协议: 若是 Sentinel 在给定的时间范围内, 从其余 Sentinel 那里接收到了足够数量的主服务器下线报告, 那么 Sentinel 就会将主服务器的状态从主观下线改变为客观下线。 若是以后其余 Sentinel 再也不报告主服务器已下线, 那么客观下线状态就会被移除。
客观下线条件只适用于主服务器: 对于任何其余类型的 Redis 实例, Sentinel 在将它们判断为下线前不须要进行协商, 因此从服务器或者其余 Sentinel 永远不会达到客观下线条件。
只要一个 Sentinel 发现某个主服务器进入了客观下线状态, 这个 Sentinel 就可能会被其余 Sentinel 推选出, 并对失效的主服务器执行自动故障迁移操做。
一个 Sentinel 能够与其余多个 Sentinel 进行链接, 各个 Sentinel 之间能够互相检查对方的可用性, 并进行信息交换。
你无须为运行的每一个 Sentinel 分别设置其余 Sentinel 的地址, 由于 Sentinel 能够经过发布与订阅功能来自动发现正在监视相同主服务器的其余 Sentinel , 这一功能是经过向频道 sentinel:hello 发送信息来实现的。
与此相似, 你也没必要手动列出主服务器属下的全部从服务器, 由于 Sentinel 能够经过询问主服务器来得到全部从服务器的信息。
在默认状况下, Sentinel 使用 TCP
端口 26379 (普通 Redis 服务器使用的是 6379 )。
Sentinel 接受 Redis 协议格式的命令请求, 因此你可使用 redis-cli 或者任何其余 Redis 客户端来与 Sentinel 进行通信。
有两种方式能够和 Sentinel 进行通信:
如下列出的是 Sentinel 接受的命令:
客户端能够将 Sentinel 看做是一个只提供了订阅功能的 Redis 服务器: 你不可使用 PUBLISH 命令向这个服务器发送信息, 但你能够用 SUBSCRIBE 命令或者 PSUBSCRIBE 命令, 经过订阅给定的频道来获取相应的事件提醒。
一个频道可以接收和这个频道的名字相同的事件。 好比说, 名为 +sdown 的频道就能够接收全部实例进入主观下线(SDOWN)状态的事件。
经过执行 PSUBSCRIBE * 命令能够接收全部事件信息。注意, 当信息中包含 instance details 字样时, 表示频道所返回的信息中包含了如下用于识别目标实例的内容:
<instance-type> <name> <ip> <port> @ <master-name> <master-ip> <master-port>
@ 字符以后的内容用于指定主服务器, 这些内容是可选的, 它们仅在 @ 字符以前的内容指定的实例不是主服务器时使用。
能够订阅的频道以下:
一次故障转移操做由如下步骤组成:
SLAVEOF NO ONE
命令,让它转变为主服务器。每当一个 Redis 实例被从新配置(reconfigured) —— 不管是被设置成主服务器、从服务器、又或者被设置成其余主服务器的从服务器 —— Sentinel 都会向被从新配置的实例发送一个 CONFIG REWRITE 命令, 从而确保这些配置会持久化在硬盘里。
Sentinel 使用如下规则来选择新的主服务器:
关于sentinel leader选举和failover的详细过程,参见以下,
1) Leader选举:
其实在sentinels故障转移中,仍然须要一个“Leader”来调度整个过程:master的选举以及slave的重配置和同步。当集群中有多个sentinel实例时,如何选举其中一个sentinel为leader呢?
在配置文件中“can-failover”“quorum”参数,以及“is-master-down-by-addr”指令配合来完成整个过程。
A) “can-failover”用来代表当前sentinel是否能够参与“failover”过程,若是为“YES”则代表它将有能力参与“Leader”的选举,不然它将做为“Observer”,observer参与leader选举投票但不能被选举;
B) “quorum”不只用来控制master ODOWN状态确认,同时还用来选举leader时最小“赞同票”数;
C) “is-master-down-by-addr”,在上文中以及提到,它能够用来检测“ip + port”的master是否已经处于SDOWN状态,不过此指令不只可以得到master是否处于SDOWN,同时它还额外的返回当前sentinel本地“投票选举”的Leader信息(runid);
每一个sentinel实例都持有其余的sentinels信息,在Leader选举过程当中(当为leader的sentinel实例失效时,有可能master server并没失效,注意分开理解),sentinel实例将从全部的sentinels集合中去除“can-failover = no”和状态为SDOWN的sentinels,在剩余的sentinels列表中按照runid按照“字典”顺序排序后,取出runid最小的sentinel实例,并将它“投票选举”为Leader,并在其余sentinel发送的“is-master-down-by-addr”指令时将推选的runid追加到响应中。每一个sentinel实例都会检测“is-master-down-by-addr”的响应结果,若是“投票选举”的leader为本身,且状态正常的sentinels实例中,“赞同者”的本身的sentinel个数不小于(>=) 50% + 1,且不小与<quorum>,那么此sentinel就会认为选举成功且leader为本身。
在sentinel.conf文件中,咱们指望有足够多的sentinel实例配置“can-failover yes”,这样可以确保当leader失效时,可以选举某个sentinel为leader,以便进行failover。若是leader没法产生,好比较少的sentinels实例有效,那么failover过程将没法继续.
2) failover过程:
在Leader触发failover以前,首先wait数秒(随即0~5),以便让其余sentinel实例准备和调整(有可能多个leader??),若是一切正常,那么leader就须要开始将一个salve提高为master,此slave必须为状态良好(不能处于SDOWN/ODOWN状态)且权重值最低(redis.conf中)的,当master身份被确认后,开始failover
A)“+failover-triggered”: Leader开始进行failover,此后紧跟着“+failover-state-wait-start”,wait数秒。
B)“+failover-state-select-slave”: Leader开始查找合适的slave
C)“+selected-slave”: 已经找到合适的slave
D) “+failover-state-sen-slaveof-noone”: Leader向slave发送“slaveof no one”指令,此时slave已经完成角色转换,此slave即为master
E) “+failover-state-wait-promotition”: 等待其余sentinel确认slave
F)“+promoted-slave”:确认成功
G)“+failover-state-reconf-slaves”: 开始对slaves进行reconfig操做。
H)“+slave-reconf-sent”:向指定的slave发送“slaveof”指令,告知此slave跟随新的master
I)“+slave-reconf-inprog”: 此slave正在执行slaveof + SYNC过程,如过slave收到“+slave-reconf-sent”以后将会执行slaveof操做。
J)“+slave-reconf-done”: 此slave同步完成,此后leader能够继续下一个slave的reconfig操做。循环G)
K)“+failover-end”: 故障转移结束
L)“+switch-master”:故障转移成功后,各个sentinel实例开始监控新的master。
Sentinel 自动故障迁移使用 Raft 算法来选举领头(leader) Sentinel , 从而确保在一个给定的纪元(epoch)里, 只有一个领头产生。
这表示在同一个纪元中, 不会有两个 Sentinel 同时被选中为领头, 而且各个 Sentinel 在同一个纪元中只会对一个领头进行投票。
更高的配置纪元老是优于较低的纪元, 所以每一个 Sentinel 都会主动使用更新的纪元来代替本身的配置。
简单来讲, 咱们能够将 Sentinel 配置看做是一个带有版本号的状态。 一个状态会以最后写入者胜出(last-write-wins)的方式(也便是,最新的配置老是胜出)传播至全部其余 Sentinel 。
举个例子, 当出现网络分割(network partitions)时, 一个 Sentinel 可能会包含了较旧的配置, 而当这个 Sentinel 接到其余 Sentinel 发来的版本更新的配置时, Sentinel 就会对本身的配置进行更新。
若是要在网络分割出现的状况下仍然保持一致性, 那么应该使用 min-slaves-to-write 选项, 让主服务器在链接的从实例少于给定数量时中止执行写操做, 与此同时, 应该在每一个运行 Redis 主服务器或从服务器的机器上运行 Redis Sentinel 进程。
Sentinel 的状态会被持久化在 Sentinel 配置文件里面。
每当 Sentinel 接收到一个新的配置, 或者当领头 Sentinel 为主服务器建立一个新的配置时, 这个配置会与配置纪元一块儿被保存到磁盘里面。
这意味着中止和重启 Sentinel 进程都是安全的。
即便没有自动故障迁移操做在进行, Sentinel 总会尝试将当前的配置设置到被监视的实例上面。 特别是:
不过, 在以上这些条件知足以后, Sentinel 在对实例进行从新配置以前仍然会等待一段足够长的时间, 确保能够接收到其余 Sentinel 发来的配置更新, 从而避免自身由于保存了过时的配置而对实例进行了没必要要的从新配置。
Redis Sentinel 严重依赖计算机的时间功能: 好比说, 为了判断一个实例是否可用, Sentinel 会记录这个实例最后一次相应 PING 命令的时间, 并将这个时间和当前时间进行对比, 从而知道这个实例有多长时间没有和 Sentinel 进行任何成功通信。
不过, 一旦计算机的时间功能出现故障, 或者计算机很是忙碌, 又或者进程由于某些缘由而被阻塞时, Sentinel 可能也会跟着出现故障。
TILT 模式是一种特殊的保护模式: 当 Sentinel 发现系统有些不对劲时, Sentinel 就会进入 TILT 模式。
由于 Sentinel 的时间中断器默认每秒执行 10 次, 因此咱们预期时间中断器的两次执行之间的间隔为 100 毫秒左右。 Sentinel 的作法是, 记录上一次时间中断器执行时的时间, 并将它和这一次时间中断器执行的时间进行对比:
当 Sentinel 进入 TILT 模式时, 它仍然会继续监视全部目标, 可是:
若是 TILT 能够正常维持 30 秒钟, 那么 Sentinel 退出 TILT 模式。
当 Lua 脚本的运行时间超过指定时限时, Redis 就会返回 -BUSY 错误。
当出现这种状况时, Sentinel 在尝试执行故障转移操做以前, 会先向服务器发送一个 SCRIPT KILL 命令, 若是服务器正在执行的是一个只读脚本的话, 那么这个脚本就会被杀死, 服务器就会回到正常状态。
附:
一.Sentinel.conf详解
##sentinel实例之间的通信端口 ##redis-0 port 26379 ##sentinel须要监控的master信息:<mastername> <masterIP> <masterPort> <quorum> ##<quorum>应该小于集群中slave的个数,只有当至少<quorum>个sentinel实例提交"master失效" ##才会认为master为O_DWON("客观"失效) sentinel monitor def_master 127.0.0.1 6379 2 sentinel auth-pass def_master 012_345^678-90 ##master被当前sentinel实例认定为“失效”的间隔时间 ##若是当前sentinel与master直接的通信中,在指定时间内没有响应或者响应错误代码,那么 ##当前sentinel就认为master失效(SDOWN,“主观”失效) ##<mastername> <millseconds> ##默认为30秒 sentinel down-after-milliseconds def_master 30000 ##当前sentinel实例是否容许实施“failover”(故障转移) ##no表示当前sentinel为“观察者”(只参与"投票".不参与实施failover), ##全局中至少有一个为yes sentinel can-failover def_master yes ##当新master产生时,同时进行“slaveof”到新master并进行“SYNC”的slave个数。 ##默认为1,建议保持默认值 ##在salve执行salveof与同步时,将会终止客户端请求。 ##此值较大,意味着“集群”终止客户端请求的时间总和和较大。 ##此值较小,意味着“集群”在故障转移期间,多个salve向客户端提供服务时仍然使用旧数据。 sentinel parallel-syncs def_master 1 ##failover过时时间,当failover开始后,在此时间内仍然没有触发任何failover操做, ##当前sentinel将会认为这次failoer失败。 sentinel failover-timeout def_master 900000 ##当failover时,能够指定一个“通知”脚本用来告知系统管理员,当前集群的状况。 ##脚本被容许执行的最大时间为60秒,若是超时,脚本将会被终止(KILL) ##脚本执行的结果: ## 1 -> 稍后重试,最大重试次数为10; ## 2 -> 执行结束,无需重试 ##sentinel notification-script mymaster /var/redis/notify.sh ##failover以后重配置客户端,执行脚本时会传递大量参数,请参考相关文档 # sentinel client-reconfig-script <master-name> <script-path>
更详细信息,请参考src/sentinel.c源码 .配置文件加载过程参见方法:sentinelHandlerConfiguration(..)
2、Jedis客户端与Setinel
Java代码
Set<String> sentinels = new HashSet<String>(16); sentinels.add("127.0.0.1:26379");//集群中全部sentinels的地址 sentinels.add("127.0.0.1:26479"); sentinels.add("127.0.0.1:26579"); GenericObjectPoolConfig config = new GenericObjectPoolConfig(); config.setMaxTotal(32); //setinel客户端提供了master自动发现功能 JedisSentinelPool jedisSentinelPool = new JedisSentinelPool("def_master", sentinels,config, "012_345^678-90"); Jedis jedis = jedisSentinelPool.getResource(); try{ // jedis.set("key","value"); } finally { jedisSentinelPool.returnResource(jedis); } jedisSentinelPool.close();
JedisSentinelPool将遍历sentinels列表,并尝试与每一个sentinel创建链接直到成功为止;若是创建链接成功,就向此sentinel发送“get-master-addr-by-name”指令,获取master的位置,此后将创建与master的链接。此后JedisSentinelPool还会启动一个监测线程,用于监测master的存活状况,若是master角色迁移,则从新获取新的master地址,并从新初始化链接池。注意JedisSentinelPool并无提供“M-S”下读写分离的设计,即读写操做均在master上发生。不过很遗憾,我以为仍是须要一种客户端解决方案,可以实现读写分离。