主从复制
什么是主从复制?
有多个redis进程,其中一个作为master,其余的作为slave(奴隶),salve通过socket连接到master,会与master保持相同的数据
在redis中,配置主从复制非常简单,redis允许slave实例对master进行完整拷贝,在连接断开时,slave会自动重新连接至主实例,并尽可能与master保持同步。
使用场景:
1.读写分离,提高并发能力
2.数据的备份
三个主要机制
1.当连接可用时,master将发送命令流到slave来使slave保持更新,以下操作将引发该操作,对master的写入操作(包括删除、更新),key过期
2.master和slave节点都会进行超时检测,当连接不稳定时,slave将尽快重新链接并进行部分重新同步,即不需要完全重新同步
3.若无法进行部分重新同步,则slave将发起完全重新同步,master会将最新的数据快照发送给slave,后续的操作仍然是发送命令流。
其他特性
主从之间,采用异步复制,复制过程中依然可以正常响应客户端操作,支持一主多从,且从节点还可以类似的级联其他从节点,如图所示:
主从复制的作用
使用主从复制,能够避免Redis的单点故障,实现数据防灾备份;
可配置主节点执行写入,多个从节点分担查询,实现读写分离获得更好的性能
注意:
使用主从来做读写分离时,意味主节点自身没有任何持久化数据;
如果配置了哨兵,一旦节点重启,则将使用空数据进行同步,导致从节点覆盖所有持久化数据,这是非常危险的,强烈建议在主节点和从节点上开启持久化,如果一定要关闭,则必须配置主节点禁止哨兵自动重启故障节点;
主从配置
-
创建两个redis实例,一个master一个slave。
[root@bogon redis]# cp -r bin slave1 -
master实例不做任何特殊配置。
-
修改slave的配置文件
vim redis.conf
输入i进入插入模式
#配置文件中按以下方式添加主节点的ip 和端口即可
replicaof <masterip> <masterport>
例如: replicaof 192.168.10.131 65533
修改从的端口,不要与主冲突
输入:wq
#配置文件中按以下方式添加主节点的ip 和端口即可
replicaof 192.168.74.134 6379
#若主节点配置了授权密码则需要指定密码
masterauth 密码
#主节点通过以下方式设置授权密码
requirepass 密码
#客户端连接后需要先验证密码
auth 密码
#可通过以下指令查看当前连接的服务的主从信息
info replication
副本只读
默认情况下副本是只读的,
若需要可以通过配置replica-read-only为no来使副本变为可写的,
但是要强调的是副本写入的数据,仅写入到当前副本本地,不会同步至任何节点,
包括当前副本的副本;
当副本重启后这写数据将消失,即临时数据;
如果
仅有主从复制,如果master宕机了,整个redis将会不可写入,此时便没有实现高可用
需要哨兵
哨兵是一个独立的进程,会与master/slave通过socket进行连接,用于监控redis进程的运行状态,当有节点发生故障时,可以通过API通知管理员或者应用程序
说白了就是万一master挂了,就自动完成一个故障切换,将一个slave换为master
哨兵
哨兵(Sentinel)是为redis提供了高可用性(high available/HA),使用Sentinel部署redis时,可实现无需人工干预的情况下,自动完成固定类型的故障修复,使redis尽可能处于正常工作状态;
哨兵会每隔一段时间向节点发送消息,如果超时,则这个实例会被 Sentinel(哨兵)进程标记为主观下线,如果有相应数量的哨兵认为主观下线,节点将变成客观下线。
哨兵的主要功能
1.集群监控:监控redis 各个节点是否正常工作
2.消息通知:当某个节点出现故障时,可通过API通知系统管理员或其他程序
3.故障转移(failover):当master无法正常工作时,哨兵可以启动故障转移过程,该过程将某一个slave节点提升为master节点,并主动通知使用redis服务器的应用程序要使用新的地址
4.配置中心:当客户端连接至哨兵时,可通过哨兵获取可用的redis服务信息。
哨兵的分布式性质
Sentinel本身就是一个分布式系统,即会有多个Sentinel进程通过网络协同合作,具有以下优点
- 当多个哨兵就某一master不可用这一事实达成共识,才会进行故障转移,降低了因网络波动造成误报的可能性
- 即使一些哨兵进程无法工作时,其他可用的哨兵仍然能够正常工作,提高了整个系统应对故障的能力
反过来,如果只有单独的一个哨兵进程实际上是没有办法提供高可用的
使用vim sentinel.conf修改port
指定master的地址和端口:sentinel monitor mymaster 192.168.10.138 65533 2
启动哨兵的命令有两种写法:
#方式1
redis-sentinel sentinel.conf
#方式2
redis-server sentinel.conf --sentinel
若启动哨兵成功可以在控制台中看到其输出的master节点信息;
默认情况下哨兵监听在26379端口上,若开启了防火墙则需要开放该端口,否则哨兵无法正常工作;
故障切换
故障切换的定义:
当master不可用时将一个可用的slave提升称为master,使结点保持正常访问;
基于网络存在不稳定性这个特性,一些时候某个哨兵进程可能无法与master正常通讯,但是这并不意味这master真的不可用了,哨兵无法就此认定master不可用这一事实,哨兵需要能够检测master是否客观的不可用了,并在master不可用成为客观事实后开始执行故障切换;
- 每个Sentinel(哨兵)进程以每秒钟一次的频率向整个集群中的Master主服务器,Slave从服务器以及其他Sentinel(哨兵)进程发送一个
PING 命令 - 如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after- milliseconds
选项所指定的值,则这个实例会被 Sentinel(哨兵)进程标记为主观下线(sdown) - 如果一个Master主服务器被标记为主观下线(SDOWN),则正在监视这个Master主服务器的所有Sentinel(哨兵)进程要以每秒一次的频率确认Master主服务器的确进入了主观下线状态。
- 当有足够数量的Sentinel(哨兵)进程(大于等于配置文件指定的值)在指定的时间范围内确认Master主服务器进入了主观下线状态(SDOWN),则Master主服务器会被标记为客观下线(ODOWN)。
- 在一般情况下, 每个Sentinel(哨兵)进程会以每 10 秒一次的频率向集群中的所有Master主服务器、Slave从服务器发送
INFO 命令。 - 当Master主服务器被 Sentinel(哨兵)进程标记为客观下线(ODOWN)时,Sentinel(哨兵)进程向下线的
Master主服务器的所有 Slave从服务器发送 INFO 命令的频率会从 10 秒一次改为每秒一次。 - 若没有足够数量的 Sentinel(哨兵)进程同意 Master主服务器下线,
Master主服务器的客观下线状态就会被移除。若Master主服务器重新向 Sentinel(哨兵)进程发送 PING
命令返回有效回复,Master主服务器的主观下线状态就会被移除。
故障切换涉及到的事件和参数: - sdown:主观下线,当某个哨兵与master之间在指定时间内无法正常通讯后该哨兵将产生sdown事件
- quorum(仲裁数):是一个整数,表示master从主观下线变为客观下线所需要的哨兵数量(但有quorum个哨兵与master通讯失败则master进入主观下线)
- odown:当sdown的事件的数量达到指定值(quorum)时,将产odown事件,表示master客观下线了;
- majority(大多数):是一个整数,该值通过计算自动得出,计算公式为floor(哨兵总数量/2)+1 floor为下取整
- 当odown产生时,会选出一个哨兵准备进行故障切换,在切换前该哨兵还需要获得大多数(majority)哨兵的授权,授权成功则开始进行故障切换;
故障切换完成后,若先前宕机的节点(原来的master)恢复正常,则该节点会降为slave;
部署Sentinel
- 哨兵应与分布式的形式存在,若哨兵仅部署一个则实际上没有办法提高可用性,当仅有的哨兵进程遇到问题退出后,则无法完成故障恢复;
- 一个健壮的部署至少需要三个Sentinel实例
- 三个哨兵应该部署在相互的独立的计算机或虚拟机中;
- Sentinel无法保证在执行故障转移期间的写入的数据是否能够保留下来;
部署案例
下例图中名称的释义:
S:sentinel
M:master
R:replace(slave)
不建议的部署
上述部署中若M1(包括S1,因为在同一个机器上)宕机,剩下的S2虽然可以认定M1主观下线,但是却无法得到大多数哨兵的授权并开始故障切换,因为此时majority为2;
简单部署
上述部署由三个节点组成,每个节点都运行着Redis进程和Sentinel进程,结构简单,安全性也有了提高;
当M1发生故障时,S2和S3可以就该故障达成一致,并且能够授权进行故障切换,从而使得客户端可以正常使用;但也存在丢失已写入数据的情况,因为redis内部使用异步复制,Master和Slave之间的数据在某个时间点可能不一致;
注意:
由于网络存在分区性质,若客户端C1和M1处于同一分区,但是该分区与S1,S2所在的分区无法通讯时,C1可以继续向M1写入数据,这写数据将丢失,因为当网络恢复时,M1可会被降为slave,而丢弃自己原本的数据;
使用下例配置可缓解该问题:
min-replicas-to-write 1
min-replicas-max-lag 5
上述配置表示,只要master无法写入数据到任何一个slave超过5秒,则master停止接受写入;
在客户端部署哨兵
若某些原因导致没有足够的服务器节点用于部署哨兵,则可以将哨兵部署至客户端,如下所示
若M1出现故障,则S1,S2,S3可顺利的进行故障切换;但要注意该部署可能出现案例2中的问题
当然你也可以在客户端和服务端同时部署哨兵;
配置示例
#指出master地址和端口 以及仲裁数
sentinel monitor mymaster 192.168.10.138 65533 2
# 与master通讯超时时间,达到超时时间则sdown+1
sentinel down-after-milliseconds mymaster 60000
# 同一个master,开始新的故障转移的时间(将是上一次的两倍)
# 若slave连接到错误的master超过这个时间后slave将被重新连接到正确的master
# 取消正在进行中的故障转移等待时间
# 按照parallel-syncs指定的配置进行复制的时间,超时候将不再受parallel-syncs的限制
sentinel failover-timeout mymaster 180000
# 发生故障转移后,同时进行同步的副本数量
sentinel parallel-syncs mymaster 1
集群
一台机器上,物理内存很容易就达到极限了
只有主节点才能写入,主节点只有一个
需要多台机器组成一个整体,此时就会产生新的问题,比如要存储一个name:tom
不清楚name应该放到哪台机器,且查询的时候也不知道从哪台查
redis集群是一个提供在多个redis节点间共享数据的程序集。
redis集群并不支持处理多个keys的命令(如mset),因为这需要在不同节点间移动数据,从而达不到像redis那样的性能,在高负载的情况下可能会导致不可预料的错误
redis集群通过分区提供一定程度的可用性,在实际环境中当某个节点宕机或者不可达的情况下继续处理命令,redis集群的优势:
- 自动分割数据到不同的节点
- 整个集群的部分节点失败或不可达的情况下继续处理命令
集群的数据分片
redis集群并没有使用一致性hash,而是引用了哈希槽的概念
redis集群有16384个哈希槽,每个key通过CRC16算法校验后对16384取模决定防止哪个槽,集群的每个节点负责一部分hash槽。例如当前集群中有3个节点,那么
- 节点A包含0-5000号哈希槽
- 节点B包含5501-11000号哈希槽
- 节点C包含10001-16383号哈希槽
这种结构很容易添加或删除节点
如果想新添加节点D,首先需要从节点A、B、C中的得到部分槽到D上;
如果我想移除节点A,需要将A的槽移到B和C节点上,然后将没有任何槽的A节点从集群中移除
由于从一个节点将哈希槽移动到另一个节点并不会停止服务,所以无论添加、删除或改变某个哈希槽的节点的哈希槽的数量都不会造成集群不可用的状态,是异步完成的。
客户端可以访问任意节点进行读写操作,若哈希槽不在当前访问的节点,redis会自动的将指令移动到相关节点上。
主从复制模型
为了使在部分节点失败或者大部分节点无法通信的情况下集群仍然可用,集群使用了主从复制模型,每个节点都会至少使用一个slave
集群在没有slave的情况下,如果某个节点故障了,那么整个集群就会以为缺少一部分槽而不可用
然后如果在创建集群时为每个节点添加了从节点,在某个节点故障后,其从节点将被选举为新的主节点,整个集群就不会因找不到槽而不可用,当然若某个节点与其所有子节点都故障,那么整个节点将不可用
一致性保证
redis并不能保证数据的强一致性,这意味着在实际中集群在特定的条件下可能会丢失写操作。主要有两方面原因
- 主节点到从节点之间的指令复制是异步完成的,主从之间在某个时间点可能不一致
- 出现网络分区,导致主节点可正常写入,但是从节点已经被其他分区节点选举为新的master,写入的数据将丢失
容错性
当某master节点故障时,其他master节点将发起投票,若一半以上的master认为其不可用,则从该节点的从节点中(若存在)选举新的master;
若master没有从节点,则集群将不可用
另外当集群一半以上的节点都不可用时则无论这些节点是否有从节点,集群立即不可用
创建集群
redis在5.0版本时放弃了Ruby集群的方式,改为C语言编写的redis-cli方式,使得集群的构建方式复杂度大大降低。
集群至少需要三个节点,每个节点一个从节点,总共为6个节点
以下步骤是在一台 Linux 服务器上搭建有3个节点的 Redis集群。(因为redis 集群最低3个节点,不然 无法创建)每个节点一个从节点,一共需要6个redis实例。
-
创建6个redis实例,端口为7001~7006
[root@localhost redis]# mkdir cluster
[root@localhost redis]# cp -r bin cluster/7001
-
在redis.conf配置文件中开启集群模式
首先要设置不同的端口号port 7001
若要以集群方式运行,则需要按以下方式修改配置文件,以启用集群:
cluster-enabled yes
在实际开发中仍然建议使用单独的虚拟机来部署所有的redis节点,下例为了简化操作,在同一台虚拟机上搭建集群来进行测试:
- 分别启动6个redis示例
- 使用redis-cli创建集群
./redis-cli --cluster create 192.168.10.138:7001 192.168.10.138:7002 192.168.10.138:7003 192.168.10.138:7004 192.168.10.138:7005 192.168.10.138:7006 --cluster-replicas 1
过程中集群将重写配置文件,需输入yes确认
创建完成后提示如下信息:
可以看到,集群自动为每个master平均分配了哈希槽,并且设置了一个slave
- 连接集群
# 参数 -c 即表示连接到集群
./redis-cli -h 192.168.10.138 -p 7001 -c
-
查看集群状态
cluster info
-
查看节点信息,包括ip,port,id,槽,主/从:
cluster nodes
添加节点
Redis集群支持动态扩展,期间redis可正常响应客户端访问
- 首先制作新的redis实例端口为7007并启动
- 添加到集群中
./redis-cli --cluster add-node 192.168.10.138:7007 192.168.10.138:7001
添加成功
- 新的节点默认作为master,但是该master没有分配槽位,使用前必须分配哈希槽
下例表示从id为cc2e48268ccdd52d1c7840c8f9d2d7f15cc74c1b的节点移动1000个槽到cda3828e42e23dcbdb141db2fed221bc07c59f65节点
./redis-cli --cluster reshard 192.168.74.134:7001 --cluster-from cc2e48268ccdd52d1c7840c8f9d2d7f15cc74c1b --cluster-to cda3828e42e23dcbdb141db2fed221bc07c59f65 --cluster-slots 1000
#--cluster-from 来源节点 多个之前用逗号隔开, all 表示从所有节点中平均分配
#--cluster-to 目标节点
#--cluster-slots 1000 移动哈希槽数量
- 哈希槽均衡,该操作检查各节点的槽均衡情况,若差异较大则自动重新分配
./redis-cli --cluster rebalance 192.168.10.138:7001
- 为新的master添加从节点
再次启动一个新的实例 7008
添加至集群并指定为某master的slave
./redis-cli --cluster add-node 192.168.10.138:7008 192.168.10.138:7001 --cluster-slave --cluster-master-id cda3828e42e23dcbdb141db2fed221bc07c59f65
#--cluster-slave 指定新节点作为slave
#--cluster-master-id 指定新节点的master
删除节点
删除从节点7008
./redis-cli --cluster del-node 192.168.10.138:7008 887d2f115f6a94bda86863576d73a131f12229d5
#指定集群host:port 和要删除的节点id
删除主节点时,要先将主节点的哈希槽分配给其他的主节点
./redis-cli --cluster reshard 192.168.10.138:7001 --cluster-from cda3828e42e23dcbdb141db2fed221bc07c59f65 --cluster-to cc2e48268ccdd52d1c7840c8f9d2d7f15cc74c1b
删除主节点
./redis-cli --cluster del-node 192.168.10.138:7007 887d2f115f6a94bda86863576d73a131f12229d5
#指定集群host:port 和要删除的节点id