docker小白第九天
安装redis集群
cluster(集群)模式-docker版本,哈希槽分区进行亿级数据存储。如果1~2亿条数据需要缓存,请问如何设计这个存储案例。单机存储是不可能的,需要分布式存储,如果使用redis又该如何部署。
哈希取余分区
优点:简单粗暴,直接有效,只需要预估好数据,规划好节点,例如3台、8台、10台,就能保证一段时间的数据支撑。使用Hash算法让固定的一部分请求落到同一台服务器上,这样每台服务器固定处理一部分请求(并维护这些请求的信息),起到负载均衡+分而治之的作用。
缺点:原来规划好的节点,进行扩容或者缩容就比较麻烦,不管扩缩,每次数据变动导致节点有变动,映射关系需要重新进行计算,在服务器个数固定不变时没有问题,如果需要弹性扩容或故障停机的情况下,原来的取模公式就会发生变化:Hash(key)/3会变成Hash(key)/?。此时地址经过取余运算的结果将发生很大变化,根据公式获取的服务器也会变得不可控。某个redis机器宕机了,由于机器台数数量变化,会导致hash取余全部数据重新洗牌。
一致性哈希算法分区
一致性哈希算法在1997年由麻省理工学院中提出的,设计目标是为了解决分布式缓存数据变动和映射问题,某个机器宕机了,分母数量改变了,自然取余数失效的问题。
三大步骤:
- 算法构建一致性哈希环
- 服务器lP节点映射
- key落到服务器的落键规则
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,也就是说O点左侧的第一个点代表2^ 32-1,0和2^ 32-1在零点中方向重合,我们把这个由2^32个点组成的圆环称为Hash环。
2、IP节点映射:
将集群中各个IP节点映射到环上的某一个位置。将各个服务器使用Hash进行一个哈希,具体可以选择服务器的IP或主机名作为关键字进行哈希,这样每台机器就能确定其在哈希环上的位置。假如4个节点NodeA、B、C、D,经过IP地址的哈希函数计算(hash(ip)),使用IP地址哈希后在环空间的位置如下:
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上。
优点:假设Node c宕机,可以看到此时对象A、B、D不会受到影响,只有object C对象被重定位到Node D。一般的,在一致性Hash算法中,如果一台服务器不可用,则受影响的数据仅仅是此服务器到其环空间中前一台服务器(即沿着逆时针方向行走遇到的第一台服务器〉之间数据,其它不会受到影响。就是C宕机,受到影响的只是B、C之间的数据,并且这些数据会转移到D进行存储。假设数据量增加了,需要增加一台节点Nodex,X的位置在A和B之间,那收到影响的也就是A到X之间的数据,重新把A到X的数据录入到X上即可,不会导致hash取余全部数据重新洗牌。
缺点:一致性哈希算法的数据倾斜问题,在服务节点太少时,容易因为节点分布不均匀而造成数据倾斜(被缓存的对象大部分集中缓存在某一台服务器上),例如系统中只有两合服务器:
哈希槽分区
哈希槽实质就是一个数组,数组[0,(2^14)-1]形成hash slot空间。解决缓存数据均匀分配的问题,在数据和节点之间又加入了一层,把这层称为哈希槽(slot),用于管理数据和节点之间的关系,现在就相当于节点node上放的是槽,槽里放的是数据。
槽解决的是粒度问题,相当于把粒度变大了,这样便于数据移动(即一堆数据算做一类,以前是一个数据算做一类)。哈希解决的是映射问题,使用key的哈希值来计算所在的槽,便于数据分配。节点数1000以内的redis集群,槽位最多16384个。这些槽会分配给集群中的所有主节点,分配策略没有要求。可以指定哪些编号的槽分配给哪个主节点。集群会记录节点和槽的对应关系。解决了节点和槽的关系后,接下来就需要对key求哈希值,然后对16384取余,余数是几key就落入对应的槽里。slot =CRC16(key) % 16384。以槽为单位移动数据,因为槽的数目是固定的,处理起来比较容易,这样数据移动问题就解决了。
Redis集群中内置了16384个哈希槽、redis会根据节点数量大致均等的将哈希槽映射到不同的节点。当需要在Redis集群中放置一个key-valuel时,redis先对 key使用crc16算法算出一个结果,然后把结果对16384求余数,这样每个key都会对应一个编号在0-16383之间的哈希槽,也就是映射到某个节点上。如下代码,kéy取A、取B时在Node2, key取C时落在Node3上。
3主3从配置redis集群
关闭防火墙+启动docker后台服务
先关闭防火墙,并确认dokcer是启动状态的
systemctl stop firewalld
systemctl start 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
# --cluster-enabled yes 是开启集群配置
# 创建并运行docker容器实例 docker run
# 容器名字--name redis-node-1
# 使用宿主机的IP和端口,默认--net host
# 获取宿主机root用户权限 --privileged=true
# 挂载容器卷,-v 宿主机地址:docker内部地址地
# redis镜像和版本号redis:6.0.8
# 开启持久化 --appendonly yes
# redis端口号--port 6381
依次修改节点的数字和端口的数字,执行六次新建容器命令
docker ps
查看启动后的容器
进入redis节点1的容器命令行配置,注意其中的ip要换为自己的真实ip地址。–cluster-replicas 1表示为每个master创建一个slave节点。
docker exec -it redis-node-1 /bin/bash
redis-cli --cluster create 192.168.0.175:6381 192.168.0.175:6382 192.168.0.175:6383 192.168.0.175:6384 192.168.0.175:6385 192.168.0.175:6386 --cluster-replicas 1
可以看到,自动创建了三个hash slots(哈希槽),分配给了三个主redis节点,另外新建了三个备redis节点,与主节点对应。
在redis节点1的命令行,查看集群状态
redis-cli -p 6381
# 连接到节点1的端口
cluster info
# 查看集群信息
cluster nodes
# 更详细的3主3从集群信息
注意,这里的主从对应的序号,是不固定的,是随机的,有可能1号机与4号机,做主从关系,有可能1号机与5号机,做主从关系。
redis集群读写error,优化路由案例
# 进入第一个redis节点的容器命令行
docker exec -it redis-node-1 /bin/bash
# 客户端进入redis节点1,通过端口6381
redis-cli -p 6381
# 输入命令查看里边是否有数据
keys *
可以看到里边数据为空。
输入以下命令存储数据,发现会报错。是因为经过哈希运算,需要分配给该数据的槽位是12706,超过了redis节点1的最大槽位5460,通过端口6383判断,该数据应该被存至redis节点3。
set k1 v1
可以通过修改连接集群的命令,新增数据。不通过redis-cli -p 6381
这种单机的方式连接,造成数据只访问redis节点1。为防止数据自动跳转路由失效,加参数-c,代表使用的是集群环境,并新增两个key,测试
# 在redis节点1容器的命令行,输入如下命令
redis-cli -p 6381 -c
set k1 v1
set k2 v2
可以看到都存储成功了,并且经过哈希计算后,若待分配的槽位不在节点1时,自动redicrt重定向数据到对应的节点。
# 在节点1容器的命令行界面,查看集群信息
redis-cli --cluster check 192.168.0.175:6381
可以看到刚刚存储的两个键值对,分别在哪。还有其他一些集群信息。