Redis哨兵机制
Redis主从复制的缺点 主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。 这不是一种推荐的方式,更多时候,我们优先考虑哨兵模式 。
当主机 Master 宕机以后,我们需要人工解决切换,比如使用slaveof no one 。实际上主从复制并没有实现,高可用, 高可用侧重备份机器, 利用集群中系统的冗余,当系统中某台机器发生损坏的时候,其他后备的机器可以迅速的接替它来启动服务。 如果我们有一个监控程序能够监控各个机器的状态及时作出调整,将手动的操作变成自动的。Sentinel的出现就是为了解决这个问题
哨兵模式概述 哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
Redis Sentinel 是一个分布式架构,其中包含若干个 Sentinel 节点和 Redis 数据节点,每个 Sentinel 节点会对数据节点和其余 Sentinel 节点进行监控,当它发现节点不可达时,会对节点做下线标识。如果被标识的是主节点,它还会和其他 Sentinel 节点进行“协商”,当大多数 Sentinel 节点都认为主节点不可达时,它们会选举出一个 Sentinel 节点来完成自动故障转移的工作,同时会将这个变化实时通知给 Redis 应用方。整个过程完全是自动的,不需要人工来介入,所以这套方案很有效地解决了 Redis 的高可用问题
Redis Sentinel的架构
Redis Sentinel的主要功能 这里的哨兵有两个作用
通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式 通知其他的从服务器,修改配置文件,让它们切换主机。
然而一个哨兵进程对Redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了多哨兵模式。
哨兵 的主要功能包括 主节点存活检测、主从运行情况检测、自动故障转移 (failover
)、主从切换。Redis
的 Sentinel
最小配置是 一主一从。
监控 Sentinel
会不断的检查 主服务器 和 从服务器 是否正常运行。
通知 当被监控的某个 Redis
服务器出现问题,Sentinel
通过 API
脚本 向 管理员 或者其他的 应用程序 发送通知。
自动故障转移 当 主节点 不能正常工作时,Sentinel
会开始一次 自动的 故障转移操作,它会将与 失效主节点 是 主从关系 的其中一个 从节点 升级为新的 主节点 ,并且将其他的 从节点 指向 新的主节点 。
配置提供者 在 Redis Sentinel
模式下,客户端应用 在初始化时连接的是 Sentinel
节点集合 ,从中获取 主节点 的信息。
主观下线和客观下线 默认情况下,每个 Sentinel
节点会以 每秒一次 的频率对 Redis
节点和 其它*的 Sentinel
节点发送 PING
命令,并通过节点的回复来判断节点是否在线。
主观下线 主观下线 适用于所有 主节点 和 从节点。如果在 down-after-milliseconds
毫秒内,Sentinel
没有收到 目标节点的有效回复,则会判定 该节点 为 主观下线。
客观下线 客观下线 只适用于 主节点。如果 主节点 出现故障,Sentinel
节点会通过 sentinel is-master-down-by-addr
命令,向其它 Sentinel
节点询问对该节点的 状态判断。如果超过 个数的节点判定 主节点 不可达,则该 Sentinel
节点会判断 主节点 为 客观下线。
Sentinel的通信命令 Sentinel
节点连接一个 Redis
实例的时候,会创建 cmd
和 pub/sub
两个 连接 。Sentinel
通过 cmd
连接给 Redis
发送命令,通过 pub/sub
连接到 Redis
实例上的其他 Sentinel
实例。
Sentinel
与 Redis
主节点 和 从节点 交互的命令,主要包括:
命令
作 用
PING
Sentinel
向 Redis
节点发送 PING
命令,检查节点的状态
INFO
Sentinel
向 Redis
节点发送 INFO
命令,获取它的 从节点信息
PUBLISH
Sentinel
向其监控的 Redis
节点 __sentinel__:hello
这个 channel
发布 自己的信息 及 主节点 相关的配置
SUBSCRIBE
Sentinel
通过订阅 Redis
主节点 和 从节点 的 __sentinel__:hello
这个 channnel
,获取正在监控相同服务的其他 Sentinel
节点
Sentinel
与 Sentinel
交互的命令,主要包括:
命令
作 用
PING
Sentinel
向其他 Sentinel
节点发送 PING
命令,检查节点的状态
SENTINEL:is-master-down-by-addr
和其他 Sentinel
协商 主节点 的状态,如果 主节点 处于 SDOWN
状态,则投票自动选出新的 主节点
Redis Sentinel的工作原理 每个 Sentinel
节点都需要 定期执行 以下任务:
每个 Sentinel
以 每秒钟 一次的频率,向它所知的 主服务器 、从服务器 以及其他 Sentinel
实例 发送一个 PING
命令。
如果一个 实例 (instance
)距离 最后一次 有效回复 PING
命令的时间超过 down-after-milliseconds
所指定的值,那么这个实例会被 Sentinel
标记为 主观下线 。
如果一个 主服务器 被标记为 主观下线 ,那么正在 监视 这个 主服务器 的所有 Sentinel
节点,要以 每秒一次 的频率确认 主服务器 的确进入了 主观下线 状态。
如果一个 主服务器 被标记为 主观下线 ,并且有 足够数量 的 Sentinel
(至少要达到 配置文件 指定的数量)在指定的 时间范围 内同意这一判断,那么这个 主服务器 被标记为 客观下线 。
在一般情况下, 每个 Sentinel
会以每 10
秒一次的频率,向它已知的所有 主服务器 和 从服务器 发送 INFO
命令。当一个 主服务器 被 Sentinel
标记为 客观下线 时,Sentinel
向 下线主服务器 的所有 从服务器 发送 INFO
命令的频率,会从 10
秒一次改为 每秒一次 。
Sentinel
和其他 Sentinel
协商 主节点 的状态,如果 主节点 处于 SDOWN
状态,则投票自动选出新的 主节点 。将剩余的 从节点 指向 新的主节点 进行 数据复制 。
当没有足够数量的 Sentinel
同意 主服务器 下线时, 主服务器 的 客观下线状态 就会被移除。当 主服务器 重新向 Sentinel
的 PING
命令返回 有效回复 时,主服务器 的 主观下线状态 就会被移除。
Redis配置哨兵模式 Redis Sentinel的部署须知
一个稳健的 Redis Sentinel
集群,应该使用至少 三个 Sentinel
实例,并且保证讲这些实例放到 不同的机器 上,甚至不同的 物理区域 。
Sentinel
无法保证 强一致性 。
常见的 客户端应用库 都支持 Sentinel
。
Sentinel
需要通过不断的 测试 和 观察 ,才能保证高可用。
Redis Sentinel的配置文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 # 哨兵sentinel实例运行的端口,默认26379 port 26379 # 哨兵sentinel的工作目录 dir ./ # 哨兵sentinel监控的redis主节点的 ## ip:主机ip地址 ## port:哨兵端口号 ## master-name:可以自己命名的主节点名字(只能由字母A-z、数字0-9 、这三个字符".-_"组成。) ## quorum:当这些quorum个数sentinel哨兵认为master主节点失联 那么这时 客观上认为主节点失联了 # sentinel monitor <master-name> <ip> <redis-port> <quorum> sentinel monitor mymaster 127.0.0.1 6379 2 # 当在Redis实例中开启了requirepass <foobared>,所有连接Redis实例的客户端都要提供密码。 # sentinel auth-pass <master-name> <password> sentinel auth-pass mymaster 123456 # 指定主节点应答哨兵sentinel的最大时间间隔,超过这个时间,哨兵主观上认为主节点下线,默认30秒 # sentinel down-after-milliseconds <master-name> <milliseconds> sentinel down-after-milliseconds mymaster 30000 # 指定了在发生failover主备切换时,最多可以有多少个slave同时对新的master进行同步。这个数字越小,完成failover所需的时间就越长;反之,但是如果这个数字越大,就意味着越多的slave因为replication而不可用。可以通过将这个值设为1,来保证每次只有一个slave,处于不能处理命令请求的状态。 # sentinel parallel-syncs <master-name> <numslaves> sentinel parallel-syncs mymaster 1 # 故障转移的超时时间failover-timeout,默认三分钟,可以用在以下这些方面: ## 1. 同一个sentinel对同一个master两次failover之间的间隔时间。 ## 2. 当一个slave从一个错误的master那里同步数据时开始,直到slave被纠正为从正确的master那里同步数据时结束。 ## 3. 当想要取消一个正在进行的failover时所需要的时间。 ## 4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来同步数据了 # sentinel failover-timeout <master-name> <milliseconds> sentinel failover-timeout mymaster 180000 # 当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本。一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行。 # 对于脚本的运行结果有以下规则: ## 1. 若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10。 ## 2. 若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。 ## 3. 如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。 # sentinel notification-script <master-name> <script-path> sentinel notification-script mymaster /var/redis/notify.sh # 这个脚本应该是通用的,能被多次调用,不是针对性的。 # sentinel client-reconfig-script <master-name> <script-path> sentinel client-reconfig-script mymaster /var/redis/reconfig.sh
Redis Sentinel节点规划 生产环境使用三台服务器搭建redis哨兵集群,3个redis实例(1主2从)+ 3个哨兵实例。生产环境能够保证在哨兵存活两台的情况下,只有一台redis能够继续提供服务(一主两从三哨兵)
主虚拟机1
从虚拟机2
从虚拟机3
172.16.48.129
172.16.48.130
172.16.48.131
Redis Sentinel的配置搭建 软件安装
别在三台机器上通过yum进行redis的下载和安装以及开机启动
1 2 3 4 5 6 7 8 9 10 11 # 添加软件安装源 yum install epel-release # 安装redis yum install redis -y # 启动redis、启动redis哨兵 systemctl start redis systemctl start redis-sentinel # 允许开机启动 systemctl enable redis systemctl enable redis-sentinel # 之后进行配置修改:为哨兵集群,重启启动服务
主库配置
/etc/redis.conf
1 2 3 4 5 6 7 8 9 10 11 bind 172.16.48.129 protected-mode no requirepass "123456789" masterauth "123456789" appendonly yes
两个从库配置
/etc/redis.conf
基本配置和主库相同,bindip地址各自对应各自的。 需要添加主库同步配置
1 2 slaveof 172.16.48.129 6379
哨兵配置
/etc/redis-sentinel.conf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 bind 172.19.131.247 protected-mode no sentinel myid 04d9d3fef5508f60498ac014388571e719188527 sentinel monitor mymaster 172.16.48.129 6379 2 sentinel down-after-milliseconds mymaster 5000 sentinel failover-timeout mymaster 15000 sentinel parallel-syncs mymaster 2 sentinel auth-pass mymaster 123456789
注意:含有mymaster的配置,都必须放置在sentinel monitor mymaster 172.16.48.129 6379 2之后,否则会出现问题
哨兵模式的其他配置项
配置项
参数类型
作用
port
整数
启动哨兵进程端口
dir
文件夹目录
哨兵进程服务临时文件夹,默认为/tmp,要保证有可写入的权限
sentinel down-after-milliseconds
<服务名称><毫秒数(整数)>
指定哨兵在监控Redis服务时,当Redis服务在一个默认毫秒数内都无法回答时,单个哨兵认为的主观下线时间,默认为30000(30秒)
sentinel parallel-syncs
<服务名称><服务器数(整数)>
指定可以有多少个Redis服务同步新的主机,一般而言,这个数字越小同步时间越长,而越大,则对网络资源要求越高
sentinel failover-timeout
<服务名称><毫秒数(整数)>
指定故障切换允许的毫秒数,超过这个时间,就认为故障切换失败,默认为3分钟
sentinel notification-script
<服务名称><脚本路径>
指定sentinel检测到该监控的redis实例指向的实例异常时,调用的报警脚本。该配置项可选,比较常用
sentinel down-after-milliseconds配置项只是一个哨兵在超过规定时间依旧没有得到响应后,会自己认为主机不可用。对于其他哨兵而言,并不是这样认为。哨兵会记录这个消息,当拥有认为主观下线的哨兵达到sentinel monitor所配置的数量时,就会发起一次投票,进行failover,此时哨兵会重写Redis的哨兵配置文件,以适应新场景的需要。
重新启动 1 2 3 4 5 # 启动需要按照Master->Slave->Sentinel的顺序进行启动 # 启动redis systemctl restart redis # 启动redis哨兵 systemctl restart redis-sentinel
高可用测试 连接redis脚本 1 2 3 4 5 6 # 主虚拟机1 redis-cli -h 172.16.48.129 -p 6379 -a 123456789 # 从虚拟机2 redis-cli -h 172.16.48.130 -p 6379 -a 123456789 # 从虚拟机3 redis-cli -h 172.16.48.131 -p 6379 -a 123456789
同步状态查看 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 # 连接完成后输入命令 info replication # 主库显示如下,即可算完成(包含两个从库ip地址) # Replication role:master connected_slaves:2 slave0:ip=172.16.48.131,port=6379,state=online,offset=188041,lag=1 slave1:ip=172.16.48.130,port=6379,state=online,offset=188041,lag=1 master_repl_offset:188041 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:2 repl_backlog_histlen:188040 # 从库显示如下,即可算完成 # Replication role:slave master_host:172.16.48.129 master_port:6379 master_link_status:up master_last_io_seconds_ago:1 master_sync_in_progress:0 slave_repl_offset:174548 slave_priority:100 slave_read_only:1 connected_slaves:0 master_repl_offset:0 repl_backlog_active:0 repl_backlog_size:1048576 repl_backlog_first_byte_offset:0 repl_backlog_histlen:0
主库写入测试同步 1 2 3 4 5 6 7 8 # 主虚拟机1 set b b # 从虚拟机2 keys * get b # 从虚拟机3 keys * get b
从库只读测试 1 2 3 4 5 6 # 从虚拟机2 set c c # result : (error) READONLY You can't write against a read only slave. # 从虚拟机3 set c c # result : (error) READONLY You can' t write against a read only slave.
成功redis-sentinel日志 1 2 # 查看日志: tailf /var/log/redis/sentinel.log
成功日志,+slave slave包含两台从库的地址,+sentinel sentinel包含两台哨兵的id
1 2 3 4 5 6 7 57611:X 21 Oct 02:03:27.777 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128. 57611:X 21 Oct 02:03:27.777 # Sentinel ID is 42975048e2f70d0f4d718f77427930c16bc0b522 57611:X 21 Oct 02:03:27.777 # +monitor master mymaster 172.16.48.129 6379 quorum 2 57611:X 21 Oct 02:03:27.778 * +slave slave 172.16.48.131:6379 172.16.48.131 6379 @ mymaster 172.16.48.129 6379 57611:X 21 Oct 02:03:27.779 * +slave slave 172.16.48.130:6379 172.16.48.130 6379 @ mymaster 172.16.48.129 6379 57611:X 21 Oct 02:03:29.767 * +sentinel sentinel 29222b827e3739b564939c6f20eb610802b48706 172.16.48.130 26379 @ mymaster 172.16.48.129 6379 57611:X 21 Oct 02:03:29.769 * +sentinel sentinel ea3c41804d2840a4393bbdaf0f32dab321267a9c 172.16.48.131 26379 @ mymaster 172.16.48.129 6379
成功sentinel的连接状态 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 # 主虚拟机1 redis-cli -h 172.16.48.129 -p 26379 INFO Sentinel # result: # Sentinel sentinel_masters:1 sentinel_tilt:0 sentinel_running_scripts:0 sentinel_scripts_queue_length:0 sentinel_simulate_failure_flags:0 master0:name=mymaster,status=ok,address=172.16.48.129:6379,slaves=2,sentinels= # 从虚拟机2 redis-cli -h 172.16.48.130 -p 26379 INFO Sentinel # Sentinel sentinel_masters:1 sentinel_tilt:0 sentinel_running_scripts:0 sentinel_scripts_queue_length:0 sentinel_simulate_failure_flags:0 master0:name=mymaster,status=ok,address=172.16.48.129:6379,slaves=2,sentinels=3 # 从虚拟机3 redis-cli -h 172.16.48.131 -p 26379 INFO Sentinel # Sentinel sentinel_masters:1 sentinel_tilt:0 sentinel_running_scripts:0 sentinel_scripts_queue_length:0 sentinel_simulate_failure_flags:0 master0:name=mymaster,status=ok,address=172.16.48.129:6379,slaves=2,sentinels=3
高可用测试case 哨兵作为对redis实例的监控,通过选举算法保证哨兵的鲁棒性和高可用,所以哨兵至少要部署3台,符合半数原则,需要5或者,7,超过一半,不包含一半存活的时候,才能够选举出leader,才能进行主从的切换功能。 redis服务,至少需要存活一台,才能保证服务正常运行sentinel 选择新 master 的原则是最近可用 且 数据最新 且 优先级最高 且 活跃最久 ! 哨兵高可用测试:分别连接对应的redis服务端,手动停止哨兵,停止主reids服务,看主从是否切换成功。
三哨兵情况
redis实例挂掉两台,剩下一台能够成为主,自动切换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # 保持三个哨兵进程都存在的情况下 # 1. 三个终端分别连接redis,使用info replication查看当前连接状态: # 主虚拟机1 redis-cli -h 172.16.48.129 -p 6379 -a 123456789 info replication # 从虚拟机2 redis-cli -h 172.16.48.130 -p 6379 -a 123456789 info replication # 从虚拟机3 redis-cli -h 172.16.48.131 -p 6379 -a 123456789 info replication # 2. 停止当前role:master的对应的redis服务,重新检查状态看是否切换 systemctl stop redis # 虚拟机1的实例转换成为主的redis # 3. 继续停止剩下两台:role:master的对应的redis服务,重新检查连接状态看是否切换 systemctl stop redis # 虚拟机3的实例转换成为了主的redis # 切换顺利,实现高可用
两哨兵情况
redis实例挂掉两台,剩下一台能够成为主,自动切换
1 2 3 4 5 6 # 将全部虚拟机的redis + sentinel重新启动 systemctl start redis systemctl start redis-sentinel # 停止虚拟机1的redis-sentinel,重新执行哨兵的案例测试 systemctl stop redis-sentinel # 分别停止对应master实例的redis,最终剩下一台实例,成为了master,能够自动切换
一哨兵情况
redis实例无法主从切换
1 2 3 4 5 6 # 将全部虚拟机的redis + sentinel重新启动 systemctl start redis systemctl start redis-sentinel # 停止虚拟机1和2d的redis-sentinel,重新执行哨兵的案例测试 systemctl stop redis-sentinel # 分别停止对应master实例的redis,最终剩下一台实例,无法实现主从切换
Java中使用哨兵模式 Jedis 实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class TestSentinels { @SuppressWarnings("resource") @Test public void testSentinel () { JedisPoolConfig jedisPoolConfig = new JedisPoolConfig (); jedisPoolConfig.setMaxTotal(10 ); jedisPoolConfig.setMaxIdle(5 ); jedisPoolConfig.setMinIdle(5 ); Set<String> sentinels = new HashSet <>(Arrays.asList("192.168.11.128:26379" , "192.168.11.129:26379" ,"192.168.11.130:26379" )); JedisSentinelPool pool = new JedisSentinelPool ("mymaster" , sentinels,jedisPoolConfig,"123456" ); Jedis jedis = pool.getResource(); jedis.set("mykey" , "myvalue" ); String value = jedis.get("mykey" ); System.out.println(value); } }
RedisTemplate 实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 <bean id = "poolConfig" class ="redis.clients.jedis.JedisPoolConfig" > <property name ="maxIdle" value ="50" > </property > <property name ="maxTotal" value ="100" > </property > <property name ="maxWaitMillis" value ="20000" > </property > </bean > <bean id ="connectionFactory" class ="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" > <constructor-arg name ="poolConfig" ref ="poolConfig" > </constructor-arg > <constructor-arg name ="sentinelConfig" ref ="sentinelConfig" > </constructor-arg > <property name ="password" value ="123456" > </property > </bean > <bean id ="jdkSerializationRedisSerializer" class ="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" > </bean > <bean id ="stringRedisSerializer" class ="org.springframework.data.redis.serializer.StringRedisSerializer" > </bean > <bean id ="redisTemplate" class ="org.springframework.data.redis.core.RedisTemplate" > <property name ="connectionFactory" ref ="connectionFactory" > </property > <property name ="keySerializer" ref ="stringRedisSerializer" > </property > <property name ="defaultSerializer" ref ="stringRedisSerializer" > </property > <property name ="valueSerializer" ref ="jdkSerializationRedisSerializer" > </property > </bean > <bean id ="sentinelConfig" class ="org.springframework.data.redis.connection.RedisSentinelConfiguration" > <property name ="master" > <bean class ="org.springframework.data.redis.connection.RedisNode" > <property name ="name" value ="mymaster" > </property > </bean > </property > <property name ="sentinels" > <set > <bean class ="org.springframework.data.redis.connection.RedisNode" > <constructor-arg name ="host" value ="192.168.11.128" > </constructor-arg > <constructor-arg name ="port" value ="26379" > </constructor-arg > </bean > <bean class ="org.springframework.data.redis.connection.RedisNode" > <constructor-arg name ="host" value ="192.168.11.129" > </constructor-arg > <constructor-arg name ="port" value ="26379" > </constructor-arg > </bean > <bean class ="org.springframework.data.redis.connection.RedisNode" > <constructor-arg name ="host" value ="192.168.11.130" > </constructor-arg > <constructor-arg name ="port" value ="26379" > </constructor-arg > </bean > </set > </property > </bean >
基本命令操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # 启动 systemctl start redis systemctl start redis-sentinel # 重启 systemctl restart redis systemctl restart redis-sentinel # 停止 systemctl stop redis systemctl stop redis-sentinel # 开机启动 systemctl enable redis systemctl enable redis-sentinel # 关闭开机启动 systemctl disable redis systemctl disable redis-sentinel # 卸载,停止redis服务,sentinel服务之后,关闭开机启动,进行卸载 yum remove redis -y