Redis主从,哨兵结构与集群详解
Redis主从架构
Redis主从架构就是一个主master(模拟端口号为6379),多个slave(模拟端口号为6380,6381 ...)的结构,slave是没有写的权限的,当master宕机后,slave并不会自动切换为master,需要手动执行. 在slave(6380)客户端执行 edis-cli-p6380slaveofnoone命令将该slave(6380)设置为master,并且赋予write权限,在原来的主机(6379)恢复后,需要将现在的主机(6380)数据保存持久化,然后将dump.rdb文件覆盖到原主机,然后重启原主机(6380).重启后,再将现在有的主机(6380)恢复为从机,命令为 redis-cli-p6380slaveof127.0.0.16379
Redis主从工作原理
如果为master配置了一个slave,不管这个slave是否是第一次连接上master,它都会发送一个 SYNC命令给master请求复制数据. master收到 SYNC命令后,会在后台进行数据持久化,通过 bgsave生成最新的rdb快照文件,同时持久化期间,master会继续接收客户端的请求,它会把这些可能修改数据集的请求缓存到内存中,当持久化进行完毕后,master会将rdb文件发送给slave,slave会把接收到的数据进行持久化生成rdb,然后加载到内存中,然后master再将之前缓存在内存中的命令发送给slave.
当master与slave之间由于某些原因断开时,slave能够自动重连master,然后继续上述操作.如果master收到了多个slave并发连接请求,它只会进行一次持久化,然后把这一份持久化数据发送给多个并发连接的slave.
当master和slave断开重连后,一般都会对整份数据进行复制,但在redis2.8版本开始,master和slave断开重连后支持部分复制.
数据的部分复制
master会在其内存中创建一个复制数据用的缓存队列,缓存最近一段时间的数据,master和它所有的slave都维护了复制的数据下标offset和master进程ID,因此当网络连接断开后,slave会请求master继续进行未完成的复制,从所记录的数据下标开始,如果master进行ID发生变化或者从节点数据下标offset太旧,已经不在master的缓存队列里了,那么将会进行一次全量数据的复制.
主从架构的搭建
# 从主复制一份redis.conf文件# 配置主从复制replicaof 主IP 主端口replica-read-only yes# 启动从节点
哨兵结构
sentinel哨兵是特殊的redis服务,不提供读写服务,主要用来监控redis实例节点.Sentinel的主要功能包括主节点存活检测、主从运行情况检测、自动故障转移(failover)、主从切换。哨兵架构下client端第一次从哨兵找出redis的主节点,后续就直接访问redis的主节点,不会每次都通过sentinel代码访问redis的主节点,当redis的主节点发生变化,哨兵会第一时间感知到,并且将新的redis主节点通知给client端(这里的redis的client端一般都实现了订阅功能,订阅sentinel发布的节点变动消息)
redis sentinel的工作原理
Sentinel是Redis的高可用性解决方案,由一个或多个Sentinel实例组成的Sentinel系统可以监视任意多个主服务器,以及所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器,然后由新的主服务器代替已下线的主服务器继续处理命令请求,如下图所示
Sentinel负责监控集群中的所有主、从Redis,当发现主故障时,Sentinel会在所有的从中选一个成为新的主。并且会把其余的从变为新主的从。同时那台有问题的旧主也会变为新主的从,也就是说当旧的主即使恢复时,并不会恢复原来的主身份,而是作为新主的一个从.
哨兵架构的搭建
配置三个哨兵和1主2从的Redis服务器来演示(真实环境不建议在一台主机上)
| 服务类型 | 是否主服务器 | ip地址 | 端口 |
|---|---|---|---|
| Redis | 是 | 127.0.0.1 | 6379 |
| Redis | 否 | 127.0.0.1 | 6380 |
| Redis | 否 | 127.0.0.1 | 6381 |
| Sentinel | - | 127.0.0.1 | 26379 |
| Sentinel | - | 127.0.0.1 | 26380 |
| Sentinel | - | 127.0.0.1 | 26381 |
1、复制一份sentinel.conf文件cp sentinel.conf sentinel-26379.conf2、将相关配置修改为如下值:port 26379daemonize yespidfile "/var/run/redis-sentinel-26379.pid"logfile "/var/log/26379.log"dir "/usr/lib/redis/26379"# sentinel monitor <master-name> <ip> <redis-port> <quorum># quorum是一个数字,指明当有多少个sentinel认为一个master失效时(值一般为:sentinel总数/2 + 1),master才算真正失效sentinel monitor mymaster 127.0.0.1 6379 2# 检测主服务器宕机的时间间隔sentinel down-after-milliseconds 30000# 同步个数sentinel parallel-syncs mymaster 13、启动sentinel哨兵实例sentinel-26379.conf4、查看sentinel的info信息-p 26379:26379>info可以看到Sentinel的info里已经识别出了redis的主从5、可以自己再配置两个sentinel,端口26380和26381,注意上述配置文件里的对应数字都要修改
启动顺序
主Redis-->从redis-->三个哨兵服务进行
java中使用哨兵模式
public class TestSentinels {@Testpublic void testSentinel() {JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();jedisPoolConfig.setMaxTotal(10);jedisPoolConfig.setMaxIdle(5);jedisPoolConfig.setMinIdle(5);// 哨兵信息Set<String> sentinels = new HashSet<>(Arrays.asList("127.0.0.1:26379","127.0.0.1:26380","127.0.0.1:26381"));// 创建连接池JedisSentinelPool pool = new JedisSentinelPool("mymaster", sentinels, jedisPoolConfig, 3000, null);// 获取客户端Jedis jedis = pool.getResource();// 执行两个命令jedis.set("mykey", "myvalue");String value = jedis.get("mykey");System.out.println(value);}}
springboot中的哨兵模式配置
spring:redis:database: 0timeout: 3000sentinel: #哨兵模式master: mymaster #主服务器所在集群名称nodes: 127.0.0.1:26379,127.0.0.1:26380,127.0.0.1:26381lettuce:pool:max-idle: 10min-idle: 5max-active: 100max-wait: 1000
Redis集群
在哨兵模式中,如果主服务器宕机了,会自动的将从服务器设置为主服务器,但是在主从切换的瞬间还是存在 访问瞬断的情况,而且哨兵模式只有一个主节点对外提供服务,没有办法支持很高的并发,而且单个主节点的内存也不宜设置的过大,否则会导致持久化文件过大,从而影响数据恢复或主从同步的效率
下图是高可用的集群模式
redis集群是一个由 多个主从节点群组成的分布式服务器群,它具备 复制,高可用和分片的特性.redis集群不需要sentinel哨兵也能完成节点转移和故障转移的功能,需要将每个节点设置成集群模式,这种集群模式没有中心节点,可水平扩展,官方文档可以线性扩展到上万个节点,但是推荐不超过1000个节点.redis集群的性能和高可用行均优于哨兵模式,且配置非常简单.
Redis高可用集群搭建
Redis集群需要 至少三个master节点,我们再给每个master节点搭建一个slave节点,总共6个redis节点,我这里是在单机上部署了6个redis实例,搭建集群步骤如下:
#第一步:在第一台机器的/usr/local下创建文件夹redis-cluster,然后在其下面分别创建6个文件夾如下mkdir -p /usr/local/redis-clustermkdir 8000 8001 8002 8003 8004 8005# 复制redis.conf到800*文件夹下,修改以下内容daemonize yes# 端口号与文件夹名保持一致port 8000# 数据文件存放的位置与端口号保持一致,必须指定不同的路径,防止数据丢失dir /usr/local/redis-cluster/8000/# 启动集群模式cluster-enabled yes集群节点信息文件cluster-config-file nodes-8000.confcluster-node-timeout 5000# 关闭保护模式protected-mode no# 开启AOFappendonly yes如果需要设置密码,增加以下配置# redis访问密码requirepass 123456# 集群节点之间访问密码,与上面保持一致masterauth 123456## 其他redis实例也按照上述几部操作进行,批量修改端口号即可:%s/源字符串/目的字符串/g# 分别启动6个实例,并检查是否启动成功redis-server /usr/local/redis-cluster/800*/redis.confps -ef|grep redis# 用redis-cli创建整个集群(redis5以前的版本依靠ruby脚本redis-trib.rb实现)# --cluster-replicas 1 1代表为每个创建的主服务器节点创建一个从服务器节点redis-cli --cluster create --cluster-replicas 1 127.0.0.1:8000 127.0.0.1:8001 127.0.0.1:8002 127.0.0.1:8003 127.0.0.1:8004 127.0.0.1:8005# 验证集群# 连接任意一个客户端,-c表示集群模式 -a 密码redis-cli -c -h ip -p port -a pwd# 查看集群信息cluster info# 查看集群节点信息cluster nodes# 关闭集群需要逐个关闭,使用命令redis-cli -a pwd -c -h IP -p port shutdown
java中使用集群模式
public class JedisClusterTest {public static void main(String[] args) throws IOException {JedisPoolConfig config = new JedisPoolConfig();config.setMaxTotal(20);config.setMaxIdle(10);config.setMinIdle(5);Set<HostAndPort> jedisClusterNode = new HashSet<HostAndPort>();jedisClusterNode.add(new HostAndPort("127.0.0.1", 8001));jedisClusterNode.add(new HostAndPort("127.0.0.1", 8002));jedisClusterNode.add(new HostAndPort("127.0.0.1", 8003));jedisClusterNode.add(new HostAndPort("127.0.0.1", 8004));jedisClusterNode.add(new HostAndPort("127.0.0.1", 8005));jedisClusterNode.add(new HostAndPort("127.0.0.1", 8000));JedisCluster jedisCluster = null;try {//connectionTimeout:指的是连接一个url的连接等待时间//soTimeout:指的是连接上一个url,获取response的返回等待时间jedisCluster = new JedisCluster(jedisClusterNode, 6000, 5000, 10, "123456", config);System.out.println(jedisCluster.set("cluster", "value"));System.out.println(jedisCluster.get("cluster"));} catch (Exception e) {e.printStackTrace();} finally {if (jedisCluster != null)jedisCluster.close();}}}
springboot中的集群模式配置
spring:redis:database: 0timeout: 3000password: zhugecluster:nodes: 127.0.0.1:8001,127.0.0.1:8002,127.0.0.1:8003,127.0.0.1:8004,127.0.0.1:8005,127.0.0.1:8000lettuce:pool:max-idle: 50min-idle: 10max-active: 100max-wait: 1000
Redis集群原理分析
RedisCluster将所有数据划分为 16384个slots(槽位),每个节点负责其中一部分槽位。槽位的信息存储于每个节点中。当Redis Cluster的客户端来连接集群时,它也会得到一份集群的槽位配置信息并将其缓存在客户端本地。这样当客户端要查找某个key时,可以直接定位到目标节点.同时因为槽位的信息可能会存在客户端与服务器不一致的情况,还需要纠正机制来实现槽位信息的校验调整。
槽位定位算法
Cluster 默认会对key值使用 crc16算法进行hash得到一个整数值,然后用这个整数值对 16384进行取模来得到具体槽位。
HASH_SLOT=CRC16(key)mod16384
跳转重定位
Redis集群节点间的通信机制
redis cluster节点间采取gossip协议进行通信,维护集群的元数据有两种方式:集中式和gossip.
集中式: 优点在于元数据的更新和读取,时效性非常好,一旦元数据出现变更立即就会更新到集中式的存储中,其他节点读取的时候立即就可以立即感知到;不足在于所有的元数据的更新压力全部集中在一个地方,可能导致元数据的存储压力
gossip: gossip协议包含多种消息,包括ping,pong,meet,fail等等
ping:每个节点都会频繁给其他节点发送ping,其中包含自己的状态还有自己维护的集群元数据,互相通过ping交换元数据
pong:返回ping和meet,包含自己的状态和其他信息,也可以用于信息广播和更新
fail:某个节点判断另一个节点fail之后,就发送fail给其他节点,通知其他节点,指定的节点宕机了。
meet:某个节点发送meet给新加入的节点,让新节点加入集群中,然后新节点就会开始与其他节点进行通信,不需要发送形成网络的所需的所有CLUSTER MEET命令.发送CLUSTER MEET消息以便每个节点能够达到其他每个节点只需通过一条已知的节点链就够了.由于在心跳包中会交换gossip信息,将会创建节点间缺失的链接。
网络抖动
真实世界的机房网络往往并不是风平浪静的,它们经常会发生各种各样的小问题。比如网络抖动就是非常常见的一种现象,突然之间部分连接变得不可访问,然后很快又恢复正常
Redis Cluster提供了一种选项 cluster-node-timeout,表示当某个节点持续 timeout 的时间失联时,才可以认定该节点出现故障,需要进行主从切换。如果没有这个选项,网络抖动会导致主从频繁切换 (数据的重新复制)
Redis集群选举原理分析
当slave发现自己的master变为FAIL状态时,便尝试进行Failover,以期成为新的master。由于挂掉的master可能会有多个slave,从而存在多个slave竞争成为master节点的过程,其过程如下:
slave 发现自己的master变为FAIL状态
将自己记录的集群currentEpoch加1,并广播FAILOVERAUTHREQUEST信息
其他节点收到该信息,只有master响应,判断请求者的合法性,并发送FAILOVERAUTHACK,对每一个epoch只发送一次ack
尝试failover的slave收集master返回的FAILOVERAUTHACK
slave收到超过半数master的ack后自己就会变成新master(这里解释了集群为什么至少需要三个主节点,如果只有两个,当其中一个挂了,只剩一个主节点是不能选举成功的)
slave广播Pong消息通知其他集群节点自己变成了master
slave节点并不是在master节点一进入FAIL状态就马上尝试发起选举,而是有一定延迟,一定的延迟确保我们等待FAIL状态在集群中传播,slave如果立即尝试选举,其它masters或许尚未意识到FAIL状态,可能会拒绝投票
延迟计算公式: DELAY=500ms+random(0~500ms)+SLAVE_RANK*1000ms
SLAVE_RANK表示此slave已经从master复制数据的总量的rank.Rank越小代表已复制的数据越新。这种方式下,理论持有最新数据的slave将会首先发起选举
集群是否完整才能对外提供服务
当redis.conf的配置 cluster-require-full-coverage为no时,表示当负责一个插槽的主库下线且没有相应的从库进行故障恢复时,集群仍然可用,如果为yes则集群不可用。
Redis集群为什么至少需要三个master节点,并且推荐节点数为奇数?
因为新master的选举需要大于半数的集群master节点同意才能选举成功,如果只有两个master节点,当其中一个挂了,是达不到选举新master的条件的
奇数个master节点可以在满足选举该条件的基础上节省一个节点,比如三个master节点和四个master节点的集群相比,如果都挂了一个master节点都能选举新master节点,如果都挂了两个master节点都没法选举新master节点了,所以奇数的master节点更多的是从 节省机器资源角度出发说的
Redis集群对批量操作命令的支持
对于类似mset,mget这样的多个key的原生批量操作命令,redis集群只支持所有key落在同一slot的情况,如果有多个key一定要用mset命令在redis集群上操作,则可以在key的前面加上{XX},这样参数数据分片hash计算的只会是大括号里的值,能确保不同的key能落到统一slot里去,例如
mset{user1}:name zhangsan{user1}:age18
哨兵leader选举流程
当一个master服务器被某sentinel视为客观下线状态后,该sentinel会与其他sentinel协商选出sentinel的leader进行故障转移工作。每个发现master服务器进入客观下线的sentinel都可以要求其他sentinel选自己为sentinel的leader,选举是先到先得。同时每个sentinel每次选举都会自增配置纪元(选举周期),每个纪元中只会选择一个sentinel的leader。如果所有超过一半的sentinel选举某sentinel作为leader。之后该sentinel进行故障转移操作,从存活的slave中选举出新的master,这个选举过程跟集群的master选举很类似。
哨兵集群只有一个哨兵节点,redis的主从也能正常运行以及选举master,如果master挂了,那唯一的那个哨兵节点就是哨兵leader了,可以正常选举新master。不过为了高可用一般都推荐至少部署三个哨兵节点。为什么推荐奇数个哨兵节点原理跟集群奇数个master节点类似。
redis集群水平扩展
比如现在增加了两个redis实例,也就是需要再增加一组主从服务器 如何增加可以在redis-cli下执行命令 redis-cli--cluster help
create:创建一个集群环境host1:port1 ... hostN:portN
call:可以执行redis命令
add-node:将一个节点添加到集群里,第一个参数为新节点的ip:port,第二个参数为集群中任意一个已经存在的节点的ip:port
del-node:移除一个节点
reshard:重新分片(划分槽位)
check:检查集群状态
由以上可知,我们通过以下命令增加一个主节点到集群中 redis-cli--cluster add-node127.0.0.1:8006127.0.0.1:8000
然后查看集群状态可以看到新增的节点为master主节点,但是并没有任何槽位,接下来为新节点手工分配hash槽
redis-cli--cluster reshard127.0.0.1:8006
输入如下:
How many slots do you want to move (from 1 to 16384)? 600(ps:需要多少个槽移动到新的节点上,自己设置,比如600个hash槽)What is the receiving node ID? 2728a594a0498e98e4b83a537e19f9a0a3790f38(ps:把这600个hash槽移动到哪个节点上去,需要指定节点id)Please enter all the source node IDs.Type 'all' to use all the nodes as source nodes for the hash slots.Type 'done' once you entered all the source nodes IDs.Source node 1:all(ps:输入all为从所有主节点中分别抽取相应的槽数指定到新节点中,抽取的总槽数为600个)Do you want to proceed with the proposed reshard plan (yes/no)? yes(ps:输入yes确认开始执行分片任务)
以上只是增加了一个主节点,但是该主节点还没有从节点,下面操作是为新的主节点配置从节点
继续执行增加到集群中的命令 redis-cli--cluster add-node127.0.0.1:8007127.0.0.1:8000
该命令执行完后,通过 cluster nodes 发现该节点此时是一个主节点,所以我们需要将该节点从主节点变更为刚新增的主节点(8006)的从节点
通过 redis-cli进入该节点(8007)
-p 8007# replicate后面的参数为主节点(8006)的节点IDcluster replicate db9babc6b341425781a4816478cd946067ed8fb4
Redis集群删除节点
接下来的操作是把刚才新增的节点(主节点8006,从节点8007)从集群中删除
删除从节点
用 del-node删除从节点8007,指定删除节点ip和端口,以及节点id(8007节点id) redis-cli--clusterdel-node127.0.0.1:800763adf3ee1758de0ccff75d2c6340b8ec9a0c8a2d
删除主节点
在删除主节点时,我们需要把该主节点分配的hash槽放入到其他主节点中去,然后再进去移除节点的操作,不然会出现数据丢失的情况
redis-cli--cluster reshard127.0.0.1:8006输入如下:
... ...How many slots do you want to move (from 1 to 16384)? 600What is the receiving node ID? dfca1388f124dec92f394a7cc85cf98cfa02f86f(ps:这里是需要把数据移动到哪?8000的主节点id)Please enter all the source node IDs.Type 'all' to use all the nodes as source nodes for the hash slots.Type 'done' once you entered all the source nodes IDs.Source node 1:db9babc6b341425781a4816478cd946067ed8fb4(ps:这里是需要数据源,也就是我们的8006节点id)Source node 2:done(ps:这里直接输入done 开始生成迁移计划)... ...Do you want to proceed with the proposed reshard plan (yes/no)? Yes(ps:这里输入yes开始迁移)
查看节点信息,可以看到此时8006已经没有分配的hash槽位了,可以通过 del-nodel 来删除节点了
redis-cli--clusterdel-node127.0.0.1:8006db9babc6b341425781a4816478cd946067ed8fb4
