Redis技术内幕——RedisSentinel
Redis Sentinel(哨兵)是 Redis 官⽅提供的集群管理⼯具,是 Redis ⾼可⽤的解决⽅案,本⾝是⼀个独⽴运⾏的进程,它可以监视多个Master-Slave 集群,发现 Master 宕机之后,能进⾏⾃动切换,将该 Master 下的某个 Slave 晋升为 Master,继续处理请求。
Redis Sentinel 主要功能:
监控:检查主从服务器是否运⾏正常;
提醒:通过 API 向管理员或者其他应⽤程序发送故障通知;
⾃动故障迁移:主从切换。
Redis Sentinel 是⼀个分布式系统,你可以在⼀个架构中运⾏多个 Sentinel 进程,这些进程使⽤流⾔协议 (Gossip Protocols) 接收Master 是否下线的信息,并使⽤投票协议来决定是否执⾏⾃动故障迁移,以及选择哪个 Slave 作为新的 Master。这个跟 Zookeeper 是⽐较类似的。
流⾔协议:
每个节点都随机地与对⽅通信,最终所有节点的状态达成⼀致;
种⼦节点定期随机向其他节点发送节点列表以及需要传播的消息;
不保证信息⼀定会传递给所有节点,但是最终会趋于⼀致。
1.架构说明
Redis Sentinel 架构:
Redis Sentinel 故障转移:
1、如果 master 宕机了,连接出现中断,多个 Sentinel 发现并确认 master 有问题;
2、选举出⼀个 Sentinel 作为领导;
3、选举⼀个 slave 作为新的 master;
4、通知其余 slave 成为新的 master 的 slave;
5、通知客户端主从变化;
6、Sentinel 等待⽼的 master 复活成为新 master 的 slave。
整个过程其实就是由我们⼿动处理故障变成了 Sentinel 进⾏故障发现、故障⾃动转移、通知客户端的过程。
Redis Sentinel 还可以监控多个 master-slave 集群,每个 master-slave 使⽤ master-name 作为标识,有效节省资源。
#安装与配置
最终要达到的效果如下:
淮南牛肉汤图片Sentinel 的默认端⼝是 26379。⽣产环境中 Sentinel 节点个数应该为⼤于等于 3 且最好为奇数。
安装与配置⼤致过程如下:
1、配置开启主从节点;
2、配置开启 Sentinel 监控主节点(Sentinel 是特殊的 Redis,Sentinel 本⾝是不存储数据的,⽽且⽀持的命令⾮常有限,主要作⽤就是监控、完成故障转移、通知);
3、实际应该多机器部署 Sentinel,保证 Sentinel ⾼可⽤;
4、详细配置节点
详细配置过程:
Redis 主节点 redis/f 配置(f 模板⽂件在 f):
# 关闭保护模式
protected-mode no
# 配置启动端⼝
port 7000
# 配置后台启动
daemonize yes
# 修改pidfile指向路径 redis-${port}.pid
pidfile /var/run/redis-7000.pid
# ⽇志记录⽅式 redis-${port}.log
logfile "redis-7000.log"
# 配置dump数据存放⽬录
dir "/opt/soft/redis/data/"
# 配置dump数据⽂件名 redis-${port}.rdb
dbfilename dump-7000.rdb
启动命令:
f
苹果酱Redis 从节点 redis/f 配置(f 只是端⼝号不同):
# 关闭保护模式
protected-mode no
# 配置启动端⼝
服从意识
port 7001
# 配置后台启动
daemonize yes
# 修改pidfile指向路径 redis-${port}.pid
pidfile /var/run/redis-7001.pid
# ⽇志记录⽅式 redis-${port}.log
logfile "redis-7001.log"
# 配置dump数据存放⽬录
dir "/opt/soft/redis/data/"
# 配置dump数据⽂件名 redis-${port}.rdb
dbfilename dump-7001.rdb
# 配置master的ip地址、端⼝,在Redis启动时会⾃动从master进⾏数据同步
slaveof 127.0.0.1 7000
启动命令:
f
f
Sentinel 节点 redis/f 配置(这⾥只给出其中⼀个配置,另外两个只是端⼝号不同,f 模板⽂件在 f):
# 配置启动端⼝
port 26379
# 配置后台启动
daemonize yes
# 配置⼯作⽬录
dir "/opt/soft/redis/data/"
冷暴力分手# ⽇志记录⽅式 redis-ntinel-${port}.log
logfile "redis-ntinel-26379.log"
# ntinel监听master的名字、ip、端⼝、⼏个ntinel认为master有问题就发⽣故障转移(最好配置ntinel节点的⼆分之⼀加⼀)
ntinel monitor mymaster 127.0.0.1 7000 2
# 每⼀个ntinel节点都会向master节点、slave节点和其他ntinel节点1秒钟ping⼀次。在down-after-milliconds毫秒内没有进⾏回复,则判定该节点失败ntinel down-after-milliconds mymaster 30000
# slave复制时可以并⾏的个数,建议1,减轻master的压⼒
ntinel parallel-syncs mymaster 1
# 故障转移时间
ntinel failover-timeout mymaster 180000
男生动漫图片
启动命令:
f
f
f
Sentinel 会⾃动发现 Redis slave,并写⼊到 redis-ntinel-${port}.conf,Sentinel 彼此之间也能⾃动感知到。
2.客户端连接
客户端初始化时连接的是 Sentinel 节点集合,不再是具体的 Redis 节点,但 Sentinel 只是配置中⼼不是代理。Sentinel 中的数据节点与普通数据节点没有区别。
1.请求响应流程
1. 客户端遍历 Sentinel 节点集合,获取⼀个可⽤的 Sentinel 节点;
2. 客户端执⾏⼀个 Sentinel 的 API:get-master-addr-by-name < masterName> 获取 master 节点真
正的地址和端⼝;
3. 客户端执⾏ role 或者 role replication 进⾏⼀次验证,验证是否是真的 master 节点;
4. 连接 master 进⾏操作;
5. 客户端会订阅 Sentinel 的某⼀个频道,这个频道⾥会有谁是 master 的⼀个变化,假如有变化,则发送消息给客户端,客户端再去新
的 master 进⾏连接。
2.Java客户端连接
Jedis ⽅式:
JedisSentinelPool jedisSentinelPool =new JedisSentinelPool(masterName, ntinelSet, poolConfig, timeout);
写爱的作文Jedis jedis = null;
try{
jedis = Resource();
// jedis command
}catch(Exception e){
<("error", e);
}finally{
if(jedis != null){
jedis.clo();
}
}
JedisSentinelPool 的实现原理:
先看下 JedisSentinelPool 的构造函数:
public JedisSentinelPool(String masterName, Set<String> ntinels,
final GenericObjectPoolConfig poolConfig,final int timeout){
this(masterName, ntinels, poolConfig, timeout, null, Protocol.DEFAULT_DATABASE);
}
public JedisSentinelPool(String masterName, Set<String> ntinels,
final GenericObjectPoolConfig poolConfig,final int connectionTimeout,final int soTimeout,
final String password,final int databa,final String clientName){
this.poolConfig = poolConfig;//连接池配置
this.soTimeout = soTimeout;// 读写超时时间
this.password = password;// 密码
this.databa = databa;// 数据库, Redis⼀共有16个数据库, 默认使⽤0
this.clientName = clientName;// 客户端名
HostAndPort master =initSentinels(ntinels, masterName);// 初始化ntinel
initPool(master);// 初始化连接池, 连接master
}
看下 initSentinels ⽅法:
private HostAndPort initSentinels(Set<String> ntinels,final String masterName){
HostAndPort master = null;
boolean ntinelAvailable =fal;
// 遍历所有ntinel节点, 获取master节点
for(String ntinel : ntinels){
final HostAndPort hap = HostAndPort.parString(ntinel);
Jedis jedis = null;
try{
jedis =new Jedis(hap);
// 执⾏ntinel的API:get-master-addr-by-name <masterName>命令获取master节点真正的地址和端⼝
List<String> masterAddr = inelGetMasterAddrByName(masterName);
ntinelAvailable =true;
if(masterAddr == null || masterAddr.size()!=2){
// 如果不可⽤则continue
continue;
}
// 如果可⽤则break
master =toHostAndPort(masterAddr);
break;
}catch(JedisException e){
log.warn("Cannot get master address from ntinel running @ {}. Reason: {}. Trying next one.", hap, e.toString());
}finally{
东莞自驾游if(jedis != null){
jedis.clo();
}
}
可乐英语怎么说
}
if(master == null){
if(ntinelAvailable){
throw new JedisException("Can connect to ntinel, but "+ masterName +" ems to be ");
}el{
throw new JedisConnectionException("All ntinels down, cannot determine where is "+ masterName +" master ");
}
}
// 遍历所有ntinel节点, 订阅消息(主观下线、客观下线、领导者选举、主从切换)
for(String ntinel : ntinels){
final HostAndPort hap = HostAndPort.parString(ntinel);
JedisSentinelPool.MasterListener masterListener =new JedisSentinelPool.MasterListener(masterName, Host(), Port());
// whether MasterListener threads are alive or not, process can be stopped
masterListener.tDaemon(true);
masterListeners.add(masterListener);
masterListener.start();
}
return master;
}
MasterListener 是⼀个线程: