Redis Sentinel配置小记

Sentinel是一个管理多个redis实例的工具,它可以实现对redis的监控、通知、自动故障转移。sentinel不断的检测redis实例是否可以正常工作,通过API向其他程序报告redis的状态,如果redis master不能工作,则会自动启动故障转移进程,将其中的一个slave提升为master,其他的slave重新设置新的master实例。也就是说,它提供了:
监控(Monitoring): Sentinel 会不断地检查你的主实例和从实例是否正常。
通知(Notification): 当被监控的某个 Redis 实例出现问题时, Sentinel 进程可以通过 API 向管理员或者其他应用程序发送通知。
自动故障迁移(Automatic failover): 当一个主redis实例失效时, Sentinel 会开始记性一次failover, 它会将失效主实例的其中一个从实例升级为新的主实例, 并让失效主实例的其他从实例改为复制新的主实例; 而当客户端试图连接失效的主实例时, 集群也会向客户端返回新主实例的地址, 使得集群可以使用新主实例代替失效实例。
Redis Sentinel自身也是一个分布式系统, 你可以在一个架构中运行多个 Sentinel 进程, 这些进程使用流言协议(gossip protocols)来接收关于主Redis实例是否失效的信息, 然后使用投票协议来决定是否执行自动failover,以及评选出从Redis实例作为新的主Redis实例。

1.启动sentinel的方法

当前Redis stable版已经自带了redis-sentinel这个工具。虽然 Redis Sentinel 已经提供了一个单独的可执行文件 redis-sentinel , 但实际上它只是一个运行在特殊模式下的 Redis实例, 你可以在启动一个普通 Redis实例时通过给定 –sentinel 选项来启动 Redis Sentinel 实例。也就是说:
redis-sentinel /path/to/sentinel.conf
等同于
redis-server /path/to/sentinel.conf --sentinel
其中sentinel.conf是redis的配置文件,Redis sentinel会需要写入配置文件来保存sentinel的当前状态。当配置文件无法写入时,Sentinel启动失败。

2. sentinel的配置

一个简单的sentinel配置文件实例如下:

sentinel 选项的名字 主Redis实例的名字 选项的值
配置文件的格式为:
第一行配置指示 Sentinel 去监视一个名为 mymaster 的主redis实例, 这个主实例的 IP 地址为本机地址127.0.0.1 , 端口号为 6379 , 而将这个主实例判断为失效至少需要 2 个 Sentinel 进程的同意,只要同意 Sentinel 的数量不达标,自动failover就不会执行。同时,一个Sentinel都需要获得系统中大多数Sentinel进程的支持, 才能发起一次自动failover, 并预留一个新主实例配置的编号。而当超过半数Redis不能正常工作时,自动故障转移是无效的。
各个选项的功能如下:
down-after-milliseconds 选项指定了 Sentinel 认为Redis实例已经失效所需的毫秒数。
当实例超过该时间没有返回PING,或者直接返回错误, 那么 Sentinel 将这个实例标记为主观下线(subjectively down,简称 SDOWN )。只有一个 Sentinel进程将实例标记为主观下线并不一定会引起实例的自动故障迁移: 只有在足够数量的 Sentinel 都将一个实例标记为主观下线之后,实例才会被标记为客观下线(objectively down, 简称 ODOWN ), 这时自动故障迁移才会执行。具体的行为如下:
(1). 每个 Sentinel 每秒一次向它所监控的主实例、从实例以及其他 Sentinel 实例发送一个 PING 命令。当一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值, 那么这个实例会被 Sentinel 标记为主观下线。如果一个主实例被标记为主观下线, 并且有足够数量的 Sentinel (至少要达到配置文件指定的数量)在指定的时间范围内同意这一判断, 那么这个主实例被标记为客观下线。
(2).在一般情况下, 每个 Sentinel 进程会以每 10 秒一次的频率向它已知的所有主实例和从实例发送 INFO 命令。 当一个主实例被 Sentinel实例标记为客观下线时, Sentinel 向下线主实例的所有从实例发送 INFO 命令的频率会从 10 秒一次改为每秒一次。
(3).当没有足够数量的 Sentinel 同意主实例已经下线, 主Redis服务实例的客观下线状态就会被移除。 当主服务器重新向 Sentinel 的PING 命令返回有效回复时, 主服务器的主观下线状态就会被移除。
parallel-syncs 选项指定了在执行故障转移时, 最多可以有多少个从Redis实例在同步新的主实例, 在从Redis实例较多的情况下这个数字越小,同步的时间越长,完成故障转移所需的时间就越长。
尽管复制过程的绝大部分步骤都不会阻塞从实例, 但从redis实例在载入主实例发来的 RDB 文件时, 仍然会造成从实例在一段时间内不能处理命令请求: 如果全部从实例一起对新的主实例进行同步, 那么就可能会造成所有从Redis实例在短时间内全部不可用的情况出现。
所以从实例被设置为允许使用过期数据集(参见对 redis.conf 文件中对 slave-serve-stale-data 选项),可以缓解所有从实例都在同一时间向新的主实例发送同步请求的负担。你可以通过将这个值设为 1 来保证每次只有一个从Redis实例处于不能处理命令请求的同步状态。
failover-timeout如果在该时间(ms)内未能完成failover操作,则认为该failover失败。
notification-script: 指定sentinel检测到该监控的redis实例指向的实例异常时,调用的报警脚本。该配置项可选,但是很常用。

3. Sentinel集群的运行机制

一个 Sentinel进程可以与其他多个 Sentinel进程进行连接, 每个 Sentinel进程之间可以互相检查对方的可用性, 并进行信息交换。
和其他集群不同的是,你无须设置其他Sentinel的地址,Sentinel进程可以通过发布与订阅来自动发现正在监视相同主实例的其他Sentinel。同样,你也不必手动列出主实例属下的所有从实例,因为Sentinel实例可以通过询问主实例来获得所有从实例的信息。
每个 Sentinel 都订阅了被它监视的所有主服务器和从服务器的 __sentinel__:hello 频道, 查找之前未出现过的 sentinel进程。 当一个 Sentinel 发现一个新的 Sentinel 时,它会将新的 Sentinel 添加到一个列表中,这个列表保存了 Sentinel 已知的,监视同一个主服务器的所有其他Sentinel。

4. 启动Redis和sentinel

下面的测试环境为ubuntu 14.04 LTS,Redis 2.8.19。下面首先配置一个小型的M/2S环境。

启动后,查看进程和复制状态是否正常:

前面我们了解了,Sentinel可以通过master Redis实例来获得它的从实例的信息。所以每一个Sentinel只配置主实例的监控即可。Sentinel之间端口有所不同。

其中,通知脚本简单的记录一下failover事件。

另外两个端口分别为port 26380。然后通过redis-sentinel命令来启动两个sentinel实例。

通过日志输出可以看出已经成功监控master和两个slave。并和另一个sentinel进程通信。

5. 连接Sentinel和主动failover

在默认情况下,Sentinel 使用TCP端口26379(普通 Redis 服务器使用的是 6379)。Sentinel 接受 Redis 协议格式的命令请求, 所以你可以使用 redis-cli 或者任何其他 Redis 客户端来与 Sentinel 进行通讯。
有两种方式可以和 Sentinel 进行通讯:
第一种方法是通过直接发送命令来查询被监视 Redis 服务器的当前状态, 以及进行主动转移等操作。这些命令包括:
(1). PING :返回 PONG 。

(2). SENTINEL masters :列出所有被监视的主Redis服务实例,以及这些主服务实例的当前状态。SENTINEL slaves :列出给定主服务实例的所有从实例,以及这些从实例的当前状态。

(3). SENTINEL get-master-addr-by-name 返回给定名字的主实例的 IP 地址和端口号。 如果这个主实例正在执行故障转移操作, 或者针对这个主实例的故障转移操作已经完成, 那么这个命令返回新的主服务器的 IP 地址和端口号。

(4). SENTINEL reset : 重置所有名字和给定模式 pattern 相匹配的主服务器。 pattern 参数是一个 Glob 风格的模式。 重置操作清除该sentinel的所保存的所有状态信息,并进行一次重新的发现过程。
127.0.0.1:26379> sentinel reset myredis
(integer) 1
(5). SENTINEL failover :进行一次主动的failover。即在不询问其他 Sentinel 意见的情况下, 强制开始一次自动故障迁移 。发起故障转移的 Sentinel 会向其他 Sentinel 发送一个新的配置,其他 Sentinel 会根据这个配置进行相应的更新。

## master切换到了localhost:6381上。

第二种方法是使用发布与订阅功能, 通过接收 Sentinel 发送的通知: 当执行故障转移操作, 或者某个被监视的实例被判断为主观下线或者客观下线时, Sentinel 就会发送相应的信息。
一个频道能够接收和这个频道的名字相同的事件。 比如说, 名为 +sdown 的频道就可以接收所有实例进入主观下线(SDOWN)状态的事件。
通过执行 PSUBSCRIBE * 命令可以接收所有事件信息。例如:

###此时进行一次主动的failover,各个频道的输出中涉及了新纪元(epoch)通知、投票、选举、新主和新slave的通告、角色转变通知,最终完成了这次主动failover过程。

由此可见,一次failover的过程如下:
一次故障转移操作由以下步骤组成:
(1). 由sentinel主动发起failover或者发现主服务器已经进入客观下线状态。
(2). sentinel对我们的当前纪元(epoch)进行自增,并尝试在这个纪元中当选为此次failover的总指挥。
(3). 如果当选失败, 那么在设定的故障迁移超时时间的两倍之后, 重新尝试当选。 如果当选成功, 那么执行以下步骤。
(4). 选出一个从redis实例,并将它升级为主redis实例。
(5). 向被选中的从redis实例发送 SLAVEOF NO ONE 命令,让它转变为主redis实例。
(6). 通过发布与订阅功能, 将更新后的配置传播给所有其他 Sentinel , 其他 Sentinel 对它们自己的配置进行更新。
(7). 向已下线主服务器的从服务器发送SLAVEOF命令, 让它们去复制新的主服务器。
(8). 当所有从redis实例都已经开始复制新的主redis实例时, 领头Sentinel 终止这次故障迁移操作。

6. 被动failover场景测试

当前状态,主failover为6380

关闭6380Redis实例,等待down-after-milliseconds后,sentinel的日志有如下输出,证明切换成功。

启动6380实例,它会自己作为slave连接行6381这个master节点上。

测试成功。最后需要注意的是,sentinel集群自身也需要多数机制,也就是2个sentinel进程时,挂掉一个另一个就不可用了。

7. Sentinel的客户端

如果要做到应用程序(客户端)对Redis的failover透明Transparent),客户端需要监控sentinel的频道信息,并自动连接新的主节点。官方提供了一个专门的topic来讲解这个问题:Guidelines for Redis clients with support for Redis Sentinel,而一些常用的开发语言也已经有了整合sentinel的redis driver:
Node.JS redis-sentinel-client — Transparent Redis Sentinel client
Python redis 2.10.3
Java Jedis
以Python为例,首先安装redis-py。
pip install redis
一个简单的测试代码如下,首先获得一个Sentinel对象,然后

执行后成功得到bar这个值。

上面的master和slave都是标准的建立好连接的StrictRedis实例,slave则是sentinel查询到的第一个可用的slave。如果正在连接的master不可用时,客户端会先抛出redis.exceptions.ConnectionError异常(此时还没开始failover),然后抛出redis.sentinel.MasterNotFoundError异常(failover进行中),在sentinel正常failover之后,实例正常。所以要开发一个sentinel的高可用客户端,可以参考上面那篇官方的指导。

^^

参考:
http://antirez.com/news/80
http://redis.io/topics/sentinel
http://www.cnblogs.com/Xrinehart/p/3502213.html
http://redis.readthedocs.org/en/latest/topic/sentinel.html
http://www.2cto.com/database/201312/269019.html

Posted in Database, NoSQL, Python|R.