一、cluster(集群)模式-docker 版
哈希槽分区进行亿级数据存储
面试题:1~2 亿条数据需要缓存,请问如何设计这个存储案例?
答:单机单台 100%不可能,肯定是分布式存储,用 redis 如何落地?
上述问题阿里 P6~P7 工程案例和场景设计类必考题目,一般业界有 3 种解决方案
1、哈希取余分区
![](https://files.mdnice.com/user/27110/5927763d-619b-40dd-a4f7-c3c263528fe7.png)
2 亿条记录就是 2 亿个 k,v,我们单机不行必须要分布式多机,假设有 3 台机器构成一个集群,用户每次读写操作都是根据公式: hash(key) % N
个机器台数,计算出哈希值,用来决定数据映射到哪一个节点上。
优点:
简单粗暴,直接有效,只需要预估好数据规划好节点,例如 3 台、8 台、10 台,就能保证一段时间的数据支撑。使用 Hash 算法让固定的一部分请求落到同一台服务器上,这样每台服务器固定处理一部分请求(并维护这些请求的信息),起到负载均衡+分而治之的作用。
缺点: 原来规划好的节点,进行扩容或者缩容就比较麻烦了额,不管扩缩,每次数据变动导致节点有变动,映射关系需要重新进行计算,在服务器个数固定不变时没有问题,如果需要弹性扩容或故障停机的情况下,原来的取模公式就会发生变化:Hash(key)/3
会变成Hash(key) /?
。此时地址经过取余运算的结果将发生很大变化,根据公式获取的服务器也会变得不可控。
某个 redis 机器宕机了,由于台数数量变化,会导致 hash 取余全部数据重新洗牌。
2、一致性哈希算法分区
2.1 是什么?
一致性 Hash 算法背景
一致性哈希算法在 1997 年由麻省理工学院中提出的,设计目标是为了解决分布式缓存数据变动和映射问题,某个机器宕机了,分母数量改变了,自然取余数不 OK 了。
2.2 能干嘛
提出一致性 Hash 解决方案。目的是当服务器个数发生变动时, 尽量减少影响客户端到服务器的映射关系。
2.3 3 大步骤
(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 环。
![](https://files.mdnice.com/user/27110/b7c825b5-e5ee-42e2-8bbe-2b5d48056cb9.png)
(2)服务器 IP 节点映射
节点映射:将集群中各个 IP 节点映射到环上的某一个位置。
将各个服务器使用 Hash 进行一个哈希,具体可以选择服务器的 IP 或主机名作为关键字进行哈希,这样每台机器就能确定其在哈希环上的位置。假如 4 个节点 NodeA、B、C、D,经过 IP 地址的哈希函数计算(hash(ip)),使用 IP 地址哈希后在环空间的位置如下:
![](https://files.mdnice.com/user/27110/92debd05-2e10-4722-a64b-45f3131b7743.png)
(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 上。
![](https://files.mdnice.com/user/27110/0d7edc8a-2f57-4118-9afe-fd477960e642.png)
2.4 优点
一致性哈希算法的容错性
容错性:
假设 Node C 宕机,可以看到此时对象 A、B、D 不会受到影响,只有 C 对象被重定位到 Node D。一般的,在一致性 Hash 算法中,如果一台服务器不可用,则受影响的数据仅仅是此服务器到其环空间中前一台服务器(即沿着逆时针方向行走遇到的第一台服务器)之间数据,其它不会受到影响。简单说,就是 C 挂了,受到影响的只是 B、C 之间的数据,并且这些数据会转移到 D 进行存储。
![](https://files.mdnice.com/user/27110/e4aac118-835d-4eb6-8b88-a292c03e250c.png)
一致性哈希算法的扩展性
扩展性:
数据量增加了,需要增加一台节点 NodeX,X 的位置在 A 和 B 之间,那收到影响的也就是 A 到 X 之间的数据,重新把 A 到 X 的数据录入到 X 上即可,不会导致 hash 取余全部数据重新洗牌。
![](https://files.mdnice.com/user/27110/125318fe-57cb-4a69-8a07-0e6192be2f57.png)
2.5 缺点
一致性哈希算法的数据倾斜问题
Hash 环的数据倾斜问题:
一致性 Hash 算法在服务节点太少时,容易因为节点分布不均匀而造成数据倾斜(被缓存的对象大部分集中缓存在某一台服务器上)问题,例如系统中只有两台服务器
![](https://files.mdnice.com/user/27110/b3ec1298-062c-472c-b57f-3e37b9d431b9.png)
2.6 小总结
为了在节点数目发生改变时尽可能少的迁移数据
将所有的存储节点排列在收尾相接的 Hash 环上,每个 key 在计算 Hash 后会顺时针找到临近的存储节点存放。而当有节点加入或退出时仅影响该节点在 Hash 环上顺时针相邻的后续节点。
优点: 加入和删除节点只影响哈希环中顺时针方向的相邻的节点,对其他节点无影响。
缺点 : 数据的分布和节点的位置有关,因为这些节点不是均匀的分布在哈希环上的,所以数据在进行存储时达不到均匀分布的效果。
3、哈希槽分区
3.1 是什么?
(1)为什么出现
为了解决一致性哈希算法的数据倾斜问题
哈希槽实质就是一个数组,数组[0,2^14 -1]
形成 hash slot 空间。
(2)能干什么
解决均匀分配的问题,在数据和节点之间又加入了一层,把这层称为哈希槽(slot),用于管理数据和节点之间的关系,现在就相当于节点上放的是槽,槽里放的是数据。
![](https://files.mdnice.com/user/27110/ef1cfd80-b578-4516-ac07-31f2f9467fa3.png)
槽解决的是粒度问题,相当于把粒度变大了,这样便于数据移动。 哈希解决的是映射问题,使用 key 的哈希值来计算所在的槽,便于数据分配。
(3)多少个 hash 槽
一个集群只能有 16384 个槽,编号 0-16383(0-2^14-1)。这些槽会分配给集群中的所有主节点,分配策略没有要求。可以指定哪些编号的槽分配给哪个主节点。集群会记录节点和槽的对应关系。解决了节点和槽的关系后,接下来就需要对 key 求哈希值,然后对 16384 取余,余数是几 key 就落入对应的槽里。slot = CRC16(key) % 16384。以槽为单位移动数据,因为槽的数目是固定的,处理起来比较容易,这样数据移动问题就解决了。
3.2 哈希槽计算
Redis 集群中内置了 16384 个哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点。当需要在 Redis 集群中放置一个 key-value 时,redis 先对 key 使用 crc16 算法算出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,也就是映射到某个节点上。如下代码,key 之 A 、B 在 Node2, key 之 C 落在 Node3 上
![](https://files.mdnice.com/user/27110/997919d7-737a-419f-8227-0c4633a866f3.png)
![](https://files.mdnice.com/user/27110/527b6331-ccae-4f8b-8df2-6e59fa3c9fdf.png)
二、Redis 集群
3 主 3 从 redis 集群扩缩容配置案例架构说明
![](https://files.mdnice.com/user/27110/99ad652d-158d-4350-87a4-bfa832bd36b6.png)
三、Redis-Cluster(集群)模式-Docker 版
1、3 主 3 从 redis 集群配置
1.1 关闭防火墙+启动 docker 后台服务
systemctl start docker
1.2 新建 6 个 docker 容器 redis 实例
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
如果运行成功,效果如下:
![](https://files.mdnice.com/user/27110/5d8230ed-df42-4829-ba0e-1d55f3111521.png)
命令分步解释:
docker run 创建并运行docker容器实例
--name redis-node-6 容器名字
--net host 使用宿主机的IP和端口,默认
--privileged=true 获取宿主机root用户权限
/data/redis/share/redis-node-6:/data 容器卷,宿主机地址:docker内部地址
redis:6.0.8 redis镜像和版本号
--cluster-enabled yes 开启redis集群
--appendonly yes 开启持久化
--port 6386 redis端口号
1.3 进入容器 redis-node-1 并为 6 台机器构建集群关系
(1)进入容器
docker exec -it redis-node-1 /bin/bash
(2)构建主从关系
注意,进入 docker 容器后才能执行一下命令,且注意自己的真实 IP 地址
redis-cli --cluster create 192.168.111.147:6381 192.168.111.147:6382 192.168.111.147:6383 192.168.111.147:6384 192.168.111.147:6385 192.168.111.147:6386 --cluster-replicas 1
--cluster-replicas 1
表示为每个 master 创建一个 slave 节点
图示:
![](https://files.mdnice.com/user/27110/019d167c-c9ce-468a-875b-f7a1d9815a13.png)
![](https://files.mdnice.com/user/27110/3426d9f3-1cbb-459f-ab76-a7fe3136a61c.png)
![](https://files.mdnice.com/user/27110/d06d9e41-1666-4cd0-8a64-91a3a3f3d92b.png)
![](https://files.mdnice.com/user/27110/ff253053-d636-4470-a022-5721a0706cf9.png)
1.4 链接进入 6381 作为切入点,查看集群状态
(1) 链接进入 6381 作为切入点,查看节点状态
(2) cluster info 命令
![](https://files.mdnice.com/user/27110/dd1e8bb8-2406-4131-977c-683638fb9d68.png)
(3) cluster nodes 命令
![](https://files.mdnice.com/user/27110/d2359da4-f196-4eb0-bae4-024452247ccd.png)
2、主从容错切换迁移案例
2.1 数据读写存储
启动由六台机器构成的集群并通过 exec 进入,对 6381 新增两个 key
![](https://files.mdnice.com/user/27110/bcbd856f-a3b8-4ab7-8b26-7aa8835d27eb.png)
发现报错 error 了,没有使用集群操作。
使用集群方式进行操作: 防止路由失效加参数-c 并新增两个 key
![](https://files.mdnice.com/user/27110/e9cf2ba1-1f10-4e15-aaf3-7719f3792b88.png)
使用 --cluster check
查看集群信息
redis-cli --cluster check 192.168.111.147:6381
![](https://files.mdnice.com/user/27110/eebe23d7-2d1d-458a-bbd6-12e15b0ac5af.png)
![](https://files.mdnice.com/user/27110/f7dc7fae-a7cc-4e2c-ba98-354bf76cbe82.png)
2.2 容错切换迁移
(1) 主 6381 和从机切换,先停止主机 6381
- 6381 主机停了,对应的真实从机上位
- 6381 作为 1 号主机分配的从机以实际情况为准,具体是几号机器就是几号
(2) 再次查看集群信息
![](https://files.mdnice.com/user/27110/89d99f81-194a-42e0-bbbe-59470602f3e9.png)
6381 宕机了,6385 上位成为了新的 master。
每次案例下面挂的从机以实际情况为准,具体是几号机器就是几号.
(3) 先还原之前的 3 主 3 从
先启 6381
docker start redis-node-1
![](https://files.mdnice.com/user/27110/9104f9cd-67a8-4c72-92cd-91b523bed83f.png)
再停 6385
docker stop redis-node-5
![](https://files.mdnice.com/user/27110/c9184351-9582-43cc-822a-0b3760729621.png)
再启 6385
docker start redis-node-5
![](https://files.mdnice.com/user/27110/cc6c21a2-6f58-43bf-a5c1-b470ebc2415e.png)
主从机器分配情况以实际情况为准,中间需要等待一会儿,docker 集群重新响应。
(4) 查看集群状态
redis-cli --cluster check 自己IP:6381
![](https://files.mdnice.com/user/27110/3d9b1f59-74ae-48a7-81fa-462bba808d26.png)
3、主从扩容案例
3.1 新建 6387、6388 两个节点+新建后启动+查看是否 8 节点
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
查看 Docker 进程
docker ps
3.2 进入 6387 容器实例内部
docker exec -it redis-node-7 /bin/bash
3.3 将新增的 6387 节点(空槽号)作为 master 节点加入原集群
将新增的6387作为master节点加入集群
redis-cli --cluster add-node 自己实际IP地址:6387 自己实际IP地址:6381
6387 就是将要作为master新增节点
6381 就是原来集群节点里面的领路人,相当于6387拜拜6381的码头从而找到组织加入集群
![](https://files.mdnice.com/user/27110/9036ab99-fe2f-4d28-b754-5e2ff3715c12.png)
![](https://files.mdnice.com/user/27110/b61f26d3-3e09-4506-a248-f04794b972d1.png)
3.4 检查集群情况第 1 次
redis-cli --cluster check 真实ip地址:6381
![](https://files.mdnice.com/user/27110/723246b6-a3ed-495c-b6b3-a50ddd3e5992.png)
![](https://files.mdnice.com/user/27110/e6bb88d9-c51d-4801-80d0-ad9fbf9d830f.png)
3.5 重新分派槽号
重新分派槽号
命令: redis-cli --cluster reshard IP地址:端口号
redis-cli --cluster reshard 192.168.111.147:6381
![](https://files.mdnice.com/user/27110/45fb8327-6daf-48b1-93eb-2709c078d41d.png)
![](https://files.mdnice.com/user/27110/e34cb354-922d-4e7b-a5e2-e892f11ae4d1.png)
3.6 检查集群情况第 2 次
redis-cli --cluster check 真实ip地址:6381
![](https://files.mdnice.com/user/27110/2e7b4341-8c0d-4373-a8a0-93c71be19555.png)
![](https://files.mdnice.com/user/27110/1b63c7e5-d654-4c24-b118-c825325df85c.png)
槽号分派说明:
为什么 6387 是 3 个新的区间,以前的还是连续?
重新分配成本太高,所以前 3 家各自匀出来一部分,从 6381/6382/6383 三个旧节点分别匀出 1364 个坑位给新节点 6387
![](https://files.mdnice.com/user/27110/8f9fafec-825f-41f6-89f0-eab86a830ebf.png)
3.7 为主节点 6387 分配从节点 6388
命令:redis-cli --cluster add-node ip:新slave端口 ip:新master端口 --cluster-slave --cluster-master-id 新主机节点ID
redis-cli --cluster add-node 192.168.111.147:6388 192.168.111.147:6387 --cluster-slave --cluster-master-id e4781f644d4a4e4d4b4d107157b9ba8144631451-------这个是6387的编号,按照自己实际情况
![](https://files.mdnice.com/user/27110/58d22d9a-816e-45d1-939e-85aff7cce28b.png)
![](https://files.mdnice.com/user/27110/bc9e09cc-1182-48e2-a2f5-05054a5aeeba.png)
3.8 检查集群情况第 3 次
redis-cli --cluster check 192.168.111.147:6382
![](https://files.mdnice.com/user/27110/a38d54ac-7387-436b-858b-9b08affedf3d.png)
![](https://files.mdnice.com/user/27110/11fc7578-9616-4444-99c3-897c4db3a7cf.png)
4、主从缩容案例
4.1 目的:6387 和 6388 下线
4.2 检查集群情况 1 获得 6388 的节点 ID
redis-cli --cluster check 192.168.111.147:6381
![](https://files.mdnice.com/user/27110/c2ab6e5c-24d5-410e-b962-aaa3bdfe2aaf.png)
4.3 将 6388 删除
从集群中将 4 号从节点 6388 删除
命令:redis-cli --cluster del-node ip:从机端口 从机6388节点ID
redis-cli --cluster del-node 192.168.111.147:6388 5d149074b7e57b802287d1797a874ed7a1a284a8
![](https://files.mdnice.com/user/27110/f7a6ab4d-4af6-49fc-9345-e284bad6653d.png)
检查集群情况:
redis-cli --cluster check 192.168.111.147:6382
检查一下发现,6388 被删除了,只剩下 7 台机器了
![](https://files.mdnice.com/user/27110/f2789334-e88f-4534-9b00-61148a3f7d3f.png)
4.4 重新分配槽号
将 6387 的槽号清空,重新分配,本例将清出来的槽号都给 6381
redis-cli --cluster reshard 192.168.111.147:6381
![](https://files.mdnice.com/user/27110/530ecf47-fc89-4c45-a788-8d10980cc668.png)
![](https://files.mdnice.com/user/27110/8f153aea-6058-4c7a-aabc-c7437392b1f2.png)
![](https://files.mdnice.com/user/27110/2a43c49f-42da-4d96-a512-bd15e5cf39e1.png)
4.5 检查集群情况第二次
redis-cli --cluster check 192.168.111.147:6381
4096个槽位都指给6381,它变成了8192个槽位,相当于全部都给6381了,也可以输入3次,分配给其他三个主机
![](https://files.mdnice.com/user/27110/1e073128-8495-4412-9ffd-e29198a74924.png)
4.6 将 6387 删除
命令:redis-cli --cluster del-node ip:端口 6387节点ID
redis-cli --cluster del-node 192.168.111.147:6387 e4781f644d4a4e4d4b4d107157b9ba8144631451
![](https://files.mdnice.com/user/27110/a750dce4-6c85-43cb-8887-855d8e9ed5f3.png)
4.6 检查集群情况第三次
检查集群情况第三次
redis-cli --cluster check 192.168.111.147:6381
![](https://files.mdnice.com/user/27110/be6f5657-8086-4c05-b71c-6058b90a78b5.png)