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 --permanentfirewall-cmd --zone=public --add-port=6382/tcp --permanentfirewall-cmd --zone=public --add-port=6383/tcp --permanentfirewall-cmd --zone=public --add-port=6384/tcp --permanentfirewall-cmd --zone=public --add-port=6385/tcp --permanentfirewall-cmd --zone=public --add-port=6386/tcp --permanentfirewall-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 6381docker 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 6382docker 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 6383docker 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 6384docker 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 6385docker 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 6387docker 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,这种手动操作,能够管制存储的大小,自由度很高,十分中央便。