在Redis复制的基础上(不包括由Redis Cluster或Redis Sentinel做为附加层提供的高可用性功能),有一个很是简单的使用和配置leader follower(主从)复制:它容许从属Redis实例准确主实例的副本。每次连接断开时,slave将自动从新链接到master,而且不管master发生什么状况,它都会尝试成为它的精确副本。java
该系统使用三种主要机制:redis
Redis默认使用异步复制,即低延迟和高性能,是绝大多数Redis用例的天然复制模式。可是,Redisslave异步确认它们与master按期收到的数据量。所以主设备不会每次等待从设备处理命令,可是若是须要,它知道哪一个从设备已经处理了什么命令。这容许具备可选的同步复制。算法
客户端可使用WAIT命令请求某些数据的同步复制。可是,WAIT只能确保在其余Redis实例中存在指定数量的已确认副本,但它不会将一组Redis实例转换为具备强一致性的CP系统:在故障转移期间,确认的写入仍然可能丢失,具体取决于关于Redis持久性的确切配置。然而,对于WAIT,在失败事件以后丢失写入的几率大大下降到某些难以触发的故障模式。数据库
您能够查看Sentinel或Redis集群文档,以获取有关高可用性和故障转移的更多信息。本文档的其他部分主要描述了Redis基本复制的基本特征。安全
如下是有关Redis复制的一些很是重要的事实:bash
redis.conf
以免持久存储到磁盘,而后链接配置为save from time to time的从服务器,或者启用AOF。可是,必须当心处理此设置,由于从新启动的主服务器将以空数据集开始:若是从服务器尝试与其同步,则从服务器也将被清空。每一个Redis主服务器都有一个replication ID:它是一个大的伪随机字符串,用于标记数据集的给定叙述。每一个主服务器还会为生成的每一个复制流字节递增一个偏移量,以便将其发送到从属服务器,方便使用修改数据集的新更改来更新从属服务器的状态。即便没有实际链接的slave,复制偏移也会增长,以下:服务器
Replication ID, offset
复制代码
用来标识主数据集的确切版本。网络
命令./redis-cli -p 6379 -a 你的密码 info server | grep run
能够查看具体的id:并发
当slave链接到master时,它们使用PSYNC
命令发送它们的旧主replication ID以及它们到目前为止处理的偏移量。这样master只用发送所需的增量部分。可是,若是主缓冲区中没有足够的backlog,或者从属设备指不知道历史记录(复制ID),则发生彻底从新同步:在这种状况下,从属设备将得到数据集的完整副本, 从头开始。less
这是彻底同步在更多细节中的工做方式:
主服务器启动后台保存过程以生成RDB文件。同时它开始缓冲从客户端收到的全部新写命令。后台保存完成后,主服务器将数据库文件传输到从服务器,从服务器将其保存在磁盘上,而后将其加载到内存中。而后,主设备将全部缓冲的命令发送到从设备。这是做为命令流完成的,而且与Redis协议自己的格式相同。
您能够经过telnet本身尝试。在服务器执行某些工做时链接到Redis端口并发出SYNC命令。您将看到批量传输,而后主服务器接收的每一个命令都将在telnet会话中从新发出。实际上,较新的Redis实例再也不使用SYNC旧协议,但其仍然存在向后兼容性:它不容许部分从新同步,所以如今用PSYNC
代替。
如前所述,当主 - 从链路因为某种缘由而关闭时,从设备可以自动从新链接。若是主设备接收到多个并发从属同步请求,它将执行单个后台保存以便为全部这些请求提供服务。
1)bgsave时间。
2)RDB文件网络传输时间。
3)从节点清空数据时间。
4)从节点加载RDB的时间。
5)可能的AOF重写时间。
配置基本Redis复制很简单:只需将如下行添加到从属配置文件:
slaveof 192.168.1.1 6379
复制代码
固然,您须要将192.168.1.1 6379替换为您的主IP地址(或主机名)和端口。或者,您能够调用SLAVEOF命令,主控主机将启动与slave的同步。
还有一些参数用于调整主机在内存中采起的replication backlog以执行部分从新同步。
可使用repl-diskless-sync配置参数启用无盘复制。传输开始后,等待在第一个slave以后到达的更多slaves的延迟由repl-diskless-sync-delay 参数控制。
1)进入redis目录,复制redis.conf
文件为redis-6380.conf
cp redis.conf redis-6380.conf
复制代码
2)修改redis-6380.conf的端口号,保存的rdb文件名,日志名等参数。
3)找到REPLICATION模块,修改以下:
replicaof 127.0.0.1 6379
4)若是主节点redis设置了密码,修改masterauth属性:
在箭头处填上密码。
5)启动从结点:
./redis-server ../redis-6380.conf
复制代码
6)使用命令./redis-cli -p 6380 -a 你的密码 info replication
查看从节点状态:
能够发现role为slave,而且master_link_status为up,说明启动成功。说明一下,上图的slave_repl_offset就是所谓的偏移量。
尝试使用redis-cli在主节点中写入一个值:
./redis-cli -p 6379 -a 你的密码
> set hello world
复制代码
进入从节点,输入get hello
,能够发现已经可以正确获取值。
7)可使用命令slaveof no one
取消从节点模式。
从Redis 2.6开始,slave支持默认启用的只读模式。此行为由slave-read-only
redis.conf文件中的选项控制,能够在运行时使用CONFIG SET启用和禁用。
只读slave将拒绝全部写入命令,所以因为错误而写入slave是不可能的。这并不意味着该功能旨在将从属实例暴露给互联网,或者更广泛地意味着将不受信任的客户端存在的网络,由于管理命令喜欢DEBUG
或CONFIG
仍然启用。可是,经过使用该rename-command
指令禁用redis.conf中的命令,能够提升只读实例的安全性。
您可能想知道为何能够恢复只读设置并具备能够经过写入操做做为目标的从属实例。若是slave和master从新同步或者slave从新启动,那么这些写操做将被丢弃,但有一些合法的用例可用于在可写slave中存储短暂数据。
例如,计算慢速设置或排序集合操做并将它们存储到本地key中是屡次观察可写slave的用例。
可是请注意,版本4.0以前的可写slave没法设置到期时间。这意味着若是您使用EXPIRE或为key设置最大TTL的其余命令,key将泄漏,虽然您在使用读取命令访问key时可能再也看不到它,您将在key计数中看到它仍在使用内存。所以,一般混合可写slave(之前的版本4.0)和带有TTL的键会产生问题。
Redis 4.0 RC3及更高版本彻底解决了这个问题,如今可写的从设备可以像主设备那样用TTL删除key,但用DB编号大于63的key除外(但默认状况下Redis实例只有16个数据库)。
另请注意,因为Redis 4.0从属写入仅是本地的,而且不会传播到附加到实例的子从属。相反,子slave将始终接收与顶层master向中间slave发送的复制流相同的复制流。例如,在如下设置中:
A ---> B ---> C
复制代码
即便B
是可写的,C
也不会看到B
写入,而是具备与主实例相同的数据集A
。
1)读写分离:读流量分摊到从节点,写流量分摊到主节点。
2)可能遇到的问题
一、复制数据延迟。
二、读到过时数据(3.2已解决)。
三、从节点故障。
1)第一次全量复制
一、第一次不可避免。
二、小主节点、低峰。
2)节点运行ID不匹配
一、主节点重启(运行ID变化)。
二、故障转移,例如哨兵或集群。
3)复制积压缓冲区不足
一、网络中断,部分复制没法知足。
二、增大复制缓冲区配置rel_backlog_size,网络“加强”。
1)单节点复制风暴
问题:主节点重启,多从节点复制。
解决:更换复制拓扑。
2)单机器复制风暴
问题:机器宕机后,大量全量复制。
解决:主节点分散多机器。
Redis Sentinel为Redis提供高可用性。实际上,这意味着使用Sentinel能够建立一个Redis部署,能够在没有人为干预的状况下抵御某些类型的故障。
Redis Sentinel还提供其余附属任务,如监控,通知,并充当客户端的配置提供程序。
这是宏观级别的Sentinel功能的完整列表(即大图):
Redis Sentinel是一个分布式系统:
Sentinel自己设计为在多个Sentinel进程协同工做的配置中运行。让多个Sentinel进程协做的优点以下:
Sentinels,Redis实例(主服务器和从服务器)以及链接到Sentinel和Redis的客户端的总和也是具备特定属性的更大的分布式系统。在本文档中,将逐步介绍概念,从了解Sentinel的基本属性所需的基本信息,到更复杂的信息(可选),以了解Sentinel的工做原理。
Sentinel的当前版本称为Sentinel 2。它是使用更强大和更简单的预测算法(本文档中解释)重写初始Sentinel实现。
自Redis 2.8发布Redis Sentinel的稳定版本。
在不稳定的分支中进行了新的开发,一旦它们被认为是稳定的,新的特征有时会被反导到最新的稳定分支中。
Redis 2.6附带的Redis Sentinel版本1已弃用,不该使用。
若是您正在使用redis-sentinel
可执行文件(或者若是您的可执行文件具备该名称的符号连接redis-server
),则可使用如下命令行运行Sentinel:
redis-sentinel /path/to/sentinel.conf
复制代码
或者,您能够直接使用redis-server
在Sentinel模式下启动它的可执行文件:
redis-server /path/to/sentinel.conf --sentinel
复制代码
两种方式都是同样的。
可是**,在运行Sentinel时必须**使用配置文件,由于系统将使用此文件以保存从新启动时将从新加载的当前状态。若是没有给出配置文件或配置文件路径不可写,Sentinel将拒绝启动。
默认状况下,Sentinels会侦听与TCP端口26379的链接,所以要使 Sentinels正常工做,必须打开服务器的端口26379 以接收来自其余Sentinel实例的IP地址的链接。不然,Sentinels没法通话,也没法就该怎么作达成一致,所以永远不会执行故障转移。
Redis源代码分发包含一个名为sentinel.conf
的文件 ,它是一个能够用来配置Sentinel的自我记录的示例配置文件,可是典型的最小配置文件以下所示:
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 60000
sentinel failover-timeout mymaster 180000
sentinel parallel-syncs mymaster 1
sentinel monitor resque 192.168.1.3 6380 4
sentinel down-after-milliseconds resque 10000
sentinel failover-timeout resque 180000
sentinel parallel-syncs resque 5
复制代码
您只须要指定要监视的主服务器,为每一个单独的主服务器(可能有任意数量的从服务器)提供不一样的名称。无需指定可被自动发现的slaves。Sentinel将自动更新配置有关slaves的其余信息(以便在从新启动时保留信息)。每次在故障转移期间将slave升级为master而且每次发现新的Sentinel时,也会重写该配置。
上面的示例配置基本上监视两组Redis实例,每组实例由主设备和未定义数量的从设备组成。一组实例被调用mymaster
,另外一组被调用resque
。
sentinel monitor
语句参数的含义以下:
sentinel monitor <master-group-name> <ip> <port> <quorum>
复制代码
为清楚起见,让咱们逐行检查配置选项的含义:
第一行用于告诉Redis监视一个名为mymaster的主服务器,它位于地址127.0.0.1和端口6379,链接数为2。一切都很明显,可是quorum参数:
所以,例如,若是您有5个Sentinel进程,而且给定主服务器的quorum设置为值2,则会发生如下状况:
实际上,这意味着在故障期间,若是大多数Sentinel进程没法通话(在少数分区中也没有故障转移),Sentinel永远不会启动故障转移。
其余选项几乎老是如下列形式:
sentinel <option_name> <master_name> <option_value>
复制代码
并用于如下目的:
down-after-milliseconds
是对于Sentinel开始认为它已关闭而且实例不能到达的(不管是不回复咱们的PING仍是回复错误)以毫秒为单位的时间。parallel-syncs
设置可在同一故障转移后从新配置以使用新主服务器的从服务器数。数字越小,故障转移过程完成所需的时间就越多,可是若是从属服务器配置为提供旧数据,则可能不但愿全部从属服务器同时与主服务器从新同步。虽然复制过程对于从属设备大部分是非阻塞的,可是有一段时间它中止从主设备加载批量数据。您可能但愿经过将此选项设置为值1来确保一次只能访问一个slave。其余选项在本文档的其他部分中进行了描述,并记录在sentinel.conf
Redis发行版附带的示例文件中。
可使用该SENTINEL SET
命令在运行时修改全部配置参数。有关详细信息,请参阅“ **在运行时从新配置Sentinel”**部分。
如今您已了解Sentinel的基本信息,您可能想知道应该在哪里放置Sentinel进程,须要多少Sentinel进程等等。本节介绍几个示例部署。
请注意,咱们永远不会显示仅使用两个Sentinels的设置,由于Sentinels老是须要与大多数人交谈才能启动故障转移。
+----+ +----+
| M1 |---------| R1 |
| S1 | | S2 |
+----+ +----+
Configuration: quorum = 1
复制代码
请注意,为了执行不一样的故障转移,须要多数,而后将最新配置传播到全部Sentinels。另请注意,在没有任何协议的状况下,在上述设置的单个方面进行故障转移的能力将很是危险:
+----+ +------+
| M1 |----//-----| [M1] |
| S1 | | S2 |
+----+ +------+
复制代码
在上面的配置中,咱们以彻底对称的方式建立了两个主服务器(假设S2能够在未经受权的状况下进行故障转移)。客户端能够无限期地向双方写入,而且没法理解分区什么时候恢复正确的配置,以防止永久性的裂脑状况。
因此请始终在三个不一样的盒子中至少部署三个Sentinels。
这是一个很是简单的设置,其优势是易于调整以得到额外的安全性。它基于三个框,每一个框都运行Redis进程和Sentinel进程。
+----+
| M1 |
| S1 |
+----+
|
+----+ | +----+
| R2 |----+----| R3 |
| S2 | | S3 |
+----+ +----+
Configuration: quorum = 2
复制代码
若是主M1发生故障,S2和S3将赞成故障,而且可以受权故障转移,使客户可以继续。
在每一个Sentinel设置中,Redis被异步复制,老是存在丢失一些写入的风险,由于给定的已确认写入可能没法到达被提高为主设备的从设备。可是在上面的设置中,因为客户端使用旧主服务器进行分区,所以风险较高,以下图所示:
+----+
| M1 |
| S1 | <- C1 (writes will be lost)
+----+
|
/
/
+------+ | +----+
| [M2] |----+----| R3 |
| S2 | | S3 |
+------+ +----+
复制代码
在这种状况下,网络分区隔离了旧的主M1,所以从属R2被提高为主。可是,与旧主服务器位于同一分区的客户端(如C1)可能会继续将数据写入旧主服务器。这个数据将永远丢失,由于当分区将愈合时,主机将被从新配置为新主机的从机,丢弃其数据集。
使用如下Redis复制功能能够缓解此问题,若是主服务器检测到再也不可以将其写入传输到指定数量的从服务器,则容许中止接受写入。
min-slaves-to-write 1
min-slaves-max-lag 10
复制代码
使用上述配置(请参阅redis.conf
Redis发行版中的自注释示例以获取更多信息)Redis实例做为主服务器时,若是没法写入至少1个从服务器,将中止接受写入。因为复制是异步的,所以没法实际写入意味着从属设备已断开链接,或者未向咱们发送超过指定max-lag
秒数的异步确认。
使用此配置,上例中的旧Redis主M1将在10秒后变为不可用。当分区恢复时,Sentinel配置将收敛到新的配置,客户端C1将可以获取有效配置并继续使用新主设备。
可是没有免费的午饭。经过这种改进,若是两个从属设备关闭,主设备将中止接受写入。这是一个折衷。
1)将三个redis(一主二从)启动。
2)配置sentinel.conf
文件:
一、去掉protected-mode no的注释,即让它生效
二、将daemonize改为yes
三、修改logfile为26379.log
四、写入redis密码:sentinel auth-pass mymaster 你的密码(注意这里须要将其配置在sentinel monitor mymaster 你的服务器ip 6379 2语句之下,否则会报“No such master with specified name.”的错误。
五、修改dir
3)进入src目录,使用命令./redis-sentinel ../sentinel.conf
进行启动。
4)使用命令:
./redis-cli -p 26379
>info
复制代码
获得结果以下:
5)复制sentinel.conf为sentinel-26380.conf与sentinel-26381.conf,修改端口等信息,重复上述步骤。而且须要将myid注释,最终,sentinels的数目将等于3。
1)Sentinel地址集合。
2)masterName。
3)不是代理模式。
package com.lamarsan.sentinel.util;
import redis.clients.jedis.*;
import redis.clients.jedis.exceptions.JedisConnectionException;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
/**
* className: RedisUtil
* description: TODO
*
* @author hasee
* @version 1.0
* @date 2018/12/26 10:57
*/
public class RedisUtil {
private static final Logger myLogger = Logger.getLogger("com.lamarsan.sentinel.util");
private static JedisSentinelPool pool = null;
static {
try {
Set<String> sentinels = new HashSet<String>();
sentinels.add("你的ip:26380");
sentinels.add("你的ip.140:26379");
sentinels.add("你的ip:26381");
String masterName = "mymaster";
String password = "123456";
JedisPoolConfig config = new JedisPoolConfig();
config.setMinIdle(8);
config.setMaxTotal(100);
config.setMaxIdle(100);
config.setMaxWaitMillis(10000);
pool = new JedisSentinelPool(masterName, sentinels, config, password);
try {
pool.getResource();
} catch (JedisConnectionException e) {
myLogger.info(e.getMessage());
e.printStackTrace();
}
} catch (Exception e) {
myLogger.info(e.getMessage());
e.printStackTrace();
}
}
private static void returnResource(JedisSentinelPool pool, Jedis jedis) {
if (jedis != null) {
jedis.close();
}
}
/**
* <p>经过key获取储存在redis中的value</p>
* <p>并释放链接</p>
*
* @param key
* @return 成功返回value 失败返回null
*/
public static String get(String key) {
Jedis jedis = null;
String value = null;
try {
jedis = pool.getResource();
value = jedis.get(key);
} catch (Exception e) {
if (jedis != null) {
jedis.close();
}
e.printStackTrace();
} finally {
returnResource(pool, jedis);
}
return value;
}
/**
* <p>向redis存入key和value,并释放链接资源</p>
* <p>若是key已经存在 则覆盖</p>
*
* @param key
* @param value
* @return 成功 返回OK 失败返回 0
*/
public static String set(String key, String value) {
Jedis jedis = null;
try {
jedis = pool.getResource();
return jedis.set(key, value);
} catch (Exception e) {
if (jedis != null) {
jedis.close();
}
e.printStackTrace();
return "0";
} finally {
returnResource(pool, jedis);
}
}
.....
}
复制代码
package com.lamarsan.sentinel;
import com.lamarsan.sentinel.util.RedisUtil;
/**
* className: Test
* description: TODO
*
* @author hasee
* @version 1.0
* @date 2019/9/13 15:54
*/
public class Test {
public static void main(String[] args) throws InterruptedException {
RedisUtil.setnx("hello", "world");
while (true) {
Thread.sleep(10000);
String result = RedisUtil.get("hello");
System.out.println(result);
}
}
}
复制代码
1)每10秒每一个sentinel对master和slave执行info
一、发现slave结点。
二、确认主从关系。
2)每2秒每一个sentinel经过master结点的channel交换信息(pub/sub)
一、经过_sentinel_:hello频道交互。
二、交互对节点的”见解“和自身信息。
3)每1秒每一个sentinel对其余sentinel和redis执行ping。
1)机器下线:例如过保等状况。
2)机器性能不足:例如CPU、内存、硬盘、网络等。
3)节点自身故障:例如服务不稳定等。
sentinel failover <masterName>
临时下线仍是永久下线,例如是否作一些清理工做,也要考虑读写分离的状况。