本文讲述Redis高可用方案中的哨兵模式——Sentinel,RedisClient中的Jedis如何使用以及使用原理。

  • Redis主从复制
  • Redis Sentinel模式
  • Jedis中的Sentinel

Redis主从复制

Redis主从复制是Sentinel模式的基石,在学习Sentinel模式前,需要理解主从复制的过程。

1.保证数据一致性的机制

Redis主从复制的含义和Mysql的主从复制一样,即利用Slave从服务器同步Master服务器数据的副本。主从复制的最为关键的点在于主从数据的一致性,在Redis中主要通过以下三点:

  • 当Master和Slave连接正常时,Master会源源不断的发送命令流至Slave,更新Slave的数据,保证主从数据的一致性,其中包括:写入、过期和驱逐等操作;
  • 当Master和Slave之间出现连接断开或者连接超时等情况,当Slave重新连接上Master时,Slave会主动请求Master进行部分重同步——即在连接断开的窗口内,Master数据集变化的部分同步至Slave,保证其数据一致性;
  • 当无法进行部分重同步时,Slave则请求进行全量同步;

利用以上三点,Redis的主从复制保证数据的最终一致性。

2.主从复制的工作流程

假设有两台服务器,一台是Master,另一台是Slave。现在需求是保证Master和Slave的数据一致性。

如果要保证精确的一致性,最好的方式是实时的进行全量同步,基于全量肯定是一致的。但是这样造成的性能损耗必然不可估计。

增量同步即同步变化的数据,不同步未发生变化的数据,虽然实现程度比全量复杂,但是能让性能提升。

Redis中实现主从复制是全量结合增量实现。

增量同步,必须获取主从服务器之间的数据差异,对于数据同一份数据的差异获取,最常见的方式即版本控制。如常见的版本控制系统:svn、git等。在Redis主从关系中,数据的最初来源于Master,所以数据版本控制由Master控制。

Notes:

同一份数据的演变记录,最好的方式即版本控制

在Redis中,每个Master都有一个RelicationID,标识一个给定的历史数据集,是一串伪随机串。同时还有一个OffsetID,当Master将变化的数据发送给Slave时,发送多少个字节,相应的offsetID就增长多少,依据此做数据集的版本控制。即使没有Slave,Master也会增长OffsetID,一个RelicationID和OffsetID的组合都会标识一个数据集版本。

当Slave连接到Master时,Slave会向Master主动发送自己的RelicationID和OffsetID,Master依此判断Slave当前的数据版本,将变化的数据发送给Slave。当Slave发送的是一个未知的RelicationID和OffsetID,Master则会进行一次全同步。

Master会开启另一个复制进程。复制进程会创建一个持久化的RDB快照文件,并将新的请求命令缓冲在缓冲区中,达到Copy-On-Write的效果。在RDB文件创建完成后,会将RDB文件发送给Slave,Slave接收到后,将文件保存至磁盘,然后再载入内存。最后Master再将缓冲区的命令流发送给Slave,完成最终的数据同步。

对于主从复制还有很多特性,如:主从同步中的过期键处理主从之间的认证允许N个附加的副本Slave只读模式等,可以参考:复制

2.主从复制的配置

Redis主从复制的配置比较简单,分为两种方式:静态文件配置和动态命令行配置。redis.conf中提供:

slaveof 192.168.1.1 6379

配置项用于配置Slave节点的Master节点,表示是谁的Slave。

同时还可以在redis-cli命令行中使用slaveof 192.168.1.1 6379格式的命令配置一个Slave的Master节点。可以使用slaveof no one取消其从节点的身份。

Redis Sentinel模式

1.为什么需要Sentinel

Redis已经具备了主从复制的功能,为什么仍然需要Sentinel模式?

Redis的主从模式从一定从程度上的确解决了可用性问题,这毋庸置疑。但是只仅仅主从复制来完成可用性,就比较简陋,灵活性不够,操作复杂。更不用说高可用!

  1. 当Master宕机,需要运维人员干预将Slave提升至新的Master,或者脚本自动化完成,但是都无法避免问题的复杂化;
  2. 客户端应用需要切换至新的Master,这点可能是最大的痛点,应用无法自动切换至新的Master,无法完成自动的故障转移,不够灵活,无法高可用;

基于以上的需求,Redis Sentinel是Redis提供的高可用的一种模型,在Sentinel模式下,无需人员的干预,Sentinel能够帮助完成以下工作:

  • 监控:Sentinel能够持续不断的检查Master和Slaves是否在正常工作;
  • 通知:Sentinel可以以Api的方式通知另一个程序或者管理员:发生错误的Redis实例;
  • 自动化故障转移:如果Master发生故障,Sentinel将开始故障转移,在这过程中将提升一个Slave为新的Master,将其他的Slave重新配置其Master为新提升的Master,并通知使用Redis的应用程序使用新的Master;
  • 配置提供者:应用连接上Sentinel,可以获取整个高可用组中的Slave和Master的信息,Sentinel充当着客户端服务发现的来源;
2.Sentinel是什么

Sentinel本身就是一个分布式系统。Sentinel基于一个配置运行多个进程协同工作,这些进程可以在一个服务器实例上,也可以分布在多个不同实例上。多个Sentinel工作有如下特点:

  • 当多个Sentinel认为一个Master不可用时,将会发起失败检测,降低误报的可能性。比如某些Sentinel因为与Master网络问题导致的误报;
  • 即使不是所有的Sentinel进程都是完好的,Sentinel仍然能够正常的工作,解决了Sentinel本身的单点问题;

在Sentinel体系中,Sentinel、Redis实例和连接到Sentinel和Redis实例的应用这三者也共同组成了一个完整的分布式系统。

3.搭建Sentinel

Redis中提供了搭建Sentinel的相关命令:redis-sentinel。其中Redis包中也包含了sentinel.conf的示例配置。

启动Sentinel实例,可以直接运行:

redis-sentinel sentinel.conf

但是在配置sentinel模式前,现需要做些准备工作:

  1. 至少需要准备三台sentinel实例,解决sentinel本身的单点问题。如果是线上,最好保证sentinel的实例是不同的机器;
  2. 需要使用支持Sentinel模式的client;
  3. 需要保证Sentinel实例之间的网络连通,Sentinel采用自动服务发现机制发现其他的Sentinel;
  4. 需要保证Sentinel和Redis实例之间的网络连通,Sentinel需要实时的获取Master和Slave信息并与其交互;

关于Sentinel系统的其他关注点,请参考:Fundamental things to know about Sentinel before deploying

下面看下Sentinel的配置文件:

sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 60000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000

sentinel monitor

用于配置sentinel的名称,master节点,仲裁数。

  • master-group-name:用于指定一个唯一的sentinel名称;
  • ip、port:master节点的ip和port;
  • quorum:认为Master不可用的sentinel进程数量时,尝试发起故障转移。但是并不是立即进行,而只仅仅作为用于检测是否有故障。对于实际发起故障转移,sentinel需要进行选举,整个过程需要整个sentinel进程中的大多数投票表决;

举个例子,假设有5个sentinel进程:

  1. 其中有两个进程认为master不可用,则其一尝试进行故障转移;
  2. 如果至少有三个sentinel可用,则进行实际的故障转移;

down-after-milliseconds

用于配置sentinel认为Redis实例不可用的至少时间时多少,以毫秒为单位

sentinel parallel-syncs

用于配置故障转移后,同时进行重新配置slave节点的个数。重新配置slave时,则slave将无法处理客户端的查询请求。如果同时配置所有的slave,则将会出现,整个Redis不可用。但是如果该值较小,又会导致重新配置时间过长。需要trade off。

sentinel failover-timeout

用于配置故障转移的超时时间

接下来实际演示配置Redis Sentinel过程:

  • 准备环境
  • 编写配置:Sentinel conf和Redis conf
  • 启动Redis Sentinel

准备环境,由于笔者没有如此多的服务器,虽然可以使用Docker,但是为了简单,直接使用一台机器,监听不同端口实现。

#sentinel实例
127.0.0.1:26379
127.0.0.1:26380
127.0.0.1:26381
127.0.0.1:26382
127.0.0.1:26383 #redis实例
127.0.0.1:6379
127.0.0.1:6380
127.0.0.1:6381

编写sentinel的配置:

port 26379
dir "/Users/xxx/redis/sentinel/data"
logfile "/Users/xxx/redis/sentinel/log/sentinel_26379.log"
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 60000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000

其他的sentinel实例配置依次类推,分别使用26380,26381,26382,26383端口,日志文件名称也做相应更换。主机节点使用127.0.0.1:6379。

配置6379端口的Redis实例如下:

port 6379
daemonize yes
logfile "/Users/xxx/redis/sentinel/log/6379.log"
dbfilename "dump-6379.rdb"
dir "/Users/xxx/redis/sentinel/data"

6380和6381端口另外再加上一行配置:slaveof 127.0.0.1 6379,表示slave节点。

再分别启动Redis实例和Sentinel实例:

redis-server redis6379.conf
....
redis-sentinel sentinel26379.conf &

启动结束后可以查找Redis的相关进程有:

501  2165     1   0  7:47下午 ??         0:00.55 redis-server *:6379
501 2167 1 0 7:47下午 ?? 0:00.58 redis-server *:6380
501 2171 1 0 7:47下午 ?? 0:00.59 redis-server *:6381
501 2129 1890 0 7:39下午 ttys000 0:02.03 redis-sentinel *:26379 [sentinel]
501 2130 1890 0 7:39下午 ttys000 0:01.99 redis-sentinel *:26380 [sentinel]
501 2131 1890 0 7:39下午 ttys000 0:02.02 redis-sentinel *:26381 [sentinel]
501 2132 1890 0 7:39下午 ttys000 0:01.97 redis-sentinel *:26382 [sentinel]
501 2133 1890 0 7:39下午 ttys000 0:01.93 redis-sentinel *:26383 [sentinel]

表示整个Redis Sentinel模式搭建完毕!

可以使用redis-cli命令行连接到Sentinel查询相关信息

redis-cli -p 26379

#查询sentinel中的master节点信息和状态,考虑篇幅,这里只展示部分
127.0.0.1:26379> sentinel master mymaster
1) "name"
2) "mymaster"
3) "ip"
4) "127.0.0.1"
5) "port"
6) "6379"
7) "runid"
8) "67065dc606ffeb58d1b11e336bc210598743b676"
9) "flags"
10) "master"
11) "link-pending-commands" #查询sentinel中的slaves节点信息和状态,考虑篇幅,这里只展示部分
127.0.0.1:26379> sentinel slaves mymaster
1) 1) "name"
2) "127.0.0.1:6381"
3) "ip"
4) "127.0.0.1"
5) "port"
6) "6381"
7) "runid"
8) "728f17ca3786e46cd28d76b94a1c62c7d7475d08"
9) "flags"
10) "slave"

这里可以将master节点的进程kill,sentinel会自动进行故障转移。

kill -9 2165

#再查询master时,sentinel已经进行了故障转移
127.0.0.1:26379> sentinel master mymaster
1) "name"
2) "mymaster"
3) "ip"
4) "127.0.0.1"
5) "port"
6) "6381"
7) "runid"
8) "728f17ca3786e46cd28d76b94a1c62c7d7475d08"
9) "flags"
10) "master"

sentinel get-master-addr-by-name mymaster命令用于获取master节点

Notes:

以上的sentinel配置中并没有配置slave相关的信息,只配置master节点。sentinel可以根据master节点获取所有的slave节点。

最后再来看下Sentinel中的Pub/Sub,Sentinel堆外提供了事件通知机制。Client可以订阅Sentinel的指定通道获取特定事件类型的通知。通道名称和事件名称相同,例如redis-cli - 23679登录sentinel,订阅subcribe +sdown通道,然后kill监听6379的Redis实例,则会收到如下通知:

1) "pmessage"
2) "*"
3) "+sdown"
4) "slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6381"

Redis Sentinel模式下的Client都是利用其特点,实现应用的故障自动转移。

关于Sentinel还有很多其他的功能特性,如:增加移除一个sentinel,增加移除slave等,更多细节,请参靠Redis Sentinel Documentation

Jedis中的Sentinel

前文中提到Redis Sentinel模式需要应用客户端的支持才能实现故障自动转移,切换至新提升的master节点上。同时也讲解Redis Sentinel系统提供了Pub/Sub的API供应用客户端订阅Sentinel的特定通道获取相应的事件类型的通知。

在Jedis中就是利用这些特点完成对Redis Sentinel模式的支持。下面循序渐进的探索Jedis中的Sentinel源码实现。

Jedis中实现Sentinel只有一个核心类JedisSentinelPool,该类实现了:

  • 获取Sentinel中的master节点;
  • 实现自动故障转移;
1.JedisSentinelPool使用方式

JedisSentinelPool直接提供了构造函数API,可以直接利用sentinel的信息集合构造JedisSentinelPool,其中的getResource直接返回与当前master相关的Jedis对象。

@Test
public void sentinel() {
Set<String> sentinels = new HashSet<>();
sentinels.add(new HostAndPort("localhost", 26379).toString());
sentinels.add(new HostAndPort("localhost", 26380).toString());
sentinels.add(new HostAndPort("localhost", 26381).toString());
sentinels.add(new HostAndPort("localhost", 26382).toString());
sentinels.add(new HostAndPort("localhost", 26383).toString()); String sentinelName = "mymaster";
JedisSentinelPool pool = new JedisSentinelPool(sentinelName, sentinels);
Jedis redisInstant = pool.getResource();
System.out.println("current host:" + redisInstant.getClient().getHost() +
", current port:" + redisInstant.getClient().getPort());
redisInstant.set("testK", "testV"); // 故障转移
Jedis sentinelInstant = new Jedis("localhost", 26379);
sentinelInstant.sentinelFailover(sentinelName); System.out.println("current host:" + redisInstant.getClient().getHost() +
", current port:" + redisInstant.getClient().getPort());
Assert.assertEquals(redisInstant.get("testK"), "testV");
}
2.JedisSentinelPool中成员域
public class JedisSentinelPool extends JedisPoolAbstract {

  // 连接池配置
protected GenericObjectPoolConfig poolConfig; // 默认建立tcp连接的超时时间
protected int connectionTimeout = Protocol.DEFAULT_TIMEOUT;
// socket读写超时时间
protected int soTimeout = Protocol.DEFAULT_TIMEOUT; // 认证密码
protected String password; // Redis中的数据库
protected int database = Protocol.DEFAULT_DATABASE; protected String clientName; // 故障转移器,用于实现master节点切换
protected Set<MasterListener> masterListeners = new HashSet<MasterListener>(); protected Logger log = LoggerFactory.getLogger(getClass().getName()); // 创建与Redis实例的连接的工厂,使用volatile,保证多线程下的可见性
private volatile JedisFactory factory; // 当前正在使用的master节点,使用volatile,保证多线程下的可见性
private volatile HostAndPort currentHostMaster;
}
3.JedisSentinelPool的构造过程

JedisSentinelPool的构造函数被重载很多,但是其中最核心的构造函数如下:

public JedisSentinelPool(String masterName, Set<String> sentinels,
final GenericObjectPoolConfig poolConfig, final int connectionTimeout, final int soTimeout,
final String password, final int database, final String clientName) {
// 初始化池配置、超时时间
this.poolConfig = poolConfig;
this.connectionTimeout = connectionTimeout;
this.soTimeout = soTimeout;
this.password = password;
this.database = database;
this.clientName = clientName;
// 初始化sentinel
HostAndPort master = initSentinels(sentinels, masterName);
// 初始化redis实例连接池
initPool(master);
}

继续看initSentinels过程

// sentinels是sentinel配置:ip/port
// masterName是sentinel名称
private HostAndPort initSentinels(Set<String> sentinels, final String masterName) { HostAndPort master = null;
boolean sentinelAvailable = false; log.info("Trying to find master from available Sentinels..."); // 循环处理每个sentinel,寻找master节点
for (String sentinel : sentinels) {
// 解析字符串ip:port -> HostAndPort对象
final HostAndPort hap = HostAndPort.parseString(sentinel); log.debug("Connecting to Sentinel {}", hap); Jedis jedis = null;
try {
// 创建与sentinel对应的jedis对象
jedis = new Jedis(hap);
// 从sentinel获取master节点
List<String> masterAddr = jedis.sentinelGetMasterAddrByName(masterName); // connected to sentinel...
sentinelAvailable = true; // 如果为空,或者不是ip和port组成的size为2的list,则处理下一个sentinel
if (masterAddr == null || masterAddr.size() != 2) {
log.warn("Can not get master addr, master name: {}. Sentinel: {}", masterName, hap);
continue;
} // 构造成表示master的HostAndPort对象
master = toHostAndPort(masterAddr);
log.debug("Found Redis master at {}", master);
// 寻找到master,跳出循环
break;
} catch (JedisException e) {
// resolves #1036, it should handle JedisException there's another chance
// of raising JedisDataException
log.warn(
"Cannot get master address from sentinel running @ {}. Reason: {}. Trying next one.", hap,
e.toString());
} finally {
if (jedis != null) {
jedis.close();
}
}
} // 如果master为空,则sentinel异常,throws ex
if (master == null) {
if (sentinelAvailable) {
// can connect to sentinel, but master name seems to not
// monitored
throw new JedisException("Can connect to sentinel, but " + masterName
+ " seems to be not monitored...");
} else {
throw new JedisConnectionException("All sentinels down, cannot determine where is "
+ masterName + " master is running...");
}
} log.info("Redis master running at " + master + ", starting Sentinel listeners..."); // 遍历sentinel集合,对每个sentinel创建相应的监视器
// sentinel本身是集群高可用,这里需要为每个sentinel创建监视器,监视相应的sentinel
// 即使sentinel挂掉一部分,仍然可用
for (String sentinel : sentinels) {
final HostAndPort hap = HostAndPort.parseString(sentinel);
// 创建sentinel监视器
MasterListener masterListener = new MasterListener(masterName, hap.getHost(), hap.getPort());
// whether MasterListener threads are alive or not, process can be stopped
// sentinel设置为守护线程
masterListener.setDaemon(true);
masterListeners.add(masterListener);
// 启动线程监听sentinel的事件通知
masterListener.start();
} return master;
}

初始化sentinel中的主要逻辑分为两部分:

  • 通过遍历sentinel,寻找redis的master节点,只要寻找到遍历遍结束;
  • 遍历sentinel,为每个sentinel创建线程监听器;

下面继续探索initPool方法,该方法以初始化setntinel中寻找的master节点为参数,进行初始化jedis与redis的master节点的JedisFactory。

// 该过程主要是为了初始化jedis与master节点的JedisFactory对象
// 一旦JedisFactory被初始化,应用就可以用其创建操作master节点相关的Jedis对象
private void initPool(HostAndPort master) {
// 判断当前的master节点是否与要设置的master相同,currentHostMaster是volatile变量
// 保证线程可见性
if (!master.equals(currentHostMaster)) {
// 如果不相等,则重新设置当前的master节点
currentHostMaster = master;
// 如果factory是空,则利用新的master创建factory
if (factory == null) {
factory = new JedisFactory(master.getHost(), master.getPort(), connectionTimeout,
soTimeout, password, database, clientName);
initPool(poolConfig, factory);
} else {
// 否则更新factory中的master节点
factory.setHostAndPort(currentHostMaster);
// although we clear the pool, we still have to check the
// returned object
// in getResource, this call only clears idle instances, not
// borrowed instances
internalPool.clear();
}
log.info("Created JedisPool to master at " + master);
}
}

initPool中完成了应用于redis的master节点的连接创建,Jedis对象工厂的创建。

这样应用就可以使用JedisSentinelPool的getResource方法获取与master节点对应的Jedis对象对master节点进行读写。这些步骤主要用于应用启动时执行与master节点的初始化操作。但是在应用运行期间,如果sentinel的master发生故障转移,应用如何实现自动切换至新的master节点,这样的功能主要是sentinel监视器MasterListener完成。接下来主要分析MasterListener的实现。

// MasterListener本身是一个线程对象的实现,所以sentinel模式中有几个sentinel进程
// 应用就会为其创建多少个相对应的线程监听,这样主要是为了保证sentinel本身的高可用
protected class MasterListener extends Thread {
// sentinel的名称,应用同样的Redis实例群体可以组建不同的sentinel
protected String masterName;
// 对应的sentinel host
protected String host;
// 对应的端口
protected int port;
// 订阅重试的等待时间,前文中介绍,实现自动故障转移的核心是利用sentinel提供的
// pub/sub API,实现订阅相应类型通道,接受相应的事件通知
protected long subscribeRetryWaitTimeMillis = 5000;
// 与sentinel连接操作的Jedis
protected volatile Jedis j;
// 表示对应的sentinel是否正在运行
protected AtomicBoolean running = new AtomicBoolean(false);
protected MasterListener() {
}
public MasterListener(String masterName, String host, int port) {
super(String.format("MasterListener-%s-[%s:%d]", masterName, host, port));
this.masterName = masterName;
this.host = host;
this.port = port;
}
public MasterListener(String masterName, String host, int port,
long subscribeRetryWaitTimeMillis) {
this(masterName, host, port);
this.subscribeRetryWaitTimeMillis = subscribeRetryWaitTimeMillis;
}
}

实现自动转移至新提升的master节点的逻辑在run方法中

@Override
public void run() {
// 线程第一次启动时,设置sentinel运行标识为true
running.set(true); // 如果该sentinel仍然活跃,则循环
while (running.get()) { // 创建与该sentinel对应的jedis对象,用于操作该sentinel
j = new Jedis(host, port); try {
// 再次检查,因为在以上的操作期间,该sentinel可能会销毁,可以查看shutdown方法
// double check that it is not being shutdown
if (!running.get()) {
break;
} /*
* Added code for active refresh
*/
// 获取sentinel中的master节点
List<String> masterAddr = j.sentinelGetMasterAddrByName(masterName);
if (masterAddr == null || masterAddr.size() != 2) {
log.warn("Can not get master addr, master name: {}. Sentinel: {}:{}.",masterName,host,port);
}else{
// 如果master合法,则调用initPoolf方法初始化与master节点的JedisFactory
initPool(toHostAndPort(masterAddr));
} // 订阅该sentinel的+switch-master通道。+switch-master通道的事件类型为故障转移,切换新的master的事件类型
j.subscribe(new JedisPubSub() {
// redis sentinel中一旦发生故障转移,切换master。就会收到消息,消息内容为新提升的master节点
@Override
public void onMessage(String channel, String message) {
log.debug("Sentinel {}:{} published: {}.", host, port, message); // 解析消息获取新提升的master节点
String[] switchMasterMsg = message.split(" "); if (switchMasterMsg.length > 3) { if (masterName.equals(switchMasterMsg[0])) {
// 将应用的当前master改为新提升的master,初始化。实现应用端的故障转移
initPool(toHostAndPort(Arrays.asList(switchMasterMsg[3], switchMasterMsg[4])));
} else {
log.debug(
"Ignoring message on +switch-master for master name {}, our master name is {}",
switchMasterMsg[0], masterName);
} } else {
log.error(
"Invalid message received on Sentinel {}:{} on channel +switch-master: {}", host,
port, message);
}
}
}, "+switch-master"); } catch (JedisException e) {
// 如果繁盛异常,判断对应的sentinel是否仍然处于运行状态
if (running.get()) {
// 如果是处于运行,则是连接问题,线程睡眠subscribeRetryWaitTimeMillis毫秒,然后while循环继续订阅+switch-master通道
log.error("Lost connection to Sentinel at {}:{}. Sleeping 5000ms and retrying.", host,
port, e);
try {
Thread.sleep(subscribeRetryWaitTimeMillis);
} catch (InterruptedException e1) {
log.error("Sleep interrupted: ", e1);
}
} else {
log.debug("Unsubscribing from Sentinel at {}:{}", host, port);
}
} finally {
j.close();
}
}
}

以上的应用端实现故障发生时自动切换master节点的逻辑,注释已经讲述的非常清晰。这里需要关注的几点问题:

  1. 因为sentinel进程可能有多个,保证自身高可用。所以这里MasterListener对应也有多个,所以对于实现切换master节点是多线程环境。其中优秀的地方在于没有使用任何的同步,只是利用volatile保证可见性。因为对currentMaster和factory变量的操作,都只是赋值操作;

  2. 因为是多线程,所以initPool会被调用多次。一个是应用启动的main线程,还有就是N个sentinel对应的MasterListener监听线程。所以initPool被调用N+1次,同时发生故障转移时,将会被调用N次。但是即使是多次初始化,master的参数都是一样,基本上不会出现线程安全问题;

到这里,Redis的Sentinel模式和Jedis中实现应用端的故障自动转移就探索结束。下面再总结下Redis Sentinel模式在保证高可用的前提下的缺陷。

总结

Redis Setninel模式固然结局了Redis单机的单点问题,实现高可用。但是它是基于主从模式,无论任何主从的实现,其中最为关键的点就是数据一致性。在软件架构中两者数据一致性的实现方式可谓五花八门:

  1. 两者之间进行异步复制数据,保证数据一致性(可软件自实现或者第三方组件进行异步复制);
  2. 同步回写方式。应用写主时,主在back写入从,主再返回应用响应;
  3. 双写方式:应用既写主又写从(但是从一般都设置只读模式);

在主从模式中,实现一致性,大多数是利用异步复制的方式,如:binlog、dumpfile、commandStream等等,且又分为全量和增量方式结合使用。

经过以上描述,提出的问题:

  1. 因为是异步复制,必然就存在一定的时间窗口期间,主从的数据是不一致的,那么就有可能出现,数据不一致的场景(即使很难发生);
  2. 有数据不一致场景,就有可能出现数据丢失问题(如主宕机,从切换为主,但是主的一部分数据未能异步复制,导致从的数据丢失一部分);
  3. sentinel虽然实心了故障转移,但是故障转移也是有一定的时间的,这段时间无主可用;

在使用主从模式中,很多情况下为保证性能,常将master的持久化关闭,所以经常会出现主从全部宕机,当主从自启动后,出现master的键空间为空,从又异步同步主,导致从同步空的过来,导致主从数据都出现丢失!

在Redis Sentinel模式中尽量设置主从禁止自启动,或者主开启持久化功能。

参考

Redis Sentinel Documentation

jedis

Redis(九)高可用专栏之Sentinel模式的更多相关文章

  1. Redis(九)高可用专栏之《简介篇》

    在互联网的大趋势下,用户体验.服务的可用性日趋重要.任何一个服务的不可用,都可能导致连锁式功能故障. 前言 高可用模型的已经逐渐形成一种套路: 主备/主从模式 集群模式 主备/主从模式 至少有两台服务 ...

  2. (六) Docker 部署 Redis 高可用集群 (sentinel 哨兵模式)

    参考并感谢 官方文档 https://hub.docker.com/_/redis GitHub https://github.com/antirez/redis happyJared https:/ ...

  3. Redis高可用集群-哨兵模式(Redis-Sentinel)搭建配置教程【Windows环境】

    No cross,no crown . 不经历风雨,怎么见彩虹. Redis哨兵模式,用现在流行的话可以说就是一个"哨兵机器人",给"哨兵机器人"进行相应的配置 ...

  4. Redis Sentinel安装与部署,实现redis的高可用

    前言 对于生产环境,高可用是避免不了要面对的问题,无论什么环境.服务,只要用于生产,就需要满足高可用:此文针对的是redis的高可用. 接下来会有系列文章,该系列是对spring-session实现分 ...

  5. Redis主从高可用缓存

    nopCommerce 3.9 大波浪系列 之 使用Redis主从高可用缓存   一.概述 nop支持Redis作为缓存,Redis出众的性能在企业中得到了广泛的应用.Redis支持主从复制,HA,集 ...

  6. Redis的高可用详解:Redis哨兵、复制、集群的设计原理,以及区别

    谈到Redis服务器的高可用,如何保证备份的机器是原始服务器的完整备份呢?这时候就需要哨兵和复制. 哨兵(Sentinel):可以管理多个Redis服务器,它提供了监控,提醒以及自动的故障转移的功能. ...

  7. Redis之高可用、集群、云平台搭建

    原文:Redis之高可用.集群.云平台搭建 文章大纲 一.基础知识学习二.Redis常见的几种架构及优缺点总结三.Redis之Redis Sentinel(哨兵)实战四.Redis之Redis Clu ...

  8. Redis + keepalived 高可用群集搭建

    本次实验环境介绍: 操作系统: Centos 7.3 IP : 192.168.10.10 Centos 7.3 IP : 192.168.10.20  VIP    地址   : 192.168.1 ...

  9. Redis Cluster高可用集群在线迁移操作记录【转】

    之前介绍了redis cluster的结构及高可用集群部署过程,今天这里简单说下redis集群的迁移.由于之前的redis cluster集群环境部署的服务器性能有限,需要迁移到高配置的服务器上.考虑 ...

随机推荐

  1. iOS - 常用宏定义和PCH文件知识点整理

    (一)PCH文件操作步骤演示: 第一步:图文所示: 第二步:图文所示: (二)常用宏定义整理: (1)常用Log日志宏(输出日志详细可定位某个类.某个函数.某一行) //=============== ...

  2. Jmeter对服务器进行压力测试

    一.插件准备 下载地址:https://jmeter-plugins.org/downloads/all/ 1.下载插件管理: 2.将管理插件放到jmeter/../ext文件夹中 3.在插件管理中搜 ...

  3. vue全家桶项目应用断断续续的记录

    一.引用axios插件报错 axios使用文档 Cannot read property 'protocol' of undefined 解决方法:在mainjs文件中把axios引入vue的原型函数 ...

  4. es6中,promise使用过程的小总结

    参考资料传送门:戳一戳 1.是什么 Promise是异步编程的一种解决方案,有三种状态:pending(进行中).fulfilled(已成功)和rejected(已失败); 一般成功了状态用resol ...

  5. PHP数组相关算法

    一.排序算法 1. 冒泡排序 2. 选择排序 二.查找算法 1. 遍历 2. 二分查找

  6. SQLServer常用快捷键汇总

    菜单激活键盘快捷键 操作 SQL Server 2017 SQL Server 2008 R2 移到 SQL Server Management Studio 菜单栏 Alt Alt 激活工具组件的菜 ...

  7. 人工智能头条(公开课笔记)+AI科技大本营——一拨微信公众号文章

    不错的 Tutorial: 从零到一学习计算机视觉:朋友圈爆款背后的计算机视觉技术与应用 | 公开课笔记 分享人 | 叶聪(腾讯云 AI 和大数据中心高级研发工程师) 整    理 | Leo 出   ...

  8. zzPony.ai 的基础架构挑战与实践

    本次分享将从以下几个方面介绍: Pony.ai 基础架构做什么 车载系统 仿真平台 数据基础架构 其他基础架构 1. Pony.ai 基础架构 首先给大家介绍一下 Pony.ai 的基础架构团队做什么 ...

  9. luoguP4213 【模板】杜教筛(Sum)杜教筛

    链接 luogu 思路 为了做hdu来学杜教筛. 杜教筛模板题. 卡常数,我加了register居然跑到不到800ms. 太深了. 代码 // luogu-judger-enable-o2 #incl ...

  10. 前端Vue项目——首页/课程页面开发及Axios请求

    一.首页轮播图 1.elementUI走马灯 elementUI中 Carousel 走马灯,可以在有限空间内,循环播放同一类型的图片.文字等内容. 这里使用指示器样式,可以将指示器的显示位置设置在容 ...