1. 海量数据需要缓存时,如何设计存储方案?
1.1. 哈希取余分区
假设有N台机器构成一个集群,用户每次读写操作都是根据公式:hash(key)%N个机器台数,计算出哈希值,用来决定数据映射到哪一个节点上。
优点
简单粗暴,直接有效,只需要预估好数据规划好节点,例如3台、8台、10台,就能保证一段时间的数据支撑。使用Hash算法让固定的一部分请求落到同一台服务器上,这样每台服务器固定处理一部分请求(并维护这些请求的信息),起到负载均衡+分而治之的作用
缺点
原来规划好的节点,进行扩容或者缩容就比较麻烦,不管扩缩,每次数据变动导致节点有变动,映射关系需要重新进行计算,在服务器个数固定不变时没有问题,如果需要弹性扩容或故障停机的情况下,原来的取模公式就会发生变化,Hash(key)%3会变成Hash(key)%?,此时地址经过某个redis机器宕机,由于机器台数发生变化,会导致Hash取余全部数据重新洗牌
1.2. 一致性哈希算法分区
由麻省理工学院于1997年提出用于解决分布式缓存数据变动和映射问题,当服务器个数发生变动时,尽量减少影响客户端到服务器的映射关系。例如某个机器宕机、分母数量改变、自然取余数不行
1.2.1. 算法构建一致性哈希环
一致性哈希算法必然有个hash函数并按照算法产生hash值,这个算法的所有可能哈希值会构成一个全量集,这个集合可以成为一个hash空间[0,2^32-1]。
这个是一个线性空间,但是在算法中,我们通过适当的逻辑控制将它首尾相连(0=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环。
1.2.2. 服务器IP节点映射
将集群中各个IP节点映射到环上的某一个位置,将各个服务器使用Hash进行一个哈希,具体可以选择服务器的IP或主机名作为关键字进行哈希,这样每台机器就能确定其在哈希环上的位置。假如4个节点NodeA、B、C、D,经过IP地址的哈希函数计算(hash(ip)),使用IP地址哈希后在环空间的位置如下:
1.2.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上。
1.2.4. 优点
容错性
假设Node C宕机,可以看到此时对象A、B、D不会受到影响,只有C对象被重定位到Node D。一般的,在一致性Hash算法中,如果一台服务器不可用,则受影响的数据仅仅是此服务器到其环空间中前一台服务器(即沿着逆时针方向行走遇到的第一台服务器)之间数据,其它不会受到影响。简单说,就是C挂了,受到影响的只是B、C之间的数据,并且这些数据会转移到D进行存储。
扩展性
数据量增加了,需要增加一台节点NodeX,X的位置在A和B之间,那收到影响的也就是A到X之间的数据,重新把A到X的数据录入到X上即可,不会导致hash取余全部数据重新洗牌。
1.2.5. 缺点
Hash环数据倾斜问题,一致性Hash算法在服务节点太少时,容易因为节点分布不均匀而造成数据倾斜(被缓存的对象大部分集中缓存在某一台服务器上)问题
1.3. 哈希槽分区
哈希槽实质是一个数组,数组[0,2^14-1]形成hash slot空间
解决均匀分配的问题,在数据和节点之间又加入了一层,把这层称为哈希槽(hash slot),用于管理数据和节点之间的关系,相当于节点上放的是槽,槽里放的是数据。
槽解决的是粒度问题,相当于把粒度变大了,这样便于数据移动。
哈希解决的是映射问题,使用key的哈希值来计算所在的槽,便于数据分配。
一个集群只能有16384个槽,编号0-16383(0-2^14-1)。这些槽会分配给集群中的所有主节点,分配策略没有要求。可以指定哪些编号的槽分配给哪个主节点。集群会记录节点和槽的对应关系。解决了节点和槽的关系后,接下来就需要对key求哈希值,然后对16384取余,余数是几key就落入对应的槽里。slot = CRC16(key) % 16384。以槽为单位移动数据,因为槽的数目是固定的,处理起来比较容易,这样数据移动问题就解决了。
1.3.1. 哈希槽计算
Redis 集群中内置了 16384 个哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点。当需要在 Redis 集群中放置一个 key-value时,redis 先对 key 使用 crc16 算法算出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,也就是映射到某个节点上。如下代码,key之A 、B在Node2, key之C落在Node3上
2. 搭建3主3从redis集群
2.1. redis集群配置
2.1.1. 新建6个容器实例
依次执行以下命令
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
docker run:创建并运行docker容器实例
–name redis-node-1:容器名称
–net host:使用宿主机的IP和端口
–privileged=true:获取宿主机root用户权限
-v /data/redis/share/redis-node-1:/data:容器卷,宿主机地址:docker内部地址
redis:redis镜像(版本号)
–cluster-enabled yes:开启redis集群
–appendonly yes:开启持久化
–port 6381:redis端口号
2.1.2. 构建集群关系
进入docker容器后执行如下命令,需要填写真实IP地址
redis-cli --cluster create 172.23.225.210:6381 172.23.225.210:6382 172.23.225.210:6383 172.23.225.210:6384 172.23.225.210:6385 172.23.225.210:6386 --cluster-replicas 1
–cluster-replicas 1表示为每个master创建一个slave节点
2.1.3. 查看集群状态
进入容器实例内部,使用下面两个命令cluster info和cluster nodes查看集群状态
2.2. 主从容错切换迁移
防止路由失效,需要在命令中加入参数-c
使用命令redis-cli –cluster check 172.23.225.210:6381进行集群检查
停止6381,再次查看集群状态
重新启动6318,再次查看集群状态
对比前后两次集群状态,6381重启后,只能称为从服务器
再先停6385
原master6385宕机,6381再次成为master
再次启动6385
重启后的6385只能称为slave
2.3. 主从扩容
2.3.1. 新建两个节点
新建6387和6388两个节点,命令如下:
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
2.3.2. 新节点加入原集群
进入6387节点,命令如下:
docker exec -it redis-node-7 /bin/bash
将新增的6387作为master节点加入原集群,命令如下:
redis-cli --cluster add-node 172.23.225.210:6387 172.23.225.210:6381
后面节点6387位新增的master节点,6381为原集群领头节点
查看集群节点状态
重新分配槽点,命令为
redis-cli --cluster reshard 172.23.225.210:6381
4096为总槽数16384除以master节点数
再次检查集群节点状态
新加入的6387已经分配了槽位,且是原集群中其他3个master每个节点匀了一部分槽位给新的节点6387
2.3.3. 新主节点分配新从节点
给新加入的master节点6387分配slave节点6388,命令如下
redis-cli --cluster add-node 172.23.225.210:6388 172.23.225.210:6387 --cluster-slave --cluster-master-id f317beb07aace97c7cc95ee21d598f0d7c8e8d3f
2.4. 主从缩容
2.4.1. 删除从节点
将从节点6388从集群中删除,命令如下
redis-cli --cluster del-node 172.23.225.210:6388 8fb69b4f42cb81265c7dc74fe4edb1b95b698827
2.4.2. 清空主节点槽位
重新分配槽位,命令如下
redis-cli --cluster reshard 172.23.225.210:6381
清空槽位
红色为接收槽位的节点,蓝色为清空槽位的节点,这里采用将所有清空的槽位都给一个master
再次查看集群节点状态,6387的槽位已经清空
2.4.3. 删除主节点
删除槽位为空的节点6387,命令如下
redis-cli --cluster del-node 172.23.225.210:6387 f317beb07aace97c7cc95ee21d598f0d7c8e8d3f
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/76777.html