前言
在实际生产中,不可能Redis是以单机启动的,因为这样的服务是非常不稳定的,现在的项目首先提倡高可用,而高可用最佳的使用方式就是分布式部署(多部署几份以分摊意外的分享),而集群部署和主从复制是一个意思,Redis是通过主从复制来完成集群部署的,哨兵机制(Sentinel)又是在主从复制上的又一级别的改进了!
一、主从复制
主从复制也就是当主服务(Master)更新时,从节点(Slave)也随之更新,这块也有一个数据库概念不得不提的,叫做读写分离,这一切的目的呢,都是为了可以更好地提高服务,保证高可用!
那么看一下Redis主从复制有什么优点:
- 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式
- 故障恢复:当主节点发生了问题时,可以由从节点提供服务,实现快速故障恢复,也是一种服务的冗余
- 负载均衡:在主从复制上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务,分摊服务器的负载;尤其是在写少读多的情况下,通过多个节点来分担读负载,可以大大提高Redis服务器的并发量
再次说一下Redis不可以单机启动的原因:
- 结构上,单个Redis服务容易发生单点故障,并且一台服务器需要处理所有的请求负载,压力过大
- 容量上,Redis是使用的内存存储,一台服务器的内存是有限的,哪怕超过了百G内存,也不可能将所有的数据存放下,一般来说单台Redis最大使用内存不应该超过20G
注意:
每一台Redis服务默认自己就是Master,一个Master可以有多个Slave,但是Slave只可以有一个Master
这其中涉及到的命令如下
info replication # 显示当前redis节点状态
slaveof ip part # 设置从机,ip:主机ip;part:主机part
slaveof on one # 解除当前Redis服务的Slave角色
这两个命令就可以完成这个主从复制的部署了,因为我只有一台服务器,所以就不以服务器作为测试了,如果在一台机器上测试的话,其实就是使用不同的端口号去启用。不过我没有采用这种办法,我是在本地起了三个虚拟机,他们分别拥有三个虚拟的ip端口,实现下图的关系
主从分离,在Master写入,其他Slave读取
(1)在shell中连接三台机器
分别安装Redis并且配置好配置文件,如果这个不太清楚的,看我第一节写的linux安装Redis教程,这里开启Redis服务,并且查看一下Redis的状态,可以看出来现在是Master,其他两台机器和这个状态一致
(2)其他两台Redis服务分别设置成Slave
自此就设置完了,配置这个是非常简单的,让我们测试一下以及研究一下目前存在的问题
(3)综合测试
第一需要注意的是,Slave不可以进行写操作
这里试想一下,在这个简单的三个集群中,会不会出现以下问题呢?
- Master宕机了,其他的状态是什么样的呢?
Master宕机后,整个集群还可以正常的进行读操作,但是因为没有Master,所以无法进行写操作,此时如果Java程序连接整个集群的话,那么应该可以正常读取,写操作时报错! - 当Master重启后,这个集群的状态又是什么样的呢?
当Master重启后,整个集群又恢复了正常运行了,数据因为有RDB持久化的原因,还是和以前一致!我人为的制造了数据丢失(删除dump.rdb文件),这个时候Master重启后,数据为空,其他Slave也为空!所以可以判断出这个数据的数据是以Master为主,如何避免因为Master的数据丢失,而导致整个集群数据丢失呢?其实这个在哨兵模式中得到了解决,简单来看就是选举其他Slave为Master,就可以避免这个问题。 - 其中一个Slave宕机了,这个集群的状态是什么样的呢?
其中一个Slave宕机了,对整个集群来说没有任何影响!及时将它重启即可 - 当这个Slave重启后,这个集群的状态是什么样的呢?
刚刚宕机Slave重启后,并不会再主动加入到这个集群中,需要使用命令手动再此加入到集群中!当然这个和是使用命令定义的集群关系有关系,也就是说命令定义集群关系是一种临时关系,当重启Redis服务后,这个关系就会失效,如何定义一个长期有效的关系呢?这个Redis也是支持的,就是当我们把关系写到redis.conf配置文件中的时候,这种关系就会随着Redis重启而在此加入到Redis集群,下面看一下这个相关配置
关于这些问题,我直接答案写到这了,好奇的也可以自己去探索,这些结论也是我实践出来的,若有问题,望指教
(4)主从复制原理
Slave启动成功首次连接Master后会发送一个sync同步命令,Master接受到命令后,启动存盘进程去收集所有修改数据集的命令,再执行完毕后,Master会将这个文件发送给Slave,完成一次完全同步(全量复制)
全量复制: Slave收到Master的数据库文件,将其完全存盘到内存中
增量复制:Master持续将新的修改命令依次传给Slave,完成sync
二、哨兵模式
关于哨兵模式这里,我整理了几篇文章来讲解一下这部分的内容,我认为这块简单的使用是非常方便的,在网络上摘抄一些配置内容就可以启动了,但是具体到其中的观念还是挺多的!以下内容不涉及哨兵相关的算法,这块超出了我目前探索的范围
1. 概念
(1)哨兵的概述和优缺点
哨兵机制(Sentinel)是基于Redis的主从复制的,也是为了解决主从复制所带来的问题,有句话说得好,程序里边就没有加一层解决不了的问题,就好比主从复制很方便,但是如果集群中某个机器宕机了,这就需要人工去操作重启和配置了,这里就是很不智能的地方!
那如何解决呢?当然是启用另外一个进程或者服务去监听Redis集群,如果发现宕机或者不可用,那么自动进行配置!而不是人工进行配置,这样就可以解决问题了
就好比如上图,当又有一个问题来了,那要是哨兵这个机器宕机了呢?这就是一个高可用的问题,凡是高可用的问题,大多数集群必定可以解决,搭建一个Sentinel集群就好了
所以程序的本质的处理方式都是相同的,或许语法不太相同,但是解决方案大同小异
优点:
- 哨兵模式是基于主从模式的,所有主从的优点,哨兵模式都有。
- 主从可以自动切换,系统更健壮,可用性更高
缺点:
- redis较难支持在线扩容,在集群容量达上限时在线扩容变的很复杂。
####(2)SDOWN和ODOWN
上面了解了什么是Sentinel,这里立刻就引入了这两个名词,主要是在Sentinel监控的过程中,是如何认为Master节点宕机了呢?首先Sentinel肯定是实时监控着Master节点,这个肯定没错,当Sentinel对Master节点发送ping命令反应超时或者回应错误的时候,那么这个Sentinel节点就认为Master节点SDOWN(主观下线),因为一般都是有Sentinel集群的,所以一个Sentinel认为Master节点SDOWN肯定是不行的,只要超过了(配置的参数)预定的Sentinel节点都认为这个Master节点SDOWN,那么这个节点才真正的ODOWN(客观下线),随后才会选举出一个Sentinel节点去执行failover。
需要注意的是: SDOWN和ODOWN这种概念只适用于Master节点,Slave节点并不适用
(2)版本号
- 当确定ODOWN并且一个sentinel被授权后,它将会获得宕掉的master的一份最新配置版本号,当failover执行结束以后,这个版本号将会被用于最新的配置。因为大多数sentinel都已经知道该版本号已经被要执行failover的sentinel拿走了,所以其他的sentinel都不能再去使用这个版本号。这意味着,每次failover都会附带有一个独一无二的版本号。我们将会看到这样做的重要性。
- sentinel集群都遵守一个规则:如果sentinel A推荐sentinel B去执行failover,B会等待一段时间后,自行再次去对同一个master执行failover,这个等待的时间是通过failover-timeout配置项去配置的。从这个规则可以看出,sentinel集群中的sentinel不会再同一时刻并发去failover同一个master,第一个进行failover的sentinel如果失败了,另外一个将会在一定时间内进行重新进行failover
- 总结一下,Sentinel必须被其他Sentinel选举并授权后才能进行failover(故障转移),并且在failover时,它是有一个配置版本号的,这个是唯一的!当它成功配置后,会将这个配置应用给所有的节点。如果失败了,那么另外一个Sentinel重新获取一个唯一的版本号再次进行failover
(3)配置传播
- 一旦一个sentinel成功地对一个master进行了failover,它将会把关于master的最新配置通过广播形式通知其它sentinel,其它的sentinel则更新对应master的配置。
- 一个faiover要想被成功实行,sentinel必须能够向选为master的slave发送SLAVEOF NO ONE命令,然后能够通过INFO命令看到新master的配置信息。
- 当将一个slave选举为master并发送SLAVEOF NO ONE后,即使其它的slave还没针对新master重新配置自己,failover也被认为是成功了的,然后所有sentinels将会发布新的配置信息。
- 举个例子:
当SentinelA被推选出来进行failover,此时它的最新版本号是2,其他sentinel版本号是1,那么当它failover成功后,会将最新配置2同步到其他的节点,其他节点接受到最新配置后会和自己的配置进行对比,发现最新配置版本号更高后,更新配置
这意味着sentinel集群保证了第二种活跃性:一个能够互相通信的sentinel集群最终会采用版本号最高且相同的配置。
(4)Sentinel之间的自动发现机制
- 虽然sentinel集群中各个sentinel都互相连接彼此来检查对方的可用性以及互相发送消息。但是你不用在任何一个sentinel配置任何其它的sentinel的节点。因为sentinel利用了master的发布/订阅机制去自动发现其它也监控了统一master的sentinel节点。通过向名为__sentinel__:hello的管道中发送消息来实现。
- 同样,你也不需要在sentinel中配置某个master的所有slave的地址,sentinel会通过询问master来得到这些slave的地址的。
- 每个sentinel通过向每个master和slave的发布/订阅频道__sentinel__:hello每秒发送一次消息,来宣布它的存在。
- 每个sentinel也订阅了每个master和slave的频道__sentinel__:hello的内容,来发现未知的sentinel,当检测到了新的sentinel,则将其加入到自身维护的master监控列表中。
- 每个sentinel发送的消息中也包含了其当前维护的最新的master配置。如果某个sentinel发现自己的配置版本低于接收到的配置版本,则会用新的配置更新自己的master配置。
- 在为一个master添加一个新的sentinel前,sentinel总是检查是否已经有sentinel与新的sentinel的进程号或者是地址是一样的。如果是那样,这个sentinel将会被删除,而把新的sentinel添加上去。
(5)Slave之间的选举和优先级
这里涉及到了,Sentinel是如何来判断在Slave中选举出Master的,主要是根据以下几点
- 与master断开连接的次数
- Slave的优先级
- 数据复制的下标(用来评估slave当前拥有多少master的数据)
- 进程ID
关于断开连接次数是很重要去否决一个Slave成为Master的条件
如果一个slave与master失去联系超过10次,并且每次都超过了配置的最大失联时间(down-after-milliseconds),如果sentinel在进行failover时发现slave失联,那么这个slave就会被sentinel认为不适合用来做新master的。
更严格的定义是,如果一个slave持续断开连接的时间超过
(down-after-milliseconds * 10) + milliseconds_since_master_is_in_SDOWN_state
就会被认为失去选举资格。
符合上述条件的slave才会被列入master候选人列表,并根据以下顺序来进行排序:
- sentinel首先会根据slaves的优先级来进行排序,优先级越小排名越靠前。
- 如果优先级相同,则查看复制的下标,哪个从master接收的复制数据多,哪个就靠前。
- 如果优先级和下标都相同,就选择进程ID较小的那个。
一个redis无论是master还是slave,都必须在配置中指定一个slave优先级。要注意到master也是有可能通过failover变成slave的。
如果一个redis的slave优先级配置为0,那么它将永远不会被选为master。但是它依然会从master哪里复制数据。
(6)Sentinel工作原理
- Sentinel集群通过给定的配置文件发现master,启动时会监控master。通过向master发送info信息获得该服务下面的所有从服务器。
- Sentinel集群通过命令连接向被监控的主从服务器发送hello信息(每秒一次),该信息包括Sentinel本身的ip、端口、id等内容,以此来向其他Sentinel宣告自己的存在。
- Sentinel集群通过订阅连接接收其他Sentinel发送的hello信息,以此来发现监视同一个主服务器的其他Sentinel;集群之间会互相创建命令连接用于通信,因为已经有主从服务器作为发送和接收hello信息的中介,Sentinel之间不会创建订阅连接。
- Sentinel集群使用Sentinel命令来检测实例的状态,如果指定的时间内(down-after-milliseconds)没有回复或者返回错误回复,那么该实例被判为主观下线SDOWN。
- 当failover主备切换被触发后,failover并不会马上进行,还需要Sentinel集群中另外quorum个其他Sentinel授权,成功后进入ODOWN客观下线状态,之后再进行failover。
- Sentinel向选为master的slave发送slaveof no one 命令,选择slave的条件是上面提到的选举条件
- Sentinel被授权后会获得宕机的master的一份最新配置版本号(config-epoch)当failover结束后,这个版本号将会用于最新的配置,通过广播的形式通知其他Sentinel,其它的Sentinel则更新对应的master配置。
(7)网络隔离时的一致性
例子:
- 有三个主机,每个主机分别运行一个redis和一个sentinel。初始状态下redis3是master, redis1和redis2是slave。
- 之后redis3所在的主机网络不可用了,sentinel1和sentinel2启动了failover并把redis1选举为master。
- Sentinel集群的特性保证了sentinel1和sentinel2得到了关于master的最新配置。但是sentinel3依然是旧的配置,因为它与外界隔离了。
当网络恢复以后,我们知道sentinel3将会更新它的配置。但是,如果客户端所连接的master被网络隔离,会发生什么呢?
- 客户端将依然可以向redis3写数据,但是当网络恢复后,redis3就会变成redis的一个slave,那么,在网络隔离期间,客户端向redis3写的数据将会丢失。(主要是主从复制的特性)
- 因为redis采用的是异步复制,在这样的场景下,没有办法避免数据的丢失。然而,你可以通过以下配置来配置redis3和redis1,使得数据不会丢失。
min-slaves-to-write 1
min-slaves-max-lag 10
通过上面的配置,当一个redis是master时,如果它不能向至少一个slave写数据(上面的min-slaves-to-write指定了slave的数量),它将会拒绝接受客户端的写请求。
于复制是异步的,master无法向slave写数据意味着slave要么断开连接了,要么不在指定时间内向master发送同步数据的请求了(上面的min-slaves-max-lag指定了这个时间)。
2. 配置
(1)redis.conf 关于主从复制的详细配置
################################# REPLICATION #################################
#复制选项,slave复制对应的master。
# slaveof <masterip> <masterport>
#如果master设置了requirepass,那么slave要连上master,需要有master的密码才行。masterauth就是用来配置master的密码,这样可以在连上master后进行认证。
# masterauth <master-password>
#当从库同主机失去连接或者复制正在进行,从机库有两种运行方式:1) 如果slave-serve-stale-data设置为yes(默认设置),从库会继续响应客户端的请求。2) 如果slave-serve-stale-data设置为no,除去INFO和SLAVOF命令之外的任何请求都会返回一个错误”SYNC with master in progress”。
slave-serve-stale-data yes
#作为从服务器,默认情况下是只读的(yes),可以修改成NO,用于写(不建议)。
slave-read-only yes
#是否使用socket方式复制数据。目前redis复制提供两种方式,disk和socket。如果新的slave连上来或者重连的slave无法部分同步,就会执行全量同步,master会生成rdb文件。有2种方式:disk方式是master创建一个新的进程把rdb文件保存到磁盘,再把磁盘上的rdb文件传递给slave。socket是master创建一个新的进程,直接把rdb文件以socket的方式发给slave。disk方式的时候,当一个rdb保存的过程中,多个slave都能共享这个rdb文件。socket的方式就的一个个slave顺序复制。在磁盘速度缓慢,网速快的情况下推荐用socket方式。
repl-diskless-sync no
#diskless复制的延迟时间,防止设置为0。一旦复制开始,节点不会再接收新slave的复制请求直到下一个rdb传输。所以最好等待一段时间,等更多的slave连上来。
repl-diskless-sync-delay 5
#slave根据指定的时间间隔向服务器发送ping请求。时间间隔可以通过 repl_ping_slave_period 来设置,默认10秒。
# repl-ping-slave-period 10
#复制连接超时时间。master和slave都有超时时间的设置。master检测到slave上次发送的时间超过repl-timeout,即认为slave离线,清除该slave信息。slave检测到上次和master交互的时间超过repl-timeout,则认为master离线。需要注意的是repl-timeout需要设置一个比repl-ping-slave-period更大的值,不然会经常检测到超时。
# repl-timeout 60
#是否禁止复制tcp链接的tcp nodelay参数,可传递yes或者no。默认是no,即使用tcp nodelay。如果master设置了yes来禁止tcp nodelay设置,在把数据复制给slave的时候,会减少包的数量和更小的网络带宽。但是这也可能带来数据的延迟。默认我们推荐更小的延迟,但是在数据量传输很大的场景下,建议选择yes。
repl-disable-tcp-nodelay no
#复制缓冲区大小,这是一个环形复制缓冲区,用来保存最新复制的命令。这样在slave离线的时候,不需要完全复制master的数据,如果可以执行部分同步,只需要把缓冲区的部分数据复制给slave,就能恢复正常复制状态。缓冲区的大小越大,slave离线的时间可以更长,复制缓冲区只有在有slave连接的时候才分配内存。没有slave的一段时间,内存会被释放出来,默认1m。
# repl-backlog-size 5mb
#master没有slave一段时间会释放复制缓冲区的内存,repl-backlog-ttl用来设置该时间长度。单位为秒。
# repl-backlog-ttl 3600
#当master不可用,Sentinel会根据slave的优先级选举一个master。最低的优先级的slave,当选master。而配置成0,永远不会被选举。
slave-priority 100
#redis提供了可以让master停止写入的方式,如果配置了min-slaves-to-write,健康的slave的个数小于N,mater就禁止写入。master最少得有多少个健康的slave存活才能执行写命令。这个配置虽然不能保证N个slave都一定能接收到master的写操作,但是能避免没有足够健康的slave的时候,master不能写入来避免数据丢失。设置为0是关闭该功能。
# min-slaves-to-write 3
#延迟小于min-slaves-max-lag秒的slave才认为是健康的slave。
# min-slaves-max-lag 10
(2)sentinel.conf配置
port 20086 #默认端口26379
dir "/tmp"
logfile "/var/log/redis/sentinel_20086.log"
daemonize yes
#格式:sentinel <option_name> <master_name> <option_value>;#该行的意思是:监控的master的名字叫做T1(自定义),地址为127.0.0.1:10086,行尾最后的一个2代表在sentinel集群中,多少个sentinel认为masters死了,才能真正认为该master不可用了。
sentinel monitor T1 127.0.0.1 10086 2
#sentinel会向master发送心跳PING来确认master是否存活,如果master在“一定时间范围”内不回应PONG 或者是回复了一个错误消息,那么这个sentinel会主观地(单方面地)认为这个master已经不可用了(subjectively down, 也简称为SDOWN)。而这个down-after-milliseconds就是用来指定这个“一定时间范围”的,单位是毫秒,默认30秒。
sentinel down-after-milliseconds T1 15000
#failover过期时间,当failover开始后,在此时间内仍然没有触发任何failover操作,当前sentinel将会认为此次failoer失败。默认180秒,即3分钟。
sentinel failover-timeout T1 120000
#在发生failover主备切换时,这个选项指定了最多可以有多少个slave同时对新的master进行同步,这个数字越小,完成failover所需的时间就越长,但是如果这个数字越大,就意味着越多的slave因为replication而不可用。可以通过将这个值设为 1 来保证每次只有一个slave处于不能处理命令请求的状态。
sentinel parallel-syncs T1 1
#sentinel 连接设置了密码的主和从
#sentinel auth-pass <master_name> xxxxx
#发生切换之后执行的一个自定义脚本:如发邮件、vip切换等
##sentinel notification-script <master-name> <script-path> ##不会执行,疑问?
#sentinel client-reconfig-script <master-name> <script-path> ##这个会执行
以上内容整理自Redis哨兵机制(sentinel),这个博主写的有点多,看了两三个小时,才理清楚,本来想总结整理一下的,但是发现如果想理解清晰透彻,有些内容确实不能少,所以只是整理了一部分,有些内容就照搬了
(3)Redis Sentinel集群部署
我是以虚拟机进行搭建集群的,分别在三台虚拟机上启动了一个Redis服务以及Sentinel服务,下面是详细的配置信息
1)redis.conf配置信息(关于Replication部分的)
# Slave配置一个Master的关联就可以了,其他的使用默认的就可以(毕竟是测试使用,没有那么多的要求)
replicaof 10.159.82.6 6379
2)sentinel.conf配置信息(只修改部分,其他默认)
sentinel monitor mymaster 10.159.82.6 6379 2
sentinel down-after-milliseconds mymaster 10000
3)启动Redis集群
然后依次启用所有Redis集群即可,记得这里要Master先启动,其他的顺序都没关系
redis启动命令
redis.server ./redis.conf
sentinel启动命令
redis.sentinel ./sentinel.conf
这种效果就可以了
之后就可以进行测试了,比如手动将Master宕机掉,看一下Sentinel是否会选举出一个Master,我这边是没问题的,这块只要配置文件没问题了,启动。。。。不可能出问题吧,这不可能吧
三、Cluster集群部署
集群就是cluster,这块不得不说一下写这篇博客的辛酸,在写这篇博客之前,我一直以为由三个节点组成的集群就是cluster,结果大错特错,耽误了好长时间,不过这也让我查看了好多文章,对这部分内容理解更深刻了些
下面着重讲一下Redis中主从复制、sentinel、cluster的区别
- 主从模式:备份数据、负载均衡,读写分离,一个Master可以有多个Slaves。
- 监控,自动转移,sentinel发现master挂了后,就会从slave中重新选举一个master。
- 为了解决单机Redis容量有限的问题,将数据按一定的规则分配到多台机器,内存/QPS不受限于单机,可受益于分布式集群高扩展性。
- Sentinel应用于高可用,cluster我认为是Sentinel的又一提升,可用于高并发及单机存储有限问题
搭建Cluster集群
(1)准备
首先需要准备六个节点,集群一般都是两个为一组,一共三组,这个在我看来也是最小的集群模型了!这六个节点可以在一个服务器中搭建,但是这个其实是一个伪集群,它并不能做到风险分摊。我这边是在三个虚拟机上搭建了六个节点,对了cluster集群搭建好了之后,它默认两个为一组的主从节点不是一个主机
(2)配置文件
这里他的参数很多,我测试所用只修改打开了他的cluster开关,其他内容采用了默认的形式
cluster-enabled yes #开启集群模式
cluster-config-file nodes-7000.conf #集群内部的配置文件
cluster-require-full-coverage no #redis cluster需要16384个slot都正常的时候才能对外提供服务,换句话说,只要任何一个slot异常那么整个cluster不对外提供服务。 因此生产环境一般为no
因为可能是在一个主机中进行的多个节点的分配,所以一定要修改port端口号和pid进程号,不要打开replication主从复制的配置
(3)启动服务
分别启动六个节点服务,这块我是直接写了一个脚本文件进行启动的
cd /opt/redis/6.0.9/conf/6379
./redis-server6379 ./redis6379.conf
cd /opt/redis/6.0.9/conf/6380
./redis-server6380 ./redis6380.conf
上边就是脚本文件redis-cluster.sh的内容,如果创建了脚本文件一定要给它授权后才能使用
chmod +x redis-cluster.sh
这样就可以直接启动脚本文件了
(4)搭建集群
开启六个节点服务后,就可以直接搭建集群了,这块有一点需要注意的是,现在大多数的DSCN文章都是对以前的Redis版本进行的讲解,所以使用的是ruby搭建Redis的方法!但是在Redis5.0之后,使用了redis-cli就可以进行集群的搭建了
使用命令
redis-cli --cluster create 10.159.82.252:6379 10.159.82.252:6380 10.159.82.6:6379 10.159.82.6:6380 10.159.82.17:6379 10.159.82.17:6380 --cluster-replicas 1
cluster-replicas:从机数量
redis-cli --cluster create:后面跟着的是六个节点的ip和端口号
执行命令后如下
[root@localhost conf]# redis-cli --cluster create 10.159.82.252:6379 10.159.82.252:6380 10.159.82.6:6379 10.159.82.6:6380 10.159.82.17:6379 10.159.82.17:6380 --cluster-replicas 1
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 10.159.82.6:6380 to 10.159.82.252:637