Docker容器 - 实现Redis Cluster(集群)模式 哈希槽分区进行亿级数据存储

目录

问题场景

思路

解决方案

一、哈希取余分区

二、一致性哈希算法分区

1.算法构建一致性哈希环

2.服务器IP节点映射 

3.key落到服务器的落键规则

三、哈希槽分区

哈希槽计算

3主3从Redis集群扩缩容配置

一、新建6个docker容器实例

二、进入容器redis-node-1并为6台设备构建集群关系

1.进入容器

2.构建主从关系

三、以6381作为切入点,查看集群状态

主从容错切换迁移

一、数据读写存储

1.通过exec进入

2.对6381新增几个key 

3.防止路由失效加参数-c

4.查看集群信息

二、容错切换迁移

1.宕机测试

2.进行还原

主从扩容

一、新建6387和6388两个节点

二、进入6387容器实例内部

三、将新增的6387作为master节点加入集群

四、查看集群信息--第一次

五、重新分配槽位

六、查看集群信息--第二次

七、为主节点6387分配从节点6388

八、 查看集群信息--第三次

主从缩容 

一、检查集群情况并获得6388的节点ID

二、将6388删除

三、将6387的槽位清空 重新分配

四、检查一下集群状态信息

五、删除6387


问题场景

有1-2亿跳数据需要进行缓存,如何设计存储方案?

思路

单台机器绝对无法完成,肯定是要使用分布式存储。问题在于redis如何落地,假设共有5台机器,那么数据来的时候应该存在哪一台?读取数据时能否保证读取的机器正确?


解决方案

一、哈希取余分区

Docker容器 - 实现Redis Cluster(集群)模式 哈希槽分区进行亿级数据存储_第1张图片

2亿条记录就是2亿个k,v,我们单机不行必须要分布式多机,假设有3台机器构成一个集群,用户每次读写操作都是根据公式:hash(key) % N个机器台数,计算出哈希值,用来决定数据映射到哪一个节点上。举例:取余为0,这里则存放在第一台,为1则存放在第二台,为2则存放在第三台。Redis中的key一般是不重复的,使用同一套哈希算法可以保证你存放数据和读取数据都会在同一台机器设备上。

优点:

简单粗暴,只需要预估好数据规划好节点,例如3台、8台、10台,就能保证一段时间的数据支撑。使用Hash算法让固定的一部分请求落到同一台服务器上,这样每台服务器固定处理一部分请求(并维护这些请求的信息),起到负载均衡+分而治之的作用。

缺点:

原来规划好的节点,进行扩容或者缩容就比较麻烦了额,不管扩缩,每次数据变动导致节点有变动,映射关系需要重新进行计算,在服务器个数固定不变时没有问题,如果需要弹性扩容或故障停机的情况下,原来的取模公式就会发生变化:Hash(key)/3会变成Hash(key) /?。此时地址经过取余运算的结果将发生很大变化,根据公式获取的服务器也会变得不可控。
某个redis机器宕机了,由于台数数量变化,会导致hash取余全部数据重新洗牌。 

 现实中免不了对机器设备的扩缩容,所以此方案不推荐。(小厂可以用用)


二、一致性哈希算法分区

分布式缓存数据变动和映射问题。若某个机器宕机了,分母数量改变,自然取余数就会出现问题。提出一致性Hash解决方案,目的是当服务器个数发生变动时,尽量减少客户端到服务器的映射关系。其有三大步骤,如下:

1.算法构建一致性哈希环

一致性哈希算法必然有个hash函数并按照算法产生hash值,这个算法的所有可能哈希值会构成一个全量集,这个集合可以成为一个hash空间[0,2^32-1],这个是一个线性空间,但是在算法中,我们通过适当的逻辑控制将它首尾相连(0 = 2^32),这样让它逻辑上形成了一个环形空间。
它也是按照使用取余的方法,前面笔记介绍的节点取余法是对节点(服务器)的数量进行取余。而一致性Hash算法是对2^32取余,简单来说,一致性Hash算法将整个哈希值空间组织成一个虚拟的圆环,如假设某哈希函数H的值空间为0-2^32-1(即哈希值是一个32位无符号整形),整个哈希环如下图:整个空间按顺时针方向组织,圆环的正上方的点代表0,0点右侧的第一个点代表1,以此类推,2、3、4、……直到2^32-1,也就是说0点左侧的第一个点代表2^32-1, 0和2^32-1在零点中方向重合,我们把这个由2^32个点组成的圆环称为Hash环。

简单的来说,咱们平时所说的集合就像一条线一样,它是直的。但是在这里,咱们把他掰成了一个圆形,让它首尾闭合。这个圆环就是所谓的哈希环。之后所有的东西会都存放在这个圆环之内。

Docker容器 - 实现Redis Cluster(集群)模式 哈希槽分区进行亿级数据存储_第2张图片

2.服务器IP节点映射 

将集群中各个IP节点映射到环上的某一个位置。
将各个服务器进行一个哈希,具体可以选择服务器的IP或主机名作为关键字进行哈希,这样每台机器就能确定其在哈希环上的位置。假如4个节点NodeA、B、C、D,经过IP地址的哈希函数计算(hash(ip)),使用IP地址哈希后在环空间的位置如下:  

Docker容器 - 实现Redis Cluster(集群)模式 哈希槽分区进行亿级数据存储_第3张图片

3.key落到服务器的落键规则

当我们需要存储一个kv键值对时,首先计算key的hash值,hash(key),将这个key使用相同的函数Hash计算出哈希值并确定此数据在环上的位置,从此位置沿环顺时针“行走”,第一台遇到的服务器就是其应该定位到的服务器,并将该键值对存储在该节点上。
如我们有Object A、Object B、Object C、Object D四个数据对象,经过哈希计算后,在环空间上的位置如下:根据一致性Hash算法,数据A会被定为到Node A上,B被定为到Node B上,C被定为到Node C上,D被定为到Node D上。

Docker容器 - 实现Redis Cluster(集群)模式 哈希槽分区进行亿级数据存储_第4张图片

优点:

一致性哈希算法的容错性

假设Node C宕机,可以看到此时对象A、B、D不会受到影响,只有C对象被重定位到Node D。一般的,在一致性Hash算法中,如果一台服务器不可用,则受影响的数据仅仅是此服务器到其环空间中前一台服务器(即沿着逆时针方向行走遇到的第一台服务器)之间数据,其它不会受到影响。简单说,就是C挂了,受到影响的只是B、C之间的数据,并且这些数据会转移到D进行存储。

Docker容器 - 实现Redis Cluster(集群)模式 哈希槽分区进行亿级数据存储_第5张图片

一致性法希算法的扩容性 

数据量增加了,需要增加一台节点NodeX,X的位置在A和B之间,那收到影响的也就是A到X之间的数据,重新把A到X的数据录入到X上即可,不会导致hash取余全部数据重新洗牌。

Docker容器 - 实现Redis Cluster(集群)模式 哈希槽分区进行亿级数据存储_第6张图片

缺点:

一致性哈希算法的数据倾斜问题

一致性Hash算法在服务节点太少时,容易因为节点分布不均匀而造成数据倾斜(被缓存的对象大部分集中缓存在某一台服务器上)问题,例如系统中只有两台服务器:

Docker容器 - 实现Redis Cluster(集群)模式 哈希槽分区进行亿级数据存储_第7张图片

说白了如果设备机器太少,会导致存储不均匀。(小厂用不了) 


三、哈希槽分区

哈希槽实质就是一个数组,数组[0,2^14 -1]形成hash slot空间。它能解决均匀分配的问题,在数据和节点之间又加入了一层,把这层称为哈希槽(slot),用于管理数据和节点之间的关系,现在就相当于节点上放的是槽,槽里放的是数据。

Docker容器 - 实现Redis Cluster(集群)模式 哈希槽分区进行亿级数据存储_第8张图片

这里其实有点类似于Java中,Service层调用了一个DAO接口,以完成对数据的增删改查。

槽解决的是粒度问题,相当于把粒度变大了,这样便于数据移动。
哈希解决的是映射问题,使用key的哈希值来计算所在的槽,便于数据分配。

一个集群只能有16384个槽,编号0-16383(0-2^14-1)。这些槽会分配给集群中的所有主节点,分配策略没有要求。可以指定哪些编号的槽分配给哪个主节点。集群会记录节点和槽的对应关系。解决了节点和槽的关系后,接下来就需要对key求哈希值,然后对16384取余,余数是几key就落入对应的槽里。slot = CRC16(key) % 16384。以槽为单位移动数据,因为槽的数目是固定的,处理起来比较容易,这样数据移动问题就解决了。

哈希槽计算

Redis 集群中内置了 16384 个哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点。当需要在 Redis 集群中放置一个 key-value时,redis 先对 key 使用 crc16 算法算出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,也就是映射到某个节点上。如下代码,key之A 、B在Node2, key之C落在Node3上。

这里的[0-5460]槽位范围后面要考!

Docker容器 - 实现Redis Cluster(集群)模式 哈希槽分区进行亿级数据存储_第9张图片

POM依赖: 

        
            org.springframework.boot
            spring-boot-starter-data-redis
        

槽位: 

    @Test
    public void test1(){
        System.out.println(SlotHash.getSlot("A"));//6376
        System.out.println(SlotHash.getSlot("B"));//10374
        System.out.println(SlotHash.getSlot("C"));//14503
        System.out.println(SlotHash.getSlot("van"));//8155
    }

Docker容器 - 实现Redis Cluster(集群)模式 哈希槽分区进行亿级数据存储_第10张图片


3主3从Redis集群扩缩容配置

一、新建6个docker容器实例

启动容器,一共六个,三主三从:

docker run -d --name redis-node-1 --net host --privileged=true -v /data/redis/share/redis-node-1:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6381
docker run -d --name redis-node-2 --net host --privileged=true -v /data/redis/share/redis-node-2:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6382
docker run -d --name redis-node-3 --net host --privileged=true -v /data/redis/share/redis-node-3:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6383
docker run -d --name redis-node-4 --net host --privileged=true -v /data/redis/share/redis-node-4:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6384
docker run -d --name redis-node-5 --net host --privileged=true -v /data/redis/share/redis-node-5:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6385
docker run -d --name redis-node-6 --net host --privileged=true -v /data/redis/share/redis-node-6:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6386

第一次拉取镜像启动时效果如下: 

Docker容器 - 实现Redis Cluster(集群)模式 哈希槽分区进行亿级数据存储_第11张图片

 全部执行:

Docker容器 - 实现Redis Cluster(集群)模式 哈希槽分区进行亿级数据存储_第12张图片

运行后记得ps查看一下,以确保没有问题:

docker ps

 以上代码的分步解释如下:

docker run:创建并运行docker容器实例

--name redis-node-6:容器名称

--net host:使用宿主机的IP和端口,默认

--privileged=true:获取宿主机root用户权限

-v /data/redis/share/redis-node-6:/data:容器数据卷

redis:6.0.8:redis镜像和版本号

--cluster-enabled yes:开启redis集群

--appendonly yes:开启持久化

--port 6385:redis端口号


二、进入容器redis-node-1并为6台设备构建集群关系

1.进入容器

docker exec -it redis-node-1 /bin/bash

2.构建主从关系

这里需要注意一下自己的真实IP地址:

redis-cli --cluster create 192.168.150.30:6381 192.168.150.30:6382 192.168.150.30:6383 192.168.150.30:6384 192.168.150.30:6385 192.168.150.30:6386 --cluster-replicas 1

--cluster-replicas 1 表示为每个master创建一个slave节点,执行效果:

同时这里有一个槽位范围显示,例如一号机的[0-5460],划重点,后面还要考!

Docker容器 - 实现Redis Cluster(集群)模式 哈希槽分区进行亿级数据存储_第13张图片

输入yes并回车:

主从分配完成。


三、以6381作为切入点,查看集群状态

查看节点状态:

redis-cli -p 6381
CLUSTER info

Docker容器 - 实现Redis Cluster(集群)模式 哈希槽分区进行亿级数据存储_第14张图片

 CLUSTER nodes可以很清楚的查看谁是主谁是从 

CLUSTER nodes

Docker容器 - 实现Redis Cluster(集群)模式 哈希槽分区进行亿级数据存储_第15张图片


主从容错切换迁移

一、数据读写存储

此时我们进入redis写点数据

1.通过exec进入

redis-cli -p 6381

2.对6381新增几个key 

 随便set点什么进去

set k1 v1
set k2 v2
set k3 v3
set k4 v4

Docker容器 - 实现Redis Cluster(集群)模式 哈希槽分区进行亿级数据存储_第16张图片

存储时报错。原因是因为他超出了单台机器槽位的范围(前面所说的考点)。我们回头看一眼在分配主从时候所看到槽位范围,一号机是[0-5460],而这里已经超出,甚至达到了6000+。所以存不进去。那连这都存不进去还算什么集群?

原因是我们的启动方式有问题,这里已经不能再继续使用redis-cli -p 6381这种方式来连接(这是单机的连接方式),这种方式不适用于集群环境。正确方法如下一节

3.防止路由失效加参数-c

exit退出

exit

重新连接

redis-cli -p 6381 -c

FLUSHALL清空一下

FLUSHALL

然后我们重新set数据

set k1 v1

存入成功,这里可以看到它被存到了12706号槽位,上面我们提到过,一号机的槽位范围只有[0-5460],这里通过算法后,成功把数据存到了三号机上面。

Redirected to slot[12706]:已经重定向到12706号槽位

这条命令结束后我们发现自己跳转到了6383节点,我们再存点数据试试

set k2 v2

又成功跳转回了6381

set k3 v3

 如果存储槽位在本机的槽位范围之内,则不会跳转,直接存储

set k4 v4

 这里又跳转到了6382,这里的槽位[8455]可以对照之前的槽位范围截图看一眼,是存储到了二号机上。如何查看槽位范围如下一节

4.查看集群信息

注意这里要换成自己的真实IP

redis-cli --cluster check 192.168.150.30:6381

Docker容器 - 实现Redis Cluster(集群)模式 哈希槽分区进行亿级数据存储_第17张图片


二、容错切换迁移

1.宕机测试

这里来演示一下,当master宕机后,slave如何进行切换上位

我们先看看自己设备的主从关系,连接后进行查看

redis-cli -p 6381
CLUSTER nodes

Docker容器 - 实现Redis Cluster(集群)模式 哈希槽分区进行亿级数据存储_第18张图片

这里可以得知我的6381从机为6385

再将6381宕掉,在宿主机上docker stop一下

docker stop redis-node-1

Docker容器 - 实现Redis Cluster(集群)模式 哈希槽分区进行亿级数据存储_第19张图片

停掉后我们再ps查看一下

docker ps

Docker容器 - 实现Redis Cluster(集群)模式 哈希槽分区进行亿级数据存储_第20张图片

可以确定,一号机已经被停掉了(宕机了),这时候我们再通过二号机,也就是6382上去查看一下状态信息

docker exec -it redis-node-2 /bin/bash
redis-cli -p 6382 -c

CLUSTER nodes

Docker容器 - 实现Redis Cluster(集群)模式 哈希槽分区进行亿级数据存储_第21张图片

可以看到6381挂了,6385成功上位,从slave变成了master

get一下数据,看看能否查找到

get k1
get k2
get k3
get k4

Docker容器 - 实现Redis Cluster(集群)模式 哈希槽分区进行亿级数据存储_第22张图片

数据查找成功,很明显你挂了就挂了,不影响我读取数据 

2.进行还原

我们来看看,如果6381复活了,那么它和6385的主从关系是否会发生变化

启动容器

docker start redis-node-1 
docker ps

接着去查看状态信息

CLUSTER nodes

Docker容器 - 实现Redis Cluster(集群)模式 哈希槽分区进行亿级数据存储_第23张图片

可以看出6381在复活后依旧保持slave,6385依旧是master,如果想要让6381再次当回master,则只需要将6385停掉,然后重新启动即可(这里不建议使用docker restart,建议是先stop再start)

docker stop redis-node-5

Docker容器 - 实现Redis Cluster(集群)模式 哈希槽分区进行亿级数据存储_第24张图片

CLUSTER nodes

Docker容器 - 实现Redis Cluster(集群)模式 哈希槽分区进行亿级数据存储_第25张图片

再次启动6385

docker start redis-node-5 

Docker容器 - 实现Redis Cluster(集群)模式 哈希槽分区进行亿级数据存储_第26张图片

CLUSTER nodes

Docker容器 - 实现Redis Cluster(集群)模式 哈希槽分区进行亿级数据存储_第27张图片

可以再去检查一下

redis-cli --cluster check 192.168.150.30:6381

Docker容器 - 实现Redis Cluster(集群)模式 哈希槽分区进行亿级数据存储_第28张图片

 可以看到主从还原成功 


主从扩容

如果咱们的3主3从也扛不住现在的需求,要再加入两台设备进入集群(6387和6388),而且需要将6388设置成6387的slave要如何操作?还有在加入之后,之前所分好的哈希槽要如何分配?

一、新建6387和6388两个节点

docker run -d --name redis-node-7 --net host --privileged=true -v /data/redis/share/redis-node-7:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6387
docker run -d --name redis-node-8 --net host --privileged=true -v /data/redis/share/redis-node-8:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6388

养成好习惯,记得ps查看一下是否正常

docker ps

Docker容器 - 实现Redis Cluster(集群)模式 哈希槽分区进行亿级数据存储_第29张图片

8个节点全部正常 


二、进入6387容器实例内部

docker exec -it redis-node-7 /bin/bash


三、将新增的6387作为master节点加入集群

这里注意填写自己的真实IP,说白了意思就是6387去找6381,认6381当老大,然后自己加入集群。

redis-cli --cluster add-node 192.168.150.30:6387 192.168.150.30:6381

Docker容器 - 实现Redis Cluster(集群)模式 哈希槽分区进行亿级数据存储_第30张图片

 添加节点成功,但是这里能看到的依旧是3主3从,咱们接着往下


四、查看集群信息--第一次

redis-cli --cluster check 192.168.150.30:6381

Docker容器 - 实现Redis Cluster(集群)模式 哈希槽分区进行亿级数据存储_第31张图片

可以看到这里的6387已经作为master加入到了集群中,但是6387暂时还没有被分配槽位


五、重新分配槽位

reshard意思为重新分配槽位号

redis-cli --cluster reshard 192.168.150.30:6381

Docker容器 - 实现Redis Cluster(集群)模式 哈希槽分区进行亿级数据存储_第32张图片

 这里会询问要分配多少,输入4096即可,为啥是4096呢?因为:

Docker容器 - 实现Redis Cluster(集群)模式 哈希槽分区进行亿级数据存储_第33张图片

16384/4(master数量)均匀分配一下。。所以是4096,接着会询问你放在哪个ID里面,复制6387的ID即可

Docker容器 - 实现Redis Cluster(集群)模式 哈希槽分区进行亿级数据存储_第34张图片

接着输入all然后回车,重新洗牌,分配槽位号

Docker容器 - 实现Redis Cluster(集群)模式 哈希槽分区进行亿级数据存储_第35张图片

 中途需要yes一下,回车即可

Docker容器 - 实现Redis Cluster(集群)模式 哈希槽分区进行亿级数据存储_第36张图片

 槽位号重新分配完毕


六、查看集群信息--第二次

注意真实IP

redis-cli --cluster check 192.168.150.30:6381

Docker容器 - 实现Redis Cluster(集群)模式 哈希槽分区进行亿级数据存储_第37张图片

这里可以看到 

6381的范围是[0-5460],现在变成了[1365-5460]

6382的范围从[5461-10922]变成了[6827-10922]

6383的范围从[10923-16383]变成了[12200-16383]

可以看出来所有的master设备的右边区间范围都没有发生变化。说白了是因为重新分配成本太高,所以前面三台master都分了一部分(1364个)出来给到6387。并不是全部重新均匀分配。


七、为主节点6387分配从节点6388

将6388作为slave挂到6387上面。注意真实IP,后面的ID为6387的ID,从上面复制粘贴即可

redis-cli --cluster add-node 192.168.150.30:6388 192.168.150.30:6387 --cluster-slave --cluster-master-id 08c0499f4e03063cc3ae903f74cdf0c2de089cf3

Docker容器 - 实现Redis Cluster(集群)模式 哈希槽分区进行亿级数据存储_第38张图片

分配成功


八、 查看集群信息--第三次

redis-cli --cluster check 192.168.150.30:6381

Docker容器 - 实现Redis Cluster(集群)模式 哈希槽分区进行亿级数据存储_第39张图片

4主4从配置成功 


主从缩容 

4主4从---->3主3从,将6387和6388删除。删除后的槽位符合分配?先删master6387还是先删slave6388,整体的顺序又是什么?

后面一问的答案是先删除从机slave6388,将清出来的槽位重新分配给前三台机器,最后再删除主机master6387。

一、检查集群情况并获得6388的节点ID

redis-cli --cluster check 192.168.150.30:6381

Docker容器 - 实现Redis Cluster(集群)模式 哈希槽分区进行亿级数据存储_第40张图片

 图中方框内就是6388的节点ID


二、将6388删除

注意真实IP和节点ID 

redis-cli --cluster del-node 192.168.150.30:6388 e8a034707acff6572fa0646514b6d9f773d62efe

这里可以再检查看一下,6388已经消失了,还剩4主3从 

redis-cli --cluster check 192.168.150.30:6381

Docker容器 - 实现Redis Cluster(集群)模式 哈希槽分区进行亿级数据存储_第41张图片


三、将6387的槽位清空 重新分配

这里分配槽位以6381作为落脚点进行重新分配

redis-cli --cluster reshard 192.168.150.30:6381

具体操作如下图

Docker容器 - 实现Redis Cluster(集群)模式 哈希槽分区进行亿级数据存储_第42张图片

 中间yes然后回车


四、检查一下集群状态信息

redis-cli --cluster check 192.168.150.30:6381

Docker容器 - 实现Redis Cluster(集群)模式 哈希槽分区进行亿级数据存储_第43张图片

 槽位分配成功 


五、删除6387

注意真实IP和节点ID

redis-cli --cluster del-node 192.168.150.30:6387 08c0499f4e03063cc3ae903f74cdf0c2de089cf3

再检查一下

redis-cli --cluster check 192.168.150.30:6381

Docker容器 - 实现Redis Cluster(集群)模式 哈希槽分区进行亿级数据存储_第44张图片

6387已经成功被删除,现在成功变回3主3从。

你可能感兴趣的:(Docker,Linux,Redis,redis,docker,哈希算法,云原生,服务器)