2019独角兽企业重金招聘Python工程师标准>>>
1. Redis Sentinal解决主从复制的高可用问题
1. 主从复制中存在的问题:
- 当主节点或从节点发生故障时,必须手动进行故障转移
- 单主节点,写能力和存储能力受限,因为只能在主节点进行写操作,而且即便有多个从节点,但这些从节点存储的数据也只是主节点的数据副本,实际上也就相当于数据只存储在主节点一台机器中。
2. Redis Sentinal主要就是解决了主从复制中的手动故障转移所带来的麻烦,从而实现了自动故障转移,主从复制中的故障转移问题详见上一篇博客Redis应用学习(六)——主从复制,Redis Sentinal就是负责监控所有的主从节点、实现自动故障转移流程并且通知客户端故障迁移(客户端故障迁移指客户端所连接的Redis发生了故障,客户端的连接会迁移到另一个正常的Redis上),但注意,sentinel只能对主节点进行故障转移和通知客户端迁移连接,对于从节点,sentinel无法进行故障转移,也无法通知客户端进行连接迁移,只能对其做一个下线操作。
2. 认识Redis Sentinal
1. Redis Sentinal架构:多个Sentinal节点构成一个Redis Sentinal,每个Sentinal节点都会监控着Redis中的每一个主从节点,多个Sentinal节点组成Redis Sentinal功能提供了Redis Sentinal功能的高可用型,比如当某个Sentinal节点判断Redis主节点为故障时,其他Sentinal节点也会测试该Redis主节点是否故障,进行一个类似于投票的过程,如果投票结果确定该Redis主节点确实故障,那么就会进行故障转移,而且即便某个Sentinal节点发生故障,其他的Sentinal节点也能保持Redis Sentinal功能的正常运行。而对于客户端来说,其不需要记录(直连)某个Redis节点的信息(IP地址和端口等)来进行读写,而是记录Redis Sentinal的信息(IP地址和端口等)进行交互
2. Sentinal节点:每个Sentinal节点实际上就相当于一个特殊的Redis,但是不能存储数据,默认占用端口26379,可以通过一个配置文件来修改启动的相关配置
3. Sentinal对于主节点故障的处理:大致原理过程和手动过程类似,但是是由Sentinal自动完成的,从节点故障不会进行故障转移和通知客户端进行连接迁移
- 多个Sentinal节点投票确定Redis主节点发生故障
- Sentinal节点间投票选出一个Sentinal节点作为领导者
- 通过选举出来的Sentinal领导者节点来Redis从节点中选出一个作为新的Redis主节点
- Sentinal领导者节点将其余的Redis从节点迁移到新的Redis主节点下
- 通知客户端主从节点变化,将连接原主节点的客户端连接到新主节点上
- 如果发生故障的原主节点又恢复正常,那么就将恢复正常的节点变为新主节点的从节点
4. 一个Redis Sentinal可以监控多套主从节点,详细实现下面会进行说明。
3. Redis Sentinal的安装与配置
1. 安装过程:
- 首先需要安装配置一套Redis主从节点,不做详细过程
- Sentinal节点配置:在Redis解压后的安装包文件夹中,有一个Sentinal基础配置文件sentinel.conf,就是该文件对Sentinal进行配置,主要修改以下部分参数
- port 26379:上面说过,Sentinal实际上是一个特殊的Redis,所以需要配置运行时占用端口
- dir "":日志文件存放目录
- logfile "port.log":生成的日志文件名
- sentinal monitor master-name master-ip master-port count:配置监控的Redis主节点,master-name表示要给监控的Redis主节点起一个名,必须要唯一,master-ip和master-port就是主节点的IP地址和端口,最后的count参数就是表示当count个sentinel节点都确定该主节点发生故障时,就会进行故障转移
- sentinel down-after-milliseconds master-name 30000:master-name就某个被监控的主节点,30000这个数字就表示如果该主节点如果超过30000毫秒一直都无法ping通,那么就就会被当前sentinel节点判定为故障
-
sentinel parallel-syncs master-name count:当主节点master-name被确定为故障时,就会发生故障转移,此时某个从节点就会被sentinel设置为新的主节点,其他从节点就会重新和该新主节点建立主从关系,并且每个从节点都会和新主节点执行一次全量复制,而后面的参数count就表示是同时只允许count个从节点进行全量复制
-
sentinel failover-timeout master-name number:进行故障转移的时间
-
daemonize yes:表示进行启动sentinel节点以守护线程方式启动
- 启动sentinel:通过redis-sentinel和配置文件启动,比如 ./redis-sentinel sentinel-26380.conf
- 通过客户端连接sentinel节点,注意,sentinel节点无法执行Redis中的任何数据读写命令,只有极少命令,比如info命令就是用来获取sentinel节点自身所有的相关信息,通过这些信息可以获得sentinel是否配置启动成功
4. Redis Sentinal的Java客户端
1. Sentinal的服务端与客户端的高可用:如果Sentinal不能实现主节点自动故障转移后通知客户端,那么Sentinal功能就仅仅只是实现了服务端的高可用,比如发生故障迁移后新的Redis节点IP地址和端口号不发送给客户端,那么Sentinal所实现的自动故障转移对于客户端来说相当于无效的,所以Sentinal还会在自动故障转移之后,通知客户端Redis主节点的变化,并返回信息,使得客户端也得以做出相应的改变重新连接到新的Redis主节点,那么就可以实现客户端的高可用。
2. 通过Redis Sentinal实现客户端高可用的原理:
- 首先遍历一边Sentinal节点集合,获取一个可用的Sentinal节点
- 进入sentinel客户端,通过命令sentinel get-master-addr-by-name mymaster 即可获取可用Redis主节点的IP地址和端口号
- 在获取到Redis主节点信息时,还会自动向主节点执行一条role或role replication命令来进行验证,验证IP地址和端口号是否是真的主节点
- 如果Redis主节点中发生了故障转移,监视主从节点的变化的sentinel就会把这些变化通知给客户端
3. Java客户端jedis实现对Redis Sentinal监控下的Redis主节点的连接以及发送命令:jedis客户端的实现原理基本与上面所写吻合,示例代码
import java.util.LinkedHashSet;
import java.util.Set;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisSentinelPool;
/**
* @ClassName:TestJedisSentinelPool
* @Description:连接由Redis Sentinel监视的Redis主节点
*/
public class TestJedisSentinelPool {
//注意,Dubug测试该方法时要保证linux系统的防火墙关闭(centos7关闭指令为systemctl stop firewalld)
public static void main(String[] args) {
/*
* 创建一个jedis连接池,不过该连接池创建的jedis对象所连接的Redis是被Sentinel监控的主节点
* masterName表示Sentinel配置中的Redis主节点名
* sentinels表示所有的Sentinel节点的IP地址与端口号信息
* poolConfig表示该连接池的相关配置参数
*/
String masterName="mymaster";
Set sentinels=new LinkedHashSet();
sentinels.add("192.168.10.128:26380");
sentinels.add("192.168.10.128:26381");
sentinels.add("192.168.10.128:26382");
GenericObjectPoolConfig poolConfig=new GenericObjectPoolConfig();
JedisSentinelPool pool=new JedisSentinelPool(masterName, sentinels, poolConfig);
Jedis jedis=null;
try {
//注意,这里返回的jedis对象连接的仍然是Redis节点,并不是sentinel节点
jedis=pool.getResource();
//通过jedis就可以执行Redis的相关命令
String response=jedis.set("user", "lisi");
System.out.println(response);
} catch (Exception e) {
// 异常处理
}finally {
if(jedis!=null){
//回收jedis连接对象
jedis.close();
}
}
}
}
Debug测试结果截图:
4. 模拟故障转移,观察jedis客户端的变化:要观察这种变化,需要模拟真正应用环境中,jedis客户端会不停的执行一些操作,所以更改上面的示例代码,添加一个while死循环来生成随机数,然后运行客户端测试程序,然后关闭linux系统中的Redis主节点,就可以观察到jedis中的变化,观察到服务端故障转移的过程,一旦连接的Redis主节点故障,客户端就会一直报错,但是报错一段时间之后又会停止报错继续成功执行,这个变化就是服务端自动故障转移的表现。
//测试观察服务端自动故障转移在客户端的表现
public void test(){
String masterName="mymaster";
Set sentinels=new LinkedHashSet();
sentinels.add("192.168.10.128:26380");
sentinels.add("192.168.10.128:26381");
sentinels.add("192.168.10.128:26382");
GenericObjectPoolConfig poolConfig=new GenericObjectPoolConfig();
JedisSentinelPool pool=new JedisSentinelPool(masterName, sentinels, poolConfig);
Jedis jedis=null;
Random rand=new Random();
String key=null;
int value=0;
int count=0;
while(true){
count++;
try {
value=rand.nextInt(10000);
key="key-"+value;
jedis=pool.getResource();
String response=jedis.set(key, value+"");
//每隔1秒输出一次成功执行的结果
if(count%100==0){
System.out.println("set "+key+":"+value+" "+response);
}
TimeUnit.MILLISECONDS.sleep(10);//每10毫秒执行一次循环
} catch (Exception e) {
// 错误提示
System.out.println("set "+key+":"+value+" failed");
//sentinel判定Redis主节点是否为故障,如果确定主节点故障,则进行自动故障转移
System.out.println("服务端Redis节点异常,判断是否进行自动故障转移");
}finally {
if(jedis!=null){
//回收jedis连接对象
jedis.close();
}
}
}
}
5. Redis Sentinal的服务端高可用原理
1. 三个定时任务:三个定时任务实现了自动故障转移中最重要的故障检测,通过以下三个定时任务就能发现出现故障的各个节点
- 每个sentinel节点每10秒都会向自己监控的每一套Redis主从节点发送一条info命令来获取节点信息,以此来发现从节点并确认主从关系
- 每2秒每个sentinel节点都会通过自己监控的主节点的channel(利用Redis的发布订阅功能实现)来交换信息,通过主节点中的_sentinel_:hello频道来实现各个sentinel间的交互,交互各个sentinel对主从节点甚至是其他sentinel节点的"看法"和自身信息
- 每一秒每个sentinel节点都会对其他sentinel节点和Redis节点做ping命令操作
2. 主观下线和客观下线:两个相关配置参数
- sentinal monitor master-name master-ip master-port count:sentinel的配置文件中该配置参数就是用来让sentinel节点监控一个Redis主节点及其从节点,而且通过count参数(如果超过count个sentinel节点都主观确定该Redis节点处于下线状态,那么该Redis节点就是处于客观下线)来进行判断是否该Redis节点是客观下线
- sentinel down-after-milliseconds master-name 30000:该配置文件用于sentinel节点对Redis主节点及其从节点判断是否主观下线,如果某个sentinel节点对Redis节点超过30000毫秒一直处于无法ping通的情况,那么该节点就会被当前sentinel节点判断为主观下线
3. 领导者选举:上面说过,在进行自动故障转移之前,sentinel节点群必须要选出一个sentinel节点作为领导者,领导者来执行故障转移操作
- 当一个Redis节点确认下线后,每个监控该节点并对其做了主观下线的sentinel节点都会给其他每一个sentinel节点发送一个命令(sentinel is-master-down-by-addr)都希望自己成为领导者
- 收到领导者请求命令的sentinel节点如果在此之前没有收到其他sentinel节点的请求命令,那么该请求命令就会被同意,否则被拒绝
- 如果sentinel节点发现同意自己作为领导者的票数过半且超过count个,那么就会成为领导者执行故障转移
- 如果有多个sentinel节点成为了领导者或没有sentinel节点成为领导者,那么将会重新选举
4. 故障转移中的从节点选择:在故障转移中,如果主节点确认下线,那么则需要选择一个从节点来作为新的主节点,选择规则有
- 选择从节点的salve-priority(节点优先级,可以在配置文件中进行配置,但一般不会修改)最高的节点,如果有则返回,否则执行下一条规则
- 选择复制偏移量最大的从节点(意味着它的数据复制的完整度是最接近原来的主节点的),如果存在则返回该节点,否则执行下一条规则
- 选择从节点中run-id最小的,这意味着该节点是运行时间最久的(或者说启动最早的)。
6. Redis Sentinal的常见运维
1. 节点运维:就是节点的手动上线和下线操作,针对的目标是Redis主节点、从节点和sentinel节点。
2. 节点运维解决的问题:当下面这些问题发生时,应该由手动执行节点的上下线操作来进行一个故障转移
- 机器下线:由于某种原因导致机器不能在使用(比如机器使用时间超过保质期等)
- 机器性能不足:例如CPU、内存、硬盘等
- 节点自身运行故障:例如系统宕机等
3. 对于主节点的下线:手动指定一个sentinel节点作为领导者,并对指定的Redis主节点进行故障转移:sentinel failover master-name,在某个sentinel节点的客户端运行该命令就是指定该sentinel节点为领导者,并且对其所监控的master-name节点进行故障转移
4. 对于从节点的下线:首先需要确定是临时下线(暂时关闭该节点,之后还会进行重启)还是永久下线(不再使用这台机器作为一个从节点提供服务),下线后是否需要进行一些清理工作,比如从节点的一些配置,AOF、RDB以及日志文件的清理,比如如果要临时下线来启动一下其他进程,要保证从节点的相关配置不会干扰到新进程的运行。此外,从节点的下线还要考虑读写分离的问题。
5. 对于sentinel节点的下线:与从节点类似
6. 节点上线:对于Redis主节点的上线,同样使用sentinel failover命令切换主节点即可;而对于从节点的上线使用slaveof命令将该从节点关联到主节点即可,sentinel会自动感知;对于sentinel节点的上线,只需要写好配置文件直接启动即可。
7. Redis Sentinal中高可用读写分离的实现
1. Redis Sentinal中读写分离存在的问题:客户端连接到一个从节点只进行读操作,但如果该从节点下线后,客户端无法获知下线信息,也无法将连接转移到另一个从节点上,因为sentinel只对从节点进行一个主观下线操作,但不会通知客户端从节点的下线信息,也就无法做客户端连接迁移(将连接原从节点的客户端转移到另一个从节点上)。
2. 要实现高可用的读写分离,客户端就要知道从节点变化的三个消息:
- 切换为主节点,也就是从节点晋升为主节点
- 切换为从节点,主节点变为从节点
- 从节点下线
3. 实现原理:连接从节点的客户端必须能感知到所有从节点的变化,也就是上面的三条消息,但是Java客户端里并没有提供这种从节点的读写分离实现,需要我们自己去实现,可以参考JedisSentinelPool的实现来手动编写一个从节点高可用连接池。但很明显,sentinel环境下实现读写分离的高可用非常复杂,对于性能提高、容量扩展的时候,这种方式是比较复杂的,所以,推荐使用集群(redis-cluster)。