【Redis系列9】手把手带你搭建单机版高可用分布式Redis集群(Cluster)

复制积压缓冲区

上面的部分重同步貌似看起来能解决问题,但是这又会带来另一个问题,那就是当主服务器将命令发送出去之后,为了实现部分重同步还需要将命令保存起来,否则当从服务器的偏移量低于主服务器时,主服务器也无法将命令重传播。

那么问题就来了,这个命令要保存多久呢?如果一直保存下去就会占据大量的空间,为了解决这个问题,master服务器维护了一个固定长度的FIFO队列,即复制积压缓冲区

当进行命令传播的过程中,master服务器不仅会将命令传播给所有的slave服务器,同时还会将命令写入复制积压缓冲区。复制积压缓冲区默认大小为1MB。

下面就是一个完整的部分重同步流程图:

在这里插入图片描述

也就是说,当master服务器记录的偏移量+1已经不存在与复制积压缓冲区了,就会执行一次全量同步,即发送RDB文件给从服务器。

主从服务的不足之处

主从服务器通过读写分离实现了数据的可靠性,但是其并未实现系统的高可用性。其主要存在以下两个问题:

  • 1、首次同步或者部分重同步时需要执行全量同步时发送的RDB文件如果过大,则会非常耗时。

  • 2、假如master服务器挂了,那么系统并不能手动切换master服务器,仍然需要人为进行切换。

哨兵Sentinel机制


Redis的Sentinel机制主要是为了实现Redis服务器的高可用性而设计的一种解决方案。这种方案也是为了弥补主从复制模式的不足,Sentinel机制实现了主从服务的自动切换。

Sentinel原理分析

Sentinel其本身也是一个特殊的Redis服务,在Redis的安装包内,除了redis.conf文件,还有一个sentinel.conf文件,这个就是启动sentinel服务的配置文件,启动命令则通过redis-sentinel来执行(如:./redis-sentinel ../sentinel.conf)或者也可以通过redis-server命令指定参数sentinel来启动(如:./redis-server ../sentinel.conf --sentinel)。

Sentinel主要用来监控Redis集群中的所有节点,当Sentinel服务发现master不可用时,可以实现master服务的自动切换。但是如果Sentinel服务自己挂了怎么办?所以为了实现高可用,Sentinel服务本身也是一个集群,和Redis的master-slave模式不同的是,Sentinel集群之间在正常情况下没有主从关系,相互之间是平等的,只有在需要执行故障转移时才需要进行Leader选举。

下图就是一个3个Sentinel服务集群和1主2从的Redis集群示意图:

在这里插入图片描述

Sentinel集群之间的服务会互相监控,然后每个Sentinel服务都会监控所有的master-slave节点,一旦发现master节点不可用,则Sentinel中通过选举产生的Leader节点会执行故障转移,切换master节点。

主观下线和客观下线

Sentinel服务默认以每秒1次的频率向Redis服务节点发送ping命令(Sentinel服务之间也会发送ping命令进行检测)。如果在指定时间内(可以由参数down-after-milliseconds进行控制,默认30s)没有收到有效回复,Sentinel会将该服务器标记为下线,即主观下线

down-after-milliseconds master-name milliseconds

当某一个Sentinel把master服务标记为主观下线之后,会去询问其他Sentinel节点,确认这个master是否真的下线,当达到指定数量的Sentinel服务都认为master服务器已经主观下线,这时候Sentinel就会将master服务标记为客观下线,并执行故障转移操作。

多少个Sentinel服务认定master节点主观下线才会正式将master服务标记为客观下线,由以下参数控制:

sentinel monitor

其中的quorum选项就是决定了这个数量。

需要注意的是,每个Sentinel服务的判断主观下线和客观下线的配置可能不一样,所以当Sentinel1判定master已经主观下线或者客观下线时,其他Sentinel服务并不一定会这么认为,但是只要有一个Sentinel判定master已经客观下线,其就会执行故障转移,但是故障转移并不一定是由判断为客观下线的Sentinel服务来执行,在执行故障转移的之前,Sentinel服务之间必须进行Leader选举。

Leader选举

当某一个或者多个Sentinel服务判定master服务已经下线,其会发起Leader选举,选举出Leader之后,由Leader节点执行故障转移。

Raft选举算法

Sentinel服务的Leader选举是通过Raft算法来实现的。Raft是一个共识算法(consensus algorithm),其核心思想主要有两点:

  • 1、先到先得

  • 2、少数服从多数

在Raft算法中,每个节点都维护了一个属性election timeout,这是一个随机的时间,范围在150ms~300ms之间,哪个节点先到达这个时间,哪个节点就可以发起选举投票。

选举步骤总要可以总结为以下步骤:

  • 1、发起选举的服务首先会给自己投上一票。

  • 2、然后会向其他节点发送投票请求到其他节点,其他节点在收到请求后如果在election timeout范围内还没有投过票,那么就会给发起选举的节点投上一票,然后将election timeout重置。

  • 3、如果发起选举的节点获得的票数超过一半,那么当前服务就会成为Leader节点,成为Leader节点之后就会维护一个heartbeat timeout时间属性,在每一次到达heartbeat timeout时间时,Leader节点就会向其他Follow节点发起一个心跳检测。

  • 4、Follow节点收到Leader节点的心跳包之后就会将election timeout清空,这样可以防止Follow节点因为到达election timeout而发起选举。

  • 5、假如Leader节点挂了,那么Follow节点的election timeout将不会被清空,谁先到达,谁就会再次发起选举。

PS:因为election timeout是一个随机值,虽然概率小,但也可能出现两个节点同时发起投票选举,这种情况就可能出现一次选举并不能选出Leader(比如总共4个节点,每个节点都得了2票),此时就会等待下一次首先到达election timeout的节点再次发起投票选举。

如果对Raft算法感兴趣的,可以点击这里观看演示。

Sentinel选举Leader

Sentinel中的选举虽然是源于Raft算法,但是也做了以下改进:

  • 1、触发选举并不是由election timeout时间决定,而是由谁先判定master下线来决定的。

  • 2、Sentinel节点并没有维护election timeout属性,而是维护了一个配置纪元configuration epoch属性,配置纪元是一个计数器(默认0),每一个Sentinel节点的同一个配置纪元只能投票1次(先到先得),每次投票前会将配置纪元自增+1。

  • 3、选举出Leader之后,Leader并不会向Follow节点发送心跳包告诉其他Follow节点自己成为了Leader。

故障转移

当Sentinel选举出Leader之后,Leader就会开始执行故障转移,执行故障转移主要分为一下三步:

  • 1、在已判定客观下线的master服务器的slave服务器中找到一个合格的slave服务器,向其发送replicaof no one命令,使其转换为master服务。

  • 2、向其他从服务器发送replicaof ip port命令,使其成为新master服务的slave节点。

  • 3、将已下线的master服务也设置为新的master服务的slave节点。

如何选举新的master节点

新的master选举条件主要需要参考4个因素:

  • 1、断开连接时长:首先将所有于已下线master节点断开连接时间超过down-after-milliseconds * 10的slave节点删除掉,确保salve节点的数据都是比较新的。

  • 2、slave节点的优先级排序:将所有的salve节点按照优先级进行排序,选出优先级最高的slave节点作为新的master节点(优先级由配置文件参数replica-priority决定,默认100)。

  • 3、复制偏移量:如果有多个优先级相同的slave节点,则选出复制偏移量最大的的slave节点作为新的master节点。

  • 4、进程id:如果还是没选出新的master节点,那么会再次选择进程id最小的slave节点作为新的master节点。

配置Sentinel集群

配置sentinel需要修改sentinel.conf配置文件,主要涉及到以下的一些配置属性:

protected-mode no

port 26380

pidfile /xxx/redis-sentinel-26380.pid

logfile “/xxx/sentinel.log”

dir “/xxx”

sentinel monitor mymaster xx.xxx.xxx.xxx 6370 2 //ip不要设置成127.0.0.1或者localhost

sentinel down-after-milliseconds mymaster 30000

sentinel parallel-syncs mymaster 1

sentinel failover-timeout mymaster 180000

| 参数 | 说明 |

| — | — |

| protected-mode | 是否允许外部网络访问 |

| port | sentinel端口 |

| pidfile | pid文件 |

| logfile | 日志文件 |

| dir | sentinel工作主目录 |

| sentinel monitor | 监控的master服务名称,ip和端口,其中ip建议使用真实ip取代本机ip |

| sentinel down-after-milliseconds | master下线多少毫秒才会被Sentinel 判定为主观下线 |

| sentinel parallel-syncs | 切换新的master-slave时,slave需要从新的master同步数据,这个数字表示允许多少个slave同时复制数据 |

| sentinel failover-timeout | 故障转移超时时间,这个时间有4个含义 |

注意上面的master服务名称可以取值,但是在同一个Sentinel集群中需要保持一致,否则会无法正确监控。

故障转移超时时间用在了以下4个地方:

  • 1、同一个sentinel对同一个master两次failover之间的间隔时间。

  • 2、从检测到master服务器故障开始,到被强制切换到新的master服务器并开始复制数据为止的时间。

  • 3、取消已经在进行故障转移(没有产生任何配置更改的故障转移)所需的时间。

  • 4、将所有salve配置新的master节点所需要的时间。超过这个时间如果仍然没有完成还是会继续进行,但是不一定会按照配置parallel-syncs所指定的并行数来进行。

Sentinel机制日志解析

下图就是一个Sentine故障转移的日志,最开始6371为被Sentinel监控的master服务器:

在这里插入图片描述

  • 1-7行表示Sentinel启动完毕,正在监控6371端口的master服务器。

  • 8-9行表示Sentinel发现了master服务有两个slave节点,端口为6370和6372。

  • 10行表示Sentinel判定6371的master服务器已经主观下线。

  • 11行的1/1表示已经满足客观下线条件,所以Sentinel将master判定为客观下线。

  • 12行表示将配置纪元自增1。

  • 13行表示要开始准备故障转移,但是需要先进性Leader选举。

  • 14行表示自己给自己投了1票,因为这里只配置了一个Sentinel,所以他成为了Leader,由它来执行故障转移。

  • 15-17行表示经过一些列条件判定之后,6370被推选为新的master。

  • 18-21行表示Sentinel像6370服务发送slaveof no one命令,使其成为新的master节点,并等待这个过程完成之后修改其配置文件。

  • 22-25行表示6372服务开始同步新的master节点数据。

  • 26行正式将master服务器的地址切换到6370(因为这里只有1主2从,挂了1个还有2个,如果还有其他从服务器,也需要依次来复制数据)。切换master地址之后客户端就可以获取到新的master服务地址。

  • 27行表示将6372服务器添加到新的master服务器的slave列表。

  • 28-29行表示将旧的master-6371服务器设置为新的master服务器的slave节点,这样当6371再次启动之后,会成为新的master服务器的slave节点。

Sentinel机制的使用

Sentinel机制下,客户端应该怎么连接上master服务呢?因为master是可能改变的,所以在Sentinel机制下,客户端需要连接上Sentinel服务,然后从Sentinel服务获得master的地址进行连接。如下图所示:

在这里插入图片描述

Jedis使用Sentinel机制

下面就是一个使用Jedis客户端使用Sentinel机制的例子。

1、引入pom依赖:

redis.clients

jedis

2.9.0

compile

2、新建一个测试类TestJedisSentinel进行测试:

package com.lonelyWolf.redis.sentinel;

import redis.clients.jedis.JedisSentinelPool;

import java.util.HashSet;

import java.util.Set;

public class TestJedisSentinel {

private static JedisSentinelPool pool;

private static JedisSentinelPool initJedisSentinelPool() {

// master的名字是sentinel.conf配置文件里面的名称

String masterName = “mymaster”;

Set sentinels = new HashSet();

sentinels.add(“xx.xxx.xxx.xxx:26380”);

// sentinels.add(“xx.xxx.xxx.xxx:26381”);

// sentinels.add(“xx.xxx.xxx.xxx:26382”);

pool = new JedisSentinelPool(masterName, sentinels);

return pool;

}

public static void main(String[] args) {

JedisSentinelPool pool = initJedisSentinelPool();

pool.getResource().set(“name”, “longly_wolf”);

System.out.println(pool.getResource().get(“name”));

}

}

连接时需要把所有Sentinel的连接建立并放入池内,然后客户端会遍历其中所有服务,找到第一个可用的Sentinel服务,并获取到master服务器的地址,然后建立连接。

SpringBoot使用Sentinel机制

如果使用SpringBoot,则需加入以下两个配置:

spring.redis.sentinel.master=mymaster

spring.redis.sentinel.nodes=ip:port,ip:port,ip:port

第一个参数sentinel.conf中自定义的名字,第二个参数需要配置所有的Sentinel服务器ip和端口信息。

Sentinel机制的不足之处

哨兵机制虽然实现了高可用性,但是仍然存在以下不足:

  • 1、主从切换的过程中会丢失数据,因为只有一个 master,所以在切换过程中服务是不可用的。

  • 2、哨兵机制其本质还是master-slave集群,即:1主N从。也就是master服务器依然只有1个,并没有实现水平扩展。

Redis分布式集群方案


要实现一个Redis水平扩展,需要实现分片来进行数据共享,可以有三种思路:

  • 1、在客户端实现相关的逻辑,由客户端实现分片决定路由到哪台服务器。

  • 2、将分片处理的逻辑运行一个独立的中间服务,客户端连接到这个中间服务,然后由中间服务做请求的转发。

  • 3、基于服务端实现。

客户端实现分片

客户端实现分片的话,Jedis提供了这个分片(Sharding)功能:

package com.lonelyWolf.redis.cluster.client;

import redis.clients.jedis.*;

import java.util.Arrays;

import java.util.List;

/**

  • Jedis客户端实现集群分片功能

*/

public class TestJedisSharding {

public static void main(String[] args) {

JedisPoolConfig poolConfig = new JedisPoolConfig();

//创建所有的连接服务分片

JedisShardInfo shardInfo1 = new JedisShardInfo(“xx.xxx.xxx.xxx”, 6370);

JedisShardInfo shardInfo2 = new JedisShardInfo(“xx.xxx.xxx.xxx”, 6371);

//将所有连接加入连接池

List shardInfoList = Arrays.asList(shardInfo1, shardInfo2);

ShardedJedisPool jedisPool = new ShardedJedisPool(poolConfig, shardInfoList);

//创建10个key值存入服务器

ShardedJedis jedis = jedisPool.getResource();//获取连接

for (int i=1;i<=10;i++){

jedis.set(“name” + i,“lonely_wolf” + i);

}

//取出key和其所在服务器信息

for (int i=1;i<=10;i++){

String key = “name” + i;

Client client = jedis.getShard(“name”+i).getClient();

System.out.println(“key值:” + jedis.get(key) + “,存在于服务器的端口为:” + client.getPort());

}

}

}

输出结果如下:

在这里插入图片描述

然后我们分别去服务器上看一下可以看到是完全匹配的:

在这里插入图片描述

在这里插入图片描述

客户端分片的缺陷

客户端实现分片的好处就是配置简单,而且分片规则都是由客户端来实现,但是却存在以下缺点:

  • 1、客户端需要支持分片,假如代码移植到其他项目,而其他项目使用的Redis客户端不支持分片,那就会造成集群不可用。

  • 2、客户端分片模式下不能很好地实现服务器动态增减。

中间代理服务实现分片

中间代理服务实现分片其实就是将客户端的分片逻辑进行抽取,然后单独部署成一个服务,这样做的好处是客户端不需要处理分片逻辑,而且也不用关心服务器的增减。

中间代理服务分片的方案有两个使用比较广泛,那就是(这两种方案如果有兴趣的可以点击对应的链接进去github获取到对应的源码进行了解):

使用中间服务的最大缺陷就是其本身是独立的服务,而为了保证高可用,也需要对这个中间服务进行高可用的集群配置,所以会导致整个系统架构更加复杂。

Redis Cluster方案

Redis Cluster是Redis3.0版本正式推出的,实现了高可用的分布式集群部署。

一个Cluster集群由多个节点组成,Redis当中通过配置文件cluster-enabled是否为yes来决定当前Redis服务是否开启集群模式,只有开启了集群模式,才可以使用集群相关的命令,与之相反的,如果没有开启集群的Redis我们称之为单机Redis。

数据分片

水平集群的最关键一个问题就是数据应该如何分配,主流的有哈希后取模一致性哈希两种数据分片方式。

哈希后取模

哈希后再取模这种方式比较简单,就是将key值进行哈希运算之后再来除以节点数,即:hash(key)%N,然后根据余数来决定落在哪个节点。这种方式数据分布会比较均匀,但是这种方式同时也是一种静态的数据分片方式,一旦节点数发生变化,需要重新计算然后将数据进行重新分布。

一致性哈希

一致性哈希的原理就是把所有的哈希值组织成一个虚拟的哈希圆环,其中起点0和终点232-1位置重叠,如下图所示:

在这里插入图片描述

上图中绿色表示当前存在的节点,黄色表示数据落点处,如果数据没有落在节点上,则会落到顺时针找到第一个节点,如上图中黄色的数据最终会落到Node1节点上。

这种方式的好处就是如果新增或者删除了节点,那么最多也只会影响相关的1个节点的数据,而不需要将所有的数据全部进行重新分片。

下图就是新增了一个Node5节点,那么其影响的就是Node2到Node5之间的这部分数据需要由原来落在Node3节点的数据改到Node5节点,对其他节点数据没有任何影响。

在这里插入图片描述

一致性哈希有一个问题就是当节点数比较少的情况下会导致数据分布不均匀。如下图所示:

在这里插入图片描述

这里面只有2个节点,但是有3条数据落在了Node2而Node1只有1条数据,分布不均匀,为了解决这种问题,一致性哈希又引入了虚拟节点。当节点过少的时候,在哈西环上创建一些虚拟节点,然后按照范围指派给实际节点。

如下图,蓝色的就是虚拟节点,其中Node1-1的数据会分到Node1上,而Node2-1会分到Nde2上,这样就可以使得数据较为平均:

在这里插入图片描述

槽(slot)

Redis当中的数据分布并没有采用以上两种数据分片方式,而是另外引入了一个槽(slot)的概念。

Redis将整个数据库划分为16384个槽(slot),然后根据当前数据库的节点数来划分,每个节点负责一部分槽。如下图所示:

在这里插入图片描述

槽数组也是一个位图数组(bit array),每个对象分配到哪个槽是根据CRC16算法得到的,需要注意的是:一个key落在哪一个槽是不会改变的,但是每个Redis Group(Redis主从服务)负责的槽可能会发生变化

如何让相关业务数据强制落在同一个槽

有时候我们同一个业务需要缓存一些数据,假如这些数据落在不同的槽归属于不同的服务器负责,那么在有些时候是会带来不便的,比如multi开启事务就不能跨节点,所以我们应该如何让相关业务数据强制落在同一个槽呢?

Redis提供了一种{}机制,当我们的key里面带有{}的时候,那么Redis只会通过计算{}里面的字符来进行哈希,所以我们可以通过这种方式来强制同一种业务数据落在同一个槽。

如下所示,我们可以将所有用户相关信息的key都带上{user_info},当然get的时候是需要带上{}这个完整的key,带上{}只是影响slot的计算,其他并不影响:

在这里插入图片描述

客户端的重定向

Redis集群中服务器的数据分布客户端是不可知的,所以假如在一个客户端获取key,然后这个key不存在当前服务器那么服务器会根据后台自己存储的信息判断出当前key所在槽归属于哪台服务器负责,会返回一个MOVED指令,带上服务器的ip和端口来告诉客户端当前key所在的服务器,客户端接收到MOVED指令之后会进行重定向,然后获取key值。

这种方式客户端获取一个key可能会需要连接2次服务器。Jedis等客户端会在本地维护一份 slot和node的映射关系,所以大部分时候不需要重定向,这种客户端也称之为smart jedis。但是这种特性并不是所有的客户端都支持的。

重新分片

Redis集群中,可以将已经指派给某个节点的任意数量的槽重新指派给另一个节点,且重新指派的槽所属的键值对也会被移动到新的目标节点,利用这个特性就可以实现新增节点或者删除节点。

重新分片的操作是可以在线进行的,也就是说在重新分片的过程中,集群不需要下线,并且参与重新分片的节点也可以继续处理命令请求。

ASK错误

在重新分片的过程中会涉及到键值对的迁移,那么可能会出现这样一种情况:被迁移槽的一部分键值对保存在目标节点里面,而还有一部分键值对仍然在原节点还没来得及前迁移。

而假如这时候客户端来访问原节点时发现本来应该在这个节点的键值对已经被迁移到目标节点了,那么这时候就会返回一个ASK错误来引导客户端到目标节点去访问,这时候客户端并不能直接访问去访问目标节点,因为目标节点在所有键值对被迁移完成前是无法直接通过正常明星访问得到的。

目标节点在接收其他节点指派过来的槽所对应键值对时,会通过一个临时数组属性importing_slots_from[16384]来存储,所以客户端在接收到返回的ASK错误之后,客户端会先向目标节点发送一个ASKING命令,之后再发送原本想要执行的命令,这样目标节点就知道当前客户端访问的key是正在迁移过来的,知道去哪里取这个数据。

需要注意的时,ASKING命令的作用是客户端会打上一个标记,而这个标记是临时性的,当服务器执行过一个带有ASKING标记的命令之后就会将该标记清除。

下图就是一个特殊情况下的执行流程图:

在这里插入图片描述

ASK错误和MOVED错误
  • ASK错误可以认为是一种过渡时期的特殊错误,只有在发生槽迁移的过程中,发现原本属于node1管理的槽被指派给了node2,而数据又还没有迁移完成的情况下,因为这是一种特殊场景,所以客户端收到ASK错误之后不能直接连到目标节点执行命令(这时候直接连过去目标节点会返回MOVED命令指向node1),客户端收到ASK错误之后需要先向node2节点发送一个ASKING命令给自己打上标记才能真正发送客户端想要执行的命令。

  • MOVED错误是在正常情况或者说槽已经完成重新分片的情况下返回的错误,这种情况服务器发现当前key所在槽不归自己管,那么就会直接MOVED错误和负责管理该槽的服务器信息,客户端收到MOVED错误之后就会再次连接目标节点执行命令。

Redis Group

Redis集群中的节点并不是只有一台服务器,而是一个由master-slave组成的集群,称之为Redis Group,那么既然是主从就会涉及到主从的数据复制,复制的原理和我们前面讲述的是一样的,但是故障检测以及故障转移和前面讲述的Sentinel模式下有点区别。

故障检测

集群中的各个节点都会定期向集群中的其他节点发送PING消息来检测对方是否在线,如果接收PING消息的节点没有在规定时间内返回PONG,那么发送PING消息的节点就会将其标记为疑似下线(probable fail,PFAIL)。

PFAIL类似于Sentinel机制下的主观下线,不同的是集群中并不是发现PFAIL之后才会去询问其他节点,而是定期通过消息的方式来交换状态。

在Redis集群中,各个节点会通过互相发送消息(PING)的方式来交换集群中各个节点的状态信息,一旦某一个主节点A发现半数以上的主节点都将某一个主节点B标记为PFAIL,这时候主节点A就会将主节点B标记为已下线(FAIL),然后主节点A会向集群中发一条主节点B已下线的FAIL消息的广播,所有收到这条广播消息的节点会立即将主节点B标记为已下线

注意:如果广播之后,发现这个master节点所在的Redis Group中,所有的slave节点也挂了,那么这时候就会将集群标记为下线状态,整个集群将会不可用

故障转移

当一个从节点发现自己的主节点已下线时,从节点将会开始进行故障转移,执行故障转移的从节点需要经过选举,如何选举在后面讲,故障转移的步骤主要分为以下三步:

  • 1、被选中的从节点会执行slave no one命令,使得自己成为新的主节点。

  • 2、新的主节点会将已下线的主节点负责的槽全部指派给自己。

  • 3、新的主节点会向集群发一条PONG消息的广播,收到这条消息的其他节点就会知道这个节点已经成为了新的master节点,并且将旧节点的槽进行了接管。

选举新的master节点

一个从节点发现主节点下线后,会发起选举,选举步骤如下:

  • 1、该从节点会将集群的配置纪元自增1(和Sentinel机制一样,配置纪元默认值也是0)。

  • 2、该从节点会向集群发一条CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST的消息广播。

  • 3、其他节点收到广播后,master节点会判断合法性,如果一个主节点具有投票权(正在负责处理槽),那么就会返回一个CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK消息给他投1票(同样的,一个配置纪元内,一个master节点最多只能投票1次)。

  • 4、如果同时有多个从节点发起投票,那么每个从节点都会统计自己所得票数,然后进行统计,只有得到了大于参与投票的主节点数的一半的从节点,就会成为新的master节点。

  • 5、如果一个配置纪元内没有一个从节点达到要求,那么集群会把配置纪元再次自增,并再次进行选举,直到选出新的master。

为什么槽定义为16384个

前面我们提到Redis集群中,判断一个key值落在哪个槽上是通过CRC16算法来计算的,CRC16算法产生的hash值有16bit,也就是可以产生216(即:65536)个值。

Redis中每秒都在发送PING消息,发送PING消息的节点会带上自己负责处理的槽信息,如果创建65536个槽(0-65535),那么就需要65536大小的位图(Bitmap)来存储,也就是需要:65535/8/1024=8k的位图数组空间,这对于频繁发送的心跳包来说太大了,而如果使用16384那么只需要16384/8/1024=2kb的位图数组空间,这是原因之一。

另一个原因是Redis集群中的节点数官方建议不要超过1000个,那么对于最大的1000个节点来说,16384个槽是比较合适的,因为16384/1000=16,也就是极端情况下每个节点负责16个slot,这是比较合适的,槽如果太小了(即slot/N不宜过大)会影响到位图的压缩。

下面截图就是Redis作者的回复:

在这里插入图片描述

手动配置一个Redis Cluster集群

配置一个Redis Cluster至少需要3个master节点,所以为了高可用,每个master节点又至少需要配置一个slave节点,也就是最低配版的高可用Redis Cluster服务是需要3主3从。

为什么至少需要3个maser节点

为什么至少需要3个master节点的原因是如果一个master挂了,至少3个节点才能执行后面的故障转移等操作。1个master就不用说了,挂了就没了;如果是2个master节点就可能出现这种情况A发现B挂了,但是A自己只有1个,也就是认为B挂了的主节点刚好等于所有主节点的一半,没大于一半,所以他就没办法断定节点B就是挂了,所以2个master也不行。

手把手搭建一个3主3从Redi集群
  • 1、首先需要把配置文件cluster-enabled参数改为yes,这样才能启动集群模式。

  • 2、还需要把配置文件cluster-config-file 修改一下,这个是节点文件,由每个节点自己管理,但是我们需要配置好文件名和路径,主要的配置文件如下所示:

port 6370 //端口号

daemonize yes //是否后台运行

protected-mode no //网络是否允许对外访问 no表示允许

dir /usr/local/redis-6370/ //redis工作主目录

cluster-enabled yes //是否开启集群模式

cluster-config-file /usr/local/redis-6370/nodes-6370.conf //node文件

cluster-node-timeout 5000 //集群超时时间

appendonly yes //是否开启aop持久化

pidfile /var/run/redis_6370.pid //pid文件

  • 3、完成好配置之后,依次启动6个服务。

在这里插入图片描述

  • 4、登录任意一个节点,执行cluster info命令会发现当前集群的状态是下线状态(fail)

fail

  • 5、执行下面的创建集群命令,注意:ip要使用真实的ip,而不要使用127或者localhost等本机ip。

redis-cli --cluster create ip:6370 ip:6371 ip:6372 ip:6373 ip:6374 ip:6375 --cluster-replicas 1

执行之后会有如下提示,也就是系统自动帮我们分配好了槽而且将主从确定了:

在这里插入图片描述

-6 输入yes同意系统的分配方案,然后等待配置成功。

在这里插入图片描述

  • 7、配置成功之后纳入任意一个节点执行cluster info命令查看集群信息

在这里插入图片描述

搭建集群常见错误

搭建集群过程中可能会出现以下错误:

  • 1、[ERR] Node 47.107.155.197:6370 is not empty. Either the node already knows other nodes (check with CLUSTER NODES) or contains some key in database 0.

在这里插入图片描述
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

由于篇幅原因,就不多做展示了
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。[外链图片转存中…(img-bEhLBrRn-1713535620308)]

[外链图片转存中…(img-yk7vwHuv-1713535620311)]

[外链图片转存中…(img-SeXToSry-1713535620313)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

[外链图片转存中…(img-FkqGOksM-1713535620315)]

[外链图片转存中…(img-uDRAn7iS-1713535620317)]

[外链图片转存中…(img-wF5WQLoe-1713535620319)]

[外链图片转存中…(img-HBcW3uje-1713535620321)]

[外链图片转存中…(img-wX9iwTbk-1713535620323)]

[外链图片转存中…(img-gCgJAvQs-1713535620325)]

由于篇幅原因,就不多做展示了
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 8
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值