共计 6260 个字符,预计需要花费 16 分钟才能阅读完成。
1. 如何设计亿级数据的缓存
2. 哈希取余分区
3. 一致性哈希算法分区
4. 哈希槽分区
5.Redis 集群哈希槽分区配置
6. 哈希槽分区主从容错迁徙
7. 哈希槽分区主从扩容
8. 哈希槽分区主从缩容
1. 如何设计亿级数据的缓存
假如咱们当初要设计一个存储案例,这个 存储要能包容 1~2 亿条数据,请问如何设计这个存储案例?
咱们晓得 单台的 redis 可能无奈存储这么多的数据量 ,这个时候咱们就要应用redis 集群进行分区存储,然而 redis 集群对于这么多的数据,个别有以下 三种算法 进行数据的保留:
1.1)哈希取余分区
1.2)一致性哈希算法分区
1.3)哈希槽分区
2. 哈希取余分区
哈希取余分区存储是 比较简单的一种操作,简直看图就能够明确:
原理:
假如咱们要存储 两亿的数据 ,咱们当初有 三台 reids 形成一个集群 ,咱们在 存储 / 写入 一个 key 的时候,用户每次读写操作都是依据公式:hash(key)%N 个机器节点数 ,计算取出哈希值, 用来决定数据映射在哪一个节点上。
长处:
实现简略,只须要预估好 redis 节点个数,就能保障一段时间的数据撑持。应用 hash 算法和取余能让数据落到对应的服务器上,这样每台服务器都能解决一部分申请,达到了负载平衡 + 分而治之的作用。
毛病:
因为一开始进行设计的时候,对 redis 的节点就进行了确定,进行扩缩容会比拟麻烦 , 不论是扩容还是缩容,每次数据节点的变动,都会导致所有的数据须要从新计算映射,在服务器节点永远不发生变化的时候没有问题,如果要弹性扩缩容或故障停机的状况,取模公式就会发生变化。某个 redis 机器宕机了,因为台数数量变动,会导致 hash 取余全副数据从新洗牌。
3. 一致性哈希算法分区
一致性哈希算法在 1997 年由麻省理工学院提出,设计目标是为了 解决分布式缓存因为节点台数发生变化的时候,尽量减少影响客户端的数据到服务端的映射变动。
原理:
先要构建一致性哈希环:
它也是 应用取模的办法 ,后面的 redis 集群哈希取余分区的取模办法是对节点(服务器)的数量进行取模。而一致性 Hash 算法是 对 2^32 取模 ,将整个 哈希值空间组织成一个虚构的圆环 ,这个汇合能够成为一个 hash 空间[0,2^32-1],这是一个线性空间,在算法中,咱们会通过适当的逻辑将它首尾相连(0 = 2^32), 这样就让它在 逻辑上造成了一个环形空间,咱们把这个由 2^32 个点组成的圆环称为 Hash 环。
而后是节点映射:
将集群中的节点映射到环上的某一个地位。
将 各个服务器应用 Hash 进行一个哈希 ,具体能够抉择服务器的 IP 或主机名作为关键字进行哈希,这样 每台机器就能确定本人在哈希环上的地位。如果 4 个节点 NodeAB、C、D,通过 IP 地址的哈希计算,应用 IP 地址哈希后在环空间的地位如下:
key 到服务器的落键规定:
当咱们要存储一个 kv 键值对的时候,首先要 计算 key 的 hash 值 ,通过这个 哈希值计算出这个 key 在环上的地位 ,从此地位沿着 顺时针“行走”,第一台遇到的服务器就是其要保留 key 的服务器 , 并将该 key 保留在该节点上。
如咱们有 Object A、Object B、Object C、Object D 四个数据对象,通过哈希计算后,在环空间上的地位如下:依据一致性 Hash 算法,数据 A 会被定为到 Node A 上,B 被定为到 Node B 上,C 被定为到 Node C 上,D 被定为到 Node D 上。
长处:
一致性哈希算法的 容错性佳 :
假如当初的 Node C 宕机,咱们能够看到 ABD 原有的数据不受影响,只有 C 对象被从新定位到 Node D。个别在一致性 hash 算法中,某一台服务器不可用,受到影响的仅仅是 hash 环中的 宕机这个节点与前一台服务器之间的数据 ,其它并不会受到影响,假如 C 挂了, 受影响的是 B、C 之间的数据,并且这些数据会挪动到 D。
一致性哈希算法的 扩展性佳 :
假如数据量减少了,须要减少一个节点,这个节点减少在 A 和 B 之间,那受到影响的只是 A 到 X 之间的数据,从新把 A 到 X 的数据写到 X 上即可,不须要全副从新计算。
毛病:
一致性哈希算法有 数据歪斜问题:
一致性哈希算法在 服务器节点太少的时候 ,容 易因为节点的调配不平均而导致数据的歪斜 , 被缓存的 key 大部分都集中在一台服务器上:
4. 哈希槽分区
通过后面的介绍,咱们晓得一致性 hash 算法有数据歪斜的问题,那么为了解决这种问题,咱们有了hash 槽分区算法。
原理:
哈希槽本质就是一个数组,数组 [0,2^14 -1] 造成 hash slot 空间。
它能 解决平均调配的问题 ,在 数据和节点中又退出了一层 ,咱们把这层称为哈希槽(slot),用于治理数据和节点之间的关系,当初就相当于 节点上放的是槽,槽里放的是数据。
解决的问题:
槽解决的是 粒度问题 (能够挪动数据),相当于把粒度变大了,这样便于数据挪动。
哈希解决的是 映射问题(也就是能够控制数据歪斜问题),应用 key 的哈希值来计算所在的槽,便于数据调配和调配。
redis 一共有多少个 hash 槽?
一个集群只能有 16382 个槽(起因会在上面解释),这些 槽会调配给集群中所有的主节点 ,调配策略没有要求,能够指 定哪些编号的槽调配给哪些节点 ,集群会记录节点和槽的对应关系。
解决了节点和槽的关系当前,接下来就须要对 key 进行哈希求值,而后对 16384 取余,余数是几,key 就落入对应的槽位。slot = CRC16(key) % 16384。以槽为单位挪动数据,因为槽的数目是固定的,解决起来比拟容易,这样数据挪动问题就解决了。
为什么 redis 只能有 16384 个槽?
实践上 CRC16 算法能够失去 2^16 个数值,其数值范畴在 0 -65535 之间,也就是最多能够有 65535 个虚构槽,取模运算 key 的时候,应该是 CRC(key)%65535; 然而却设计为 crc16(key)%16384,起因 是作者在设计的时候做了空间上的衡量 ,感觉节点最多不可能超过 1000 个, 节点数量越多,节点间通信的老本越大 (节 点间通信的音讯体内容越大,具体是音讯头中携带的其余节点信息越大 ),为了 保障节点之间通信效率,衡量之下所以采纳了 2^14 个哈希槽。
哈希槽的计算形式:
上述提过,Redis 集群中内置了 16384 个哈希槽,redis 会依据节点数量,将大抵平衡的哈希槽调配到对应的节点上。当须要在 Redis 集群中搁置一个 key-value 时,redis 先对 key 应用 crc16 算法算出一个后果,而后把后果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,也就是映射到某个节点上。如下代码,key 之 A、B 在 Node2,key 之 C 落在 Node3 上
5.redis 集群哈希槽分区配置
在启动 redis 集群配置之前,咱们先要把所有防火墙的端口关上,以便咱们的操作系统连贯:
firewall-cmd --zone=public --add-port=6381/tcp --permanent
firewall-cmd --zone=public --add-port=6382/tcp --permanent
firewall-cmd --zone=public --add-port=6383/tcp --permanent
firewall-cmd --zone=public --add-port=6384/tcp --permanent
firewall-cmd --zone=public --add-port=6385/tcp --permanent
firewall-cmd --zone=public --add-port=6386/tcp --permanent
firewall-cmd --reload
而后咱们启动 6 个 redis:
docker run -d --name redis-node-1 --net host --privileged=true -v /data/redis/share/redis-node-1:/data redis --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 --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 --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 --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 --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 --cluster-enabled yes --appendonly yes --port 6386
接下来咱们进入 容器 redis-node-1,为 6 台机器构建集群关系:
先查一下本人的 ip 地址:
进入容器
docker exec -it redis-node-1 /bin/bash
构建主从关系:
redis-cli --cluster create 192.168.64.129:6381 192.168.64.129:6382 192.168.64.129:6383 192.168.64.129:6384 192.168.64.129:6385 192.168.64.129:6386 --cluster-replicas 1
配置胜利!
咱们进入 6381 看一下节点的状态:
咱们往 6381 里写入数据看看:
咱们会发现报错,那时因为咱们没有开启集群模式读写,要加上参数 -c
-c Enable cluster mode (follow -ASK and -MOVED redirections).
redis-cli -p 6381 -c
此时咱们看出,设置两个值后,会主动帮咱们计算并定位。
查看一下集群的状态:
redis-cli --cluster check 192.168.64.129:6381
6. 哈希槽分区主从容错迁徙
咱们先把刚刚 的 6381 停掉,看看实在主机的上位状况:
能够看出,6381 进行后,6384 成为了新的主节点。
咱们启动原来的 6381 节点,看看 6381 节点的状态,是 master,还是 slave:
能够看出,再启动之后,之前的节点点就变成了 slave。
7. 哈希槽分区主从扩容
之前在讲哈希槽分区的实践的时候,没有说到哈希槽分区的主从扩缩容是什么样的,咱们用实战来进行了解一下:
咱们新建两个节点,退出集群之中,看看是什么成果:
创立两个节点:
docker run -d --name redis-node-7 --net host --privileged=true -v /data/redis/share/redis-node-7:/data redis --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 --cluster-enabled yes --appendonly yes --port 6388
进入容器 6387 外部:
docker exec -it redis-node-7 /bin/bash
将新增的 6387 节点作为 master 退出原集群:
redis-cli --cluster add-node 192.168.64.129:6387 192.168.64.129:6381
查看集群状态第 1 次:
redis-cli --cluster check 192.168.64.129:6381
咱们发现,当初主节点有了四个,然而槽号还是没有调配,咱们要给新加进来的主节点调配槽号。
重新分配槽号:
redis-cli --cluster reshard 192.168.64.129:6381
因为 16384/4=4096,所以咱们调配 4096 个槽号给新加进来的节点:
查看集群状态第 2 次:
redis-cli --cluster check 192.168.64.129:6381
咱们能够看出,咱们从三个主节点那里,各拿了一些槽号分给新的主节点,这就防止了整体从新 hash。
为主节点 6387 调配从节点 6388:
命令:redis-cli –cluster add-node ip: 新 slave 端口 ip: 新 master 端口 –cluster-slave –cluster-master-id 新主机节点 ID
redis-cli --cluster add-node 192.168.64.129:6388 192.168.64.129:6387 --cluster-slave 00cluster-master-id 62539568294324e2c5aa35146de9b5a45c65d440
查看集群状态第 3 次:
redis-cli --cluster check 192.168.64.129:6381
胜利实现四主四从的扩容!
能够看出,在扩容的时候,并没有整体进行重新分配,而是从之前的 redis 节点中,各拿了一些槽号到这个主节点 ,实现了扩容, 能够看出这种形式,对影响的数据还是比拟少的。
8. 哈希槽分区主从缩容
查看集群情况第 1 次,查看 6388 的节点 id:
redis-cli --cluster check 192.168.64.129:6381
将 6388 删除:
命令:redis-cli –cluster del-node ip: 从机端口 从机 6388 节点 ID
redis-cli --cluster del-node 192.168.64.129:6388 7066a848eaf5a46563da76fa669b169a07adc482
将 6387 的槽号清空,交给 6384 来接管:
查看集群情况第 2 次:
redis-cli --cluster check 192.168.64.129:6381
槽位已交出:
将 6387 删除:
redis-cli --cluster del-node 192.168.64.129:6387 62539568294324e2c5aa35146de9b5a45c65d440
查看集群状况第 3 次:
redis-cli --cluster check 192.168.64.129:6381
胜利复原三主三从!
咱们能够看出哈希槽分区在扩缩容的时候,须要手动进行操作。要把被删除的那个节点的槽号交接给其中一个节点,也是只须要挪动一部分数据,不必从新 hash,这种手动操作,能够管制存储的大小,自由度很高,十分中央便。