本文讲述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-图片手势,上传照片

    代码: RootViewController.h #import <UIKit/UIKit.h> @interface RootViewController : UIViewControl ...

  2. MVC+knocKout.js 实现下拉框级联

    数据库:部门表和员工表 在控制器里面的操作: public ActionResult Index3() { ViewBag.departments = new SelectList(getDepart ...

  3. 1 NFS高可用解决方案之DRBD+heartbeat搭建

    preface NFS作为业界常用的共享存储方案,被众多公司采用.我司也不列外,使用NFS作为共享存储,为前端WEB server提供服务,主要存储网页代码以及其他文件. 高可用方案 说道NFS,不得 ...

  4. POJ 2773 Happy 2006【GCD/欧拉函数】

    根据欧几里德算法,gcd(a,b)=gcd(a+b*t,b) 如果a和b互质,则a+b*t和b也互质,即与a互质的数对a取模具有周期性. 所以只要求出小于n且与n互质的元素即可. #include&l ...

  5. CocoaPods一个Objective-C第三方库的管理利器

    转:http://blog.csdn.net/totogo2010/article/details/8198694 介绍: 开发应用的时候第三方的库是不可缺少的,能提高开发的效率. 一些经常用到的库, ...

  6. Tcp/Ip协议族简单解读及网络数据包/报/帧数据格式及封装及解包;

    http://www.creseek.cn/products-install/install_on_bsd_linux/ 中文检索 离线cloudera ecosystem components: h ...

  7. myeclipse安装svn插件的多种方式

          开发者服务评测征文 十万现金悬赏大神 方法一:在线安装 1.打开HELP->MyEclipse Configuration Center.切换到SoftWare标签页. 2.点击Ad ...

  8. 基于gitHub+hexo搭建的个人博客

    文章导航 前期准备 安装hexo 修改hexo主题 自定义主题 部署本地文件到github查看 我的第一篇博客 前期准备 下载安装git命令行工具.node及npm环境 注册自己的GitHub账号 安 ...

  9. 实现对ASP.NETMvc及Asp.NetCore的权限控制

    AccessControlHelper Build Status Intro 由于项目需要,需要在 基于 Asp.net mvc 的 Web 项目框架中做权限的控制,于是才有了这个权限控制组件. 项目 ...

  10. Linux进程和任务管理

    process 进程 进程-线程等 Job 前台--后台: 后台任务"与"前台任务"的本质区别只有一个:是否继承标准输入 联机--脱机 -以及crontab 脱机管理主要 ...