亲宝软件园·资讯

展开

Redis 分片集群的实现

左右盲 人气:0

1 搭建分片集群

主从和哨兵可以解决高可用、高并发读的问题。但是依然有两个问题没有解决:

针对上述问题,我们可以搭建Redis的分片集群,如图所示:

Redis的分片集群具有以下特征:

接下来我们可以动手来搭建一个Redis分片集群

1.1 集群结构

分片集群需要的节点数量较多,这里我们搭建一个最小的分片集群,包含3个master节点,每个master包含一个slave节点,结构如下:

这里我们会在同一台虚拟机中开启6个redis实例,模拟分片集群,信息如下:

IPPORT角色
192.168.211.1007001master
192.168.211.1007002master
192.168.211.1007003master
192.168.211.1008001slave
192.168.211.1008002slave
192.168.211.1008003slave

1.2 准备实例和配置

这里我的redis安装目录为/usr/local/redis-6.2.6,以下操作将以此目录进行参考,额外需要注意的是,以下操作都是在redis没有设置密码的情况下进行的,如果你的redis设置了密码,那么按照以下步骤进行就会出问题。

1)创建出7001、7002、7003、8001、8002、8003目录

# 进入/local目录
cd /usr/local
# 创建目录
mkdir 7001 7002 7003 8001 8002 8003

2)在/usr/local下准备一个新的redis.conf文件,内容如下:

port 6379
# 开启集群功能
cluster-enabled yes
# 集群的配置文件名称,不需要我们创建,由redis自己维护
cluster-config-file /usr/local/6379/nodes.conf
# 节点心跳失败的超时时间
cluster-node-timeout 5000
# 持久化文件存放目录
dir /usr/local/6379
# 绑定地址
bind 0.0.0.0
# 让redis后台运行
daemonize yes
# 注册的实例ip
replica-announce-ip 192.168.211.100
# 保护模式
protected-mode no
# 数据库数量
databases 1
# 日志
logfile /usr/local/6379/run.log

3)将这个文件拷贝到每个目录下:

# 进入/local目录
cd /usr/local
# 执行拷贝
echo 7001 7002 7003 8001 8002 8003 | xargs -t -n 1 cp redis.conf

4)修改每个目录下的redis.conf,将其中的6379修改为与所在目录一致:

# 进入/local目录
cd /usr/local
# 修改配置文件
printf '%s\n' 7001 7002 7003 8001 8002 8003 | xargs -I{} -t sed -i 's/6379/{}/g' {}/redis.conf

1.3 启动

因为已经配置了后台启动模式,所以可以直接启动服务:

# 进入/usr/local目录
cd /usr/local
# 一键启动所有服务
printf '%s\n' 7001 7002 7003 8001 8002 8003 | xargs -I{} -t redis-server {}/redis.conf

通过ps查看状态:

ps -ef | grep redis

发现服务都已经正常启动:

如果要关闭所有进程,可以执行命令:

ps -ef | grep redis | awk '{print $2}' | xargs kill

或者(推荐这种方式):

printf '%s\n' 7001 7002 7003 8001 8002 8003 | xargs -I{<!--{cke_protected}{C}%3C!%2D%2D%20%2D%2D%3E-->} -t redis-cli -p {<!--{cke_protected}{C}%3C!%2D%2D%20%2D%2D%3E-->} shutdown

1.4 创建集群

虽然服务启动了,但是目前每个服务之间都是独立的,没有任何关联。我们需要执行以下命令来创建集群,注意,以下命令需要你的redis版本在5.0之后:

redis-cli --cluster create --cluster-replicas 1 192.168.211.100:7001 192.168.211.100:7002 192.168.211.100:7003 192.168.211.100:8001 192.168.211.100:8002 192.168.211.100:8003

命令说明:

执行上述命令之后,控制台会列出当前主从节点分配的结果,即将那些slave分别分配给哪些master,并询问你是否同意,这里我们输入'yes'即可

确定之后,集群开始创建

通过命令可以查看集群状态:

redis-cli -p 7001 cluster nodes

1.5 测试

集群操作时,需要在redis-cli连接时带上-c参数才可以

redis-cli -p 7001

通过观察上述从节点的状态,我们发现7001的slave是8001,我们可以尝试在7001里插入一个数字,再从8001里获取

事实上,我们不仅可以从8001里获取到num,也可以从其他slave甚至其他master里获取到num:

而且我们发现,当我们试图从其他节点获取num时,最后都会跳转到7001,为什么会这样呢?这就涉及到我们即将讲解的插槽原理

2 散列插槽

一个Redis分片集群有0~16383共16384个插槽(hash slot),这些插槽会被平均分给每一个master节点,一个master节点映射着一部分插槽,这一点在集群创建时的信息中可以看到

在分片集群中,数据key并不是与某个节点绑定,而是与插槽绑定。数据key与插槽是多对一的关系,redis会根据key的有效部分计算插槽值,然后将key放入对应插槽,key的有效部分分两种情况:

举个例子,假如key是num,那么插槽值就会根据num来计算,如果key是{itheima}num,那么插槽值就会根据itheima来计算。计算方式是利用CRC16算法得到一个hash值,然后对16384取余,得到的结果就是slot值。

如果当前操作的key所在的插槽不属于本节点,则会发生重定向,重定向的目标是该插槽所属的节点,这个节点一定是master,如果我们连接的节点为slave,则会直接进行重定向,因为slave是没有插槽的。

针对上述几点,演示如下:

如上图所示,我们连接了7001,并试图插入数据k1,这时redis需要去判断k1所属的插槽位置,由于key中不包含’{}',因此整个key都是有效部分,redis会对k1做hash运算然后对16384取余,得到的结果是12706,这也就是k1所在的插槽的位置,在当前集群中,映射该插槽的节点是7003,此时就会发生重定向,我们也可以观察到当我们执行完set k1 1命令之后,操作的端口已经变成了7003。此时我们继续在7003端口中进行操作,比如修改数据num的值,而num所在的插槽是2765,在当前集群中,映射该插槽的节点是7001,因此当我们执行完set num 2命令之后,操作的端口又重新变成了7001

那么接下来让我们思考两个问题:

为什么数据key要与插槽绑定,而不是与节点绑定呢?

这是因为Redis的主节点有可能会出现宕机情况,也有可能由于集群伸缩而被删除,当节点删除或者发生宕机时,节点上保存的数据也就丢失了,但如果数据绑定的是插槽而不是节点,那么当出现上述情况时,就可以将故障节点的插槽转移至存活节点上。这样,数据跟插槽绑定,就永远都能够找到数据所在位置。

如何将同一类数据固定的保存在一个插槽中

在业务开发中,同一类型的数据key最好是保存在一个插槽中,因为如果分散保存,在我们调用的时候就很可能出现重定向的情况,重定向是会消耗一部分性能的。如果我们希望将同一类型的数据key最好是保存在一个插槽中,可以为这些key带上一个用’{}'包裹的固定前缀,比如{user}zs、{user}ls等等,因为我们之前说过,当key中包含"{}“,且”{}“中至少包含1个字符时,”{}"中的部分是有效部分,redis会根据这一部分来计算插槽值,如果我们将同一类型的key都加上这类前缀,就能保证这些key在同一个插槽中了

3 集群伸缩

集群已经创建了,那么如果我们想在集群中添加节点或删除节点,又应该怎么做呢?

redis-cli --cluster提供了很多操作集群的命令,可以通过下面方式查看:

其中就包括添加节点的命令:

假设现在有以下需求:向集群中添加一个新的master节点7004,并在这个节点中存储 num = 10,执行步骤如下:

3.1 创建节点并添加到集群

1)在/usr/local目录下创建一个文件夹:

cd /usr/local
mkdir 7004

2)拷贝配置文件:

cp redis.conf 7004

3)修改配置文件:

printf '%s\n' 7004 | xargs -I{<!--{cke_protected}{C}%3C!%2D%2D%20%2D%2D%3E-->} -t sed -i 's/6379/{}/g' {<!--{cke_protected}{C}%3C!%2D%2D%20%2D%2D%3E-->}/redis.conf

4)启动

redis-server 7004/redis.conf

接下来就需要将7004添加到集群中了,在执行添加操作之前,我们先来了解以下添加节点的命令

添加节点首先需要以下几个参数:

了解了该命令之后,接下来我们来执行添加节点操作:

执行命令:

redis-cli --cluster add-node 192.168.211.100:7004 192.168.211.100:7001

通过命令查看集群状态:

redis-cli -p 7001 cluster nodes

如图,7004加入了集群,并且默认是一个master节点:

但是,我们也可以看到7004是没有插槽的,因为插槽已经被其他master瓜分完毕了,因此没有任何数据可以存储到7004上,这时候我们就需要进行插槽的转移,即将其他matser的插槽分出一部分给7004

3.2 转移插槽

首先回归需求本身,我们的需求是将num=10存储在7004节点上,那么我们的目的就很明显了,首先需要知道num存储在哪个插槽上,然后将这个插槽转移到7004上即可

如上图所示,num的插槽为2765,该插槽目前是保存在7001上的,因此我们可以将0~3000的插槽从7001转移到7004,转移插槽的命令格式如下:

具体步骤如下:

1)输入转移插槽命令,这里我们需要转移的插槽在7001上,因此需要指定7001的地址

redis-cli --cluster reshard 192.168.211.100:7001

2)系统会询问我们要移动多少个插槽,这里我们输入3000即可

3)系统接着询问我们需要让哪个节点来接收插槽,这里我们需要输入7004的ID

4)接着系统会询问我们要从哪些节点中移动这些插槽到7004

这里我们有三个选择:

这里我们需要从7001中获取插槽,因此填写7001的id,然后输入done表示结束

5)接下来会冒出一大串东西,并询问我们是否确定要移动这些插槽,这里我们直接输入yes即可

输入yes之后,等待控制台打印结束,插槽也就移动完毕了

6) 输入以下命令查看插槽是否已经移动到7004

redis-cli -p 7001 cluster nodes

很显然,我们的目的已经达成了

那么如果我们要删除7004节点,又应该怎样做呢?这里笔者只给去具体思路,大家可以自行尝试一下:

4 故障转移

之前我们提到过,redis分片集群可以通过master之间的心跳监测来监测彼此之间的健康状态,从而取代哨兵。而我们也知道,哨兵的作用就是监测master和slave的状态,当认为一个master客观下线后,会从slave中选举出一个新的master,现在让我们来验证一下redis分片集群是否具备这个功能。

首先集群的初始状态是这样的,如果状态为connected则表示节点正常连接

其中7001、7002、7003、7004都是master,我们计划让7002宕机。

4.1.自动故障转移

当集群中有一个master宕机会发生什么呢?我们可以直接停止一个redis实例,例如7002:

redis-cli -p 7002 shutdown

1)首先是该实例与其它实例失去连接

2)然后是疑似宕机,7002的状态变成了disconnected

3)最后是确定下线,将7002的一个slave提升为新的master,这里由于7002只有一个slave,即8002,因此8002被选为了新的master

4)接下来我们通过以下命令再次启动7002节点

redis-server /usr/local/7002/redis.conf

当7002再次启动之后,它就已经变为一个slave节点了

上面这种叫自动故障转移,但有的时候我们可能需要做手动故障转移,比如当某台master机器比较老旧,需要升级时,我们就可以在这个集群中新增一个节点,让这个节点成为取代原来的master成为新的master,这样原来的master就会变成新master的一个slave,从而实现机器的无感知升级

4.2 手动故障转移

我们可以在slave节点中使用cluster failover命令,这个命令会让当前slave节点的master暂时宕机,宕机期间会将自身的数据转移给执行命令的slave节点,宕机结束后,之前的master会变成slave,而执行命令的slave会变成新的master。

其详细流程如下:

当slave执行了cluster failover命令之后,就会向master节点发送节点替换通知,为了避免数据的丢失,master接收到slave节点发送过来的通知后,就会暂时拒绝来自客户端的任何数据读写请求。然后,master会将自己当前的offset返回给slave,slave接收到后会判断自身数据中的offset与master的offset是否一致,如果不一致,则需要进行数据同步。由于 master 已经拒绝了客户端的所有请求,那么一旦 slave完成数据同步,也就表示slave与master之间数据是完全一致的。

数据同步结束之后,就会开始进行故障转移,让slave与master 进行角色互换,该slave成为新的master,而旧的master则转变为一个新的slave。转移结束后,slave便会标记自己为master,并向集群中每一个节点广播故障转移的结果。当集群中节点收到广播后,后续的所有交互便转移至新的master。

这种failover命令可以指定三种模式:

缺省:默认的流程,如图1~6歩,一般我们会选择这个force:省略了对offset的一致性校验,直接开始故障转移takeover:直接执行第5歩,忽略数据一致性、忽略master状态和其它master的意见

接下来我们可以尝试一下,在7002这个slave节点上执行手动故障转移,让它重新夺回master地位,步骤如下:

1)利用redis-cli连接7002,并执行cluster failover命令

2)通过redis-cli -p 7001 cluster nodes命令查看节点状态

5 RedisTemplate访问分片集群

我们只需要通过以下简单的配置,就可以通过Java代码访问我们之前部署好的分片集群

1)在boot项目的pom文件中导入依赖

 <dependency>
 	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-redis</artifactId>
 </dependency>

2)在application.yml中指定sentinel相关信息:

spring:
  redis:
    cluster:
      nodes: #指定分片集群中每一个节点信息
        - 192.168.150.101:7001
        - 192.168.150.101:7002
        - 192.168.150.101:7003
        - 192.168.150.101:8001
        - 192.168.150.101:8002
        - 192.168.150.101:8003

3)在项目的启动类中,添加一个新的bean,这个bean是用来做Redis集群的读写分离的

@Bean
public LettuceClientConfigurationBuilderCustomizer clientConfigurationBuilderCustomizer(){
    return clientConfigurationBuilder -> clientConfigurationBuilder.readFrom(ReadFrom.REPLICA_PREFERRED);
}

bean中配置的信息是读写策略,包括四种可选项:

上述配置完毕之后,我们就可以正常使用RedisTemplate来对redis集群进行操作,如果集群中某个的master宕机了,集群就会自动选举新的master,并将新master的信息发送给该Java程序,这样Java程序就可以对新master进行写操作而对其他节点进行读操作了。而这一过程都是自动完成的,无需我们过多关注

加载全部内容

相关教程
猜你喜欢
用户评论