简介
Redis 的 Sentinel 系统用于管理多个 Redis 服务器(instance),Redis 的 Sentinel 为Redis提供了高可用性。使用哨兵模式创建一个可以不用人为干预而应对各种故障的Redis部署。本质上是一个特殊的Redis服务器,所以启动方式类似。
该系统执行以下三个任务:
- 监控(Monitoring):Sentinel会不断地检查你的主服务器和从服务器是否允许正常。
- 提醒(Notification):当被监控的某个Redis服务器出现问题时,Sentinel可以通过API向管理员或者其他应用程序发送通知。
- 自动故障迁移(Automatic failover): (1)当一个主服务器不能正常工作时,Sentinel会开始一次自动故障迁移操作,他会将失效主服务器的其中一个从服务器升级为新的主服务器,并让失效主服务器的其他从服务器改为复制新的主服务器;(2)客户端试图连接失败的主服务器时,集群也会向客服端返回新主服务器的地址,是的集群可以使用新主服务器代替失效服务器。
sentinel的分布式特性
Redis Sentinel 是一个分布式系统, 你可以在一个架构中运行多个 Sentinel 进程(progress), 这些进程使用流言协议(gossip protocols)来接收关于主服务器是否下线的信息, 并使用投票协议(agreement protocols)来决定是否执行自动故障迁移, 以及选择哪个从服务器作为新的主服务器。
单个sentinel进程来监控redis集群是不可靠的,当sentinel进程宕掉后(sentinel本身也有单点问题,single-point-of-failure)整个集群系统将无法按照预期的方式运行。所以有必要将sentinel集群,这样有几个好处:
- 有一些sentinel进程宕掉了,依然可以进行redis集群的主备切换;
- 如果只有一个sentinel进程,如果这个进程运行出错,或者是网络堵塞,那么将无法实现redis集群的主备切换(单点问题);
- 如果有多个sentinel,redis的客户端可以随意地连接任意一个sentinel来获得关于redis集群中的信息
一个健壮的部署至少需要三个哨兵实例。三个哨兵实例应该放置在客户使用独立方式确认故障的计算机或虚拟机中。例如不同的物理机或不同可用区域的虚拟机。【本次讲解是一个机器上进行搭建,和多级是一个道理】
Redis-sentinel搭建
1.环境
Windows10
Redis-x64-3.2.100
本次搭建说明:
master:127.0.0.1:6379 【初始化master】slave:127.0.0.1:6380 127.0.0.1:6381
sentinel:127.0.0.1:26379 127.0.0.1:26380 127.0.0.1:26381
2.安装和基本配置
主从配置见上一篇,注意此次测试不设服务器密码,bind设置为0.0.0.0。
3.Sentinel配置
这个是sentinel1.conf配置内容,其他文件同理新增然后改一下文件名和端口即可,26380,和 26381。配置文件只需要配置master的信息,不用配置slave,因为slave能够被自动检测到(master节点中有关于slave的消息)。
#当前Sentinel服务运行的端口
port 26379
# 哨兵监听的主服务器
sentinel monitor mymaster 127.0.0.1 6379 2
# 3s内mymaster无响应,则认为mymaster宕机了
sentinel down-after-milliseconds mymaster 3000
#如果10秒后,mysater仍没启动过来,则启动failover
sentinel failover-timeout mymaster 10000
# 执行故障转移时, 最多有1个从服务器同时对新的主服务器进行同步
sentinel parallel-syncs mymaster 1
每一行配置的含义
sentinel monitor [master-group-name] [ip] [port] [quorum]
- master-group-name:master名称(可以自定义)
- ip port : IP地址和端口号
- quorun:票数,Sentinel需要协商同意master是否可到达的数量。
第一行配置指示 Sentinel 去监视一个名为 mymaster 的主服务器, 这个主服务器的 IP 地址为 127.0.0.1 ,端口号为 6379 ,而将这个主服务器判断为失效至少需要 2 个 Sentinel 同意 (只要同意 Sentinel 的数量不达标,自动故障迁移就不会执行)。在本文中:redis集群中有3个sentinel实例,其中master挂掉,这里设置票数为2,表示有2个sentinel认为master挂了,才能被认为是真正挂掉。
sentinel <选项的名字> <主服务器的名字> <选项的值>
- down-after-milliseconds 选项指定了 Sentinel 认为服务器已经断线所需的毫秒数。
不过只有一个 Sentinel 将服务器标记为主观下线并不一定会引起服务器的自动故障迁移: 只有在足够数量的 Sentinel 都将一个服务器标记为主观下线之后, 服务器才会被标记为客观下线(objectively down, 简称 ODOWN ), 这时自动故障迁移才会执行。将服务器标记为客观下线所需的 Sentinel 数量由对主服务器的配置决定。
- parallel-syncs 选项指定了在执行故障转移时, 最多可以有多少个从服务器同时对新的主服务器进行同步, 这个数字越小, 完成故障转移所需的时间就越长。
4.启动
在不同cmd窗口下分别输入以下启动命令。
redis-server redis.windows.conf
redis-server redis.windows_slave1.conf
redis-server redis.windows_slave2.conf
redis-server sentinel1.conf --sentinel
redis-server sentinel2.conf --sentinel
redis-server sentinel3.conf --sentinel
查看主服务器6379信息,发现role属性为主机,并有两台从机。
查看服务器6380信息,发现role属性为从机,主机端口为6379。
Redis-Sentinel高可用场景演示
场景1:主服务器master宕机
1.关闭6379服务器
127.0.0.1:6379> shutdown
not connected>
2.查看Sentinel日志,将6381设置为了主服务器
场景2:故障的6379 master重新启动
Jedis客户端使用Sentinel
import java.util.HashSet;
import java.util.Set;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.JedisSentinelPool;
public class RedisManagerUtil {
private static JedisSentinelPool pool = null;
// 自带的哨兵模式 JedisSentinelPool, 并在一开始初始化连接池
static {
try {
JedisPoolConfig config = new JedisPoolConfig();
// 控制一个pool可分配多少个jedis实例,通过pool.getResource()来获取;
// 如果赋值为-1,则表示不限制;如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted(耗尽)。
config.setMaxTotal(Integer.valueOf(1000));
// 控制一个pool最多有多少个状态为idle(空闲的)的jedis实例。
config.setMaxIdle(Integer.valueOf(20));
// 表示当borrow(引入)一个jedis实例时,最大的等待时间,如果超过等待时间,则直接抛出JedisConnectionException;
config.setMinEvictableIdleTimeMillis(Integer.valueOf(-1));
// 在borrow一个jedis实例时,是否提前进行validate操作;如果为true,则得到的jedis实例均是可用的;
config.setTestOnBorrow(Boolean.valueOf(true));
// master名称和配置文件中配置的要一样
String master = "mymaster";
//setinel客户端提供了master自动发现功能
Set<String> sentinels = new HashSet<String>();
sentinels.add("127.0.0.1:26379");
sentinels.add("127.0.0.1:26380");
sentinels.add("127.0.0.1:26381");
pool = new JedisSentinelPool(master, sentinels, config);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 构建redis连接池
*
* @return JedisPool
*/
public static JedisSentinelPool getPool() {
return pool;
}
/**
* 返还到连接池
*
* @param pool
* @param redis
*/
public static void returnResource(JedisSentinelPool pool, Jedis redis) {
if (redis != null) {
try {
pool.returnResource(redis);
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 测试redis线程池是否正常
* @param args
*/
public static void main(String[] args) {
JedisSentinelPool pool = RedisPoolAPIManager.getPool();
Jedis redis = pool.getResource();
System.out.println("redis = " + redis);
if(redis != null){
returnResource(pool,redis);
}
}
}