背景 1~2亿条数据需要缓存,请问如何设计存储案例?上述问题阿里P6~P7工程案例和场景设计类必考题目,一般业界有三种解决方案^:2、一致性哈希算法分区3、哈希槽分区 1、哈希取余分
背景
1~2亿条数据需要缓存,请问如何设计存储案例? 上述问题阿里P6~P7工程案例和场景设计类必考题目,一般业界有三种解决方案^: 2、一致性哈希算法分区 3、哈希槽分区
1、哈希取余分区
2、一致性哈希算法分区
是什么: 一致性哈希算法在1997年由麻省理工学院中提出的,目的是为了解决分布式缓存数据变动和映射问题,某个单机坏了,分母数量变了,自然取余数就不ok了。 能干什么? 提出一致性哈希算法的解决方案。目的是当服务器个数发生变动的时候,尽量减少影响客户端到服务器的映射关系。 一致性哈希算法的具体步骤如下:(一)、算法构建一致性哈希环
一致性哈希算法必然有个hash函数,并按照算法产生哈希值,这个算法的所有可能哈希值会构成一个全量集,这个集合可以成为一个哈希空间[0,2^32-1],这是一个线性的空间,我们通过适当的逻辑控制将它的首尾相连(0=2^32),这样让它逻辑上形成了一个闭环空间。 哈希取模算法是对节点(服务器)的数量进行取模,而一致性哈希算法是对2^32进行取模,简单来说,一致性哈希算法将整个哈希值空间组成一个虚拟的圆环。 我们可以将2^32想象成一个圆,像钟表一样,钟表的圆可以理解成由60个圆点组成,而一致性哈希环我们可以理解为由2^32个远点组成,成功空间按照顺时针方向组织,圆环的正上方的点代表0,0点右侧的第一个点代表1,以此类推,2、3、4、5、6……直到2^32-1,0点与2^32重合,我们把这个由2^32个点组成的圆环称为hash环
(二)、服务器IP节点映射
(三)、key落到服务器的落键规则
当我们需要存储一个k,v键值对的时候,首先计算key的hash值,根据计算出的哈希值确定此数据在哈希环上的位置,从此位置沿着顺时针行走,遇到的第一台服务器就是其应该定位到的服务器,并将该键值对存储在该节点上。 如下图,有1、2、3,三个请求,按照上面规则,1、3请求顺时针行走遇到的第一个服务是A服务,所以将1、3的请求数据存储在A服务器上,2请求按照顺时针行走遇到的第一个服务是B服务,所以将2的请求数据存储在B服务上。一致性哈希算法的优点
1、容错性 假设,我们上面例子的B服务宕机了,此时2请求进来时,沿着顺时针行走遇到的第一台服务是C服务器,所以2请求被重新定位到C服务器。此时A、B服务环中间的那部分取数会受到影响,而存数据不会受到影响,会定位到C服务。 2、扩展性 数据量增加了,需要增加一台节点X,X的位置在A和D之间,那受到查询影响的就是X和D之间的数据,而D到X之间的存数都落到X服务上即可,不会导致哈希取余所有数据重新洗牌。一致性哈希算法的缺点
hash环的数据倾斜问题 一致性哈希算法在服务节点太少时,容易因节点分布不均匀而造成数据倾斜(被缓存的对象一大部分集中在某一台服务上)问题3、哈希槽算法分区
(一)、为什么会出现
解决一致性哈希算法的数据倾斜问题 哈希槽实际上就是一个数组,数组[0,2^14-1]形成哈希槽空间(hash slot)(二)、能干什么
解决均匀分配问题,在数据和节点之间又加入了一层,把这层成为哈希槽(hash slot) ,用于管理数据和节点之间的关系,现在就相当于节点上放的是槽,槽里放的是数据 槽解决的是粒度的问题,相当于把粒度变大了,这样便于数据移动
(三)、多少个hash槽
(四)、哈希槽的计算
Redis集群中内置了16384个哈希槽,redis会根据节点数量大致均匀的将哈希槽映射到不同的节点。当需要在Redis集群中放置一个key-value时,redis先对key使用crc16算法算出一个结果,然后把结果对16384取余,这样每个key都会对应一个编号在0-16383之间的哈希槽,也就是映射到某个节点上。一、3主3从redis集群配置
docker run:创建并运行docker容器实例 --name redis-node-1:容器实例的名字 -d:后台守护进程运行 –net host:使用宿主机的ip和端口号,默认 --privileged=true:获取宿主机root用户权限,开启容器卷挂载 --cluster-enabled yes: 表示将 Redis 实例设置成集群节点而不是单机服务器 --appendonly yes:开启AOF持久化操作 --port 6386: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 返回以下结果,6个docker容器实例创建成功,此时,六台redis实例之间没有任何关系二、主从容错切换迁移
1、数据读写存储
启动6机构成的集群,并通过exec进入
docker exec -it redis-node-1 /bin/bash redis-cli -p 6381对6381新增2个key
放入一个k1,v1后返回以下错误,数据存不进去,槽位号是12706,不在1服务上: 127.0.0.1:6381> set k1 v1 (error) MOVED 12706 192.168.150.130:6383 我们再来放一个k2,v2,结果如下,可以正常存放: 127.0.0.1:6381> set k2 v2 OK防止路由失效,加参数-c并新增2个key
以上数据无法放进去,我们来加一个参数解决这个问题: 防止路由失效,加参数-c后,并新增2个key -c代表集群环境连接 redis-cli -p 6381 -c 在此命令进入redis之后,发现可以正常插入数据,下图中,Redirected为重定向查看集群信息
redis-cli --cluster check 192.168.150.130:63812、容错切换迁移
(一)、主6381和从机切换,先停止主机6381
docker stop redis-node-1(二)、再次查看集群信息
随便进入一个节点: docker exec -it redis-node-2 bash redis-cli -p 6382 -c 查看集群信息如下: cluster nodes 可以看出6381已经宕机,6381之前对应的从机6385已经上位变成主机 并且查询之前存储的数据都可以查到
(三)、先还原之前的3主3从
先启动6381: docker start redis-node-1 我们启动节点1以后,发现,节点1成为了slaver,5节点还是master
(四)、查看集群状态
3、主从扩容案例(新增一主一从)
新建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 执行完上面两个命令后,只是启动了2个redis容器实例,这两个容器实例和集群没有任何关系进入6387容器实例内部
docker exec -it redis-node-7 /bin/bash新增的6387节点(空槽号)作为master节点加入原集群
将新增的6387节点作为master节点加入集群 redis-cli --cluster add-node 自己实际的IP地址:6387 自己实际的IP地址:6381 redis-cli --cluster add-node 192.168.150.130:6387 192.168.150.130:6381 6387就是将要作为master的新增节点 6381就是原来集群节点里的领路人,相当于6387加入6381领头的组织检查集群情况第一次
redis-cli --cluster check 真实的ip地址:6381 redis-cli --cluster check 192.168.150.130:6381 此时我们的槽位分配情况为: 6381 :0~5460 6382: 5461~10922 6383:10923~16383重新分配槽号
redis-cli --cluster reshard ip地址:端口号 redis-cli --cluster reshard 192.168.150.130:6381
检查集群情况第二次
为主节点6387分配从节点6388
redis-cli --cluster add-node IP:从机端口 IP:主机端口 --cluster-slave --cluster-master-id 主机ID redis-cli --cluster add-node 192.168.150.130:6388 192.168.150.130:6387 --cluster-slave --cluster-master-id 1570427d5a692a5a3dfe45461470fd95aa407725检查集群情况第三次
检查的时候,我们去任意一个master节点都可以,下面我们进去6382 redis-cli --cluster check 192.168.150.130:63824、主从缩容案例
检查集群情况,获得6388的节点id
redis-cli --cluster check 192.168.150.130:6381 可获取6388ID: e6c0fcd4c7b39497efc1efeeeb370257344297d3从集群中将4号从节点6388删除
redis-cli --cluster del-node IP:从机端口 从机6388节点ID redis-cli --cluster del-node 192.168.150.130:6388 e6c0fcd4c7b39497efc1efeeeb370257344297d3将6387的槽位号清空,重新分配,本例将清出来的槽位号都给6381
这里的端口号6381代表四台master机器以6381为落脚点去操作集群, redis-cli --cluster reshard 192.168.150.130:6381检查集群情况第二次
redis-cli --cluster check 192.168.150.130:6381将6387删除
redis-cli --cluster del-node ip:主机4端口 节点ID redis-cli --cluster del-node 192.168.150.130:6387 1570427d5a692a5a3dfe45461470fd95aa407725检查情况第三次
redis-cli --cluster check 192.168.150.130:6381