例如整个数据全局是1TB,引入三组Master/Slave来存储,那么每一组Master/Slave存储数据全集的一部分,从而构成一个更大的整体,称为redis集群(Cluster)
在上图中,这三组机器存储的数据都不一样,每个slave都是对应master的备份(当master挂了,就会选举一个slave成为新的master)。每个红框部分称为一个分片,如果全量数据进一步增加,只要再增加更多,即可解决。
借鉴哈希表的基本思想,针对要插入的数据的key(redis都是键值对结构)计算hash值(比如使用MD5计算hash值)。再把这个hash值余上分片个数,就得到一个下标。此时就可以把这个数据放到该下标对应的分片中。即hash(key) % N
md5是一个非常广泛使用的hash算法
- md5计算的结果是定长的,无论输入的原字符串多长,最终算出的结果都是固定长度
- md5计算的结果是分散的,两个原字符串,哪怕大部分都相同,只有一小部分不同,算出来的结果也会差别很大。因此使用md5作为hash函数,可以有效避免hash冲突
- md5计算的结果是不可逆的,给你原字符串,很容易算出md5值;给你md5值,很难还原出原始字符串。因此常使用md5加密
简单高效,数据分配均匀
随着业务增长,数据变多,现有分片不够使用。需要进行“扩容”,需要重新进行hash,计算新的下标。
上图中一共20个数据,只有3个数据不需要搬运,如果是20亿的数据,就需要搬运17亿!!!并且每个分片中不止有主节点还有从节点,需要进行主从同步,开销特别大。
使用hash求余中,当前key属于哪个分片是交替的。像上图中,102属于0号分片,103属于1号分片,104属于2号分片,105又数据0号分片,交替出现,导致搬运成本非常高。
而一致性哈希把交替出现,改进成连续出现。
N个分片把整个圆环分成了N个管辖区间,key的hash值落在某个区间内,就归对应区间管理
从3个分片扩容到4个分片时
此时,只需要将0号分片上的部分数据给搬运到3号分片上即可,1,2号分片管理的区间不变。
这是redis采用的分片算法,可以有效解决搬运成本高和数据分配不均的问题,redis cluster引入哈希槽(hash slots)算法。
:::tips
hash_slot = crc16(key) % 16384
:::
hash_slot 哈希槽
其中crc16也是一种hash算法
16384 = 16 * 1024 即16k
相当于把整个哈希值,映射到16384个槽位上,即[0,16383].
然后再把这些槽位比较均匀的分配给每个分片,每个分片的节点都需要记录自己持有的那些槽位。
例如当前有三个分片,可能的分配方式:
例如新增一个分片,就需要针对原有的槽位进行重新分配
在上述过程中,只有被移动的槽位,对应的数据才需要被搬运。并且分片上的槽位号,不一定是连续的区间。
哈希槽分区算法的实质:结合了哈希算法和一致性哈希的思想,
一共有16384个槽位,如果是16384个分片,意味着一个分片一个槽位,此时很难保证数据再各个分片的均衡性。key要先映射到槽位,再映射到分片中。如果每个分片的包含的槽位比较多,槽位个数相当,就可以认为包含的key的数量也是相当的。如果每个分片包含的槽位非常少,槽位个数不一定能直观反应到key的数目,因为有的槽位,有多个key,有的槽位,可能没有key.并且redis作者建议集群分片数不超过1000
redis作者答案:https://github.com/antirez/redis/issues/2576
节点之间通过心跳包通信,心跳包中包含该节点持有拿下slots,这个是使用位图表示,表示16384(16k)个slots,需要位图大小是2kb。如果给定的slots数更多,比如65536,需要8kb位图表示,8kb对于内存不算什么,但是在频繁的网络心跳包中,是一个不小的开销。
另一方面,redis集群不建议超过1000个分片,所以16k对于最多1000个分片来说是足够用的,同时也会使对应的槽位配置位图体积不至于很大。
基于docker,搭建一个集群,每个节点都是一个容器,拓扑结构如下:
注意:我们一共会创建11个redis节点,其中前9个用来演示集群搭建,后两个用来演示集群扩容
docker-compose.yml
和generate.sh
可以通过
docker ps -a
来查看是否全部关闭
generate.sh
内容如下区别在于每个配置中
cluster-announce-ip
是不同的,其他部分都相同,因为后续会给每个节点分配不同的ip地址
配置说明:
cluster-enabled yes
开启集群cluster-config-file nodes.conf
集群节点生成的配置cluster-node-timeout 5000
节点失联的时间cluster-announce-ip 172.30.0.101
节点自身的ipcluster-announce-port 6379
节点自身的业务端口cluster-announce-bus-port 16379
管理端口,用来给一些管理上的任务通信的。例如主节点挂了,需要让从节点成为主节点,就需要通过刚才管理端口来完成对应的操作version: '3.3'
networks:
mynet:
ipam:
config:
- subnet: 172.30.0.0/24
services:
redis1:
image: 'redis:5.0.9'
container_name: redis1
restart: always
volumes:
- ./redis1/:/etc/redis/
ports:
- 6371:6379
- 16371:16379
command:
redis-server /etc/redis/redis.conf
networks:
mynet:
ipv4_address: 172.30.0.101
redis2:
image: 'redis:5.0.9'
container_name: redis2
restart: always
volumes:
- ./redis2/:/etc/redis/
ports:
- 6372:6379
- 16372:16379
command:
redis-server /etc/redis/redis.conf
networks:
mynet:
ipv4_address: 172.30.0.102
redis3:
image: 'redis:5.0.9'
container_name: redis3
restart: always
volumes:
- ./redis3/:/etc/redis/
ports:
- 6373:6379
- 16373:16379
command:
redis-server /etc/redis/redis.conf
networks:
mynet:
ipv4_address: 172.30.0.103
redis4:
image: 'redis:5.0.9'
container_name: redis4
restart: always
volumes:
- ./redis4/:/etc/redis/
ports:
- 6374:6379
- 16374:16379
command:
redis-server /etc/redis/redis.conf
networks:
mynet:
ipv4_address: 172.30.0.104
redis5:
image: 'redis:5.0.9'
container_name: redis5
restart: always
volumes:
- ./redis5/:/etc/redis/
ports:
- 6375:6379
- 16375:16379
command:
redis-server /etc/redis/redis.conf
networks:
mynet:
ipv4_address: 172.30.0.105
redis6:
image: 'redis:5.0.9'
container_name: redis6
restart: always
volumes:
- ./redis6/:/etc/redis/
ports:
- 6376:6379
- 16376:16379
command:
redis-server /etc/redis/redis.conf
networks:
mynet:
ipv4_address: 172.30.0.106
redis7:
image: 'redis:5.0.9'
container_name: redis7
restart: always
volumes:
- ./redis7/:/etc/redis/
ports:
- 6377:6379
- 16377:16379
command:
redis-server /etc/redis/redis.conf
networks:
mynet:
ipv4_address: 172.30.0.107
redis8:
image: 'redis:5.0.9'
container_name: redis8
restart: always
volumes:
- ./redis8/:/etc/redis/
ports:
- 6378:6379
- 16378:16379
command:
redis-server /etc/redis/redis.conf
networks:
mynet:
ipv4_address: 172.30.0.108
redis9:
image: 'redis:5.0.9'
container_name: redis9
restart: always
volumes:
- ./redis9/:/etc/redis/
ports:
- 6379:6379
- 16379:16379
command:
redis-server /etc/redis/redis.conf
networks:
mynet:
ipv4_address: 172.30.0.109
redis10:
image: 'redis:5.0.9'
container_name: redis10
restart: always
volumes:
- ./redis10/:/etc/redis/
ports:
- 6380:6379
- 16380:16379
command:
redis-server /etc/redis/redis.conf
networks:
mynet:
ipv4_address: 172.30.0.110
redis11:
image: 'redis:5.0.9'
container_name: redis11
restart: always
volumes:
- ./redis11/:/etc/redis/
ports:
- 6381:6379
- 16381:16379
command:
redis-server /etc/redis/redis.conf
networks:
mynet:
ipv4_address: 172.30.0.111
:::tips
docker-compose up -d
:::
此处,把前9个主机构成集群,3主6从。后两个主机用来演示后续扩容
:::tips
redis-cli --cluster create 172.30.0.101:6379 172.30.0.102:6379 172.30.0.103:6379 172.30.0.104:6379 172.30.0.105:6379 172.30.0.106:6379 172.30.0.107:6379 172.30.0.108:6379 172.30.0.109:6379 --cluster-replicas 2172.30.0.105:6379
:::
此时,使用客服端连上集群中的任何一个节点,都相当于连上整个集群。使用命令redis-cli -h 172.30.0.101 -p 6379 -c
-c
选项,因为通过“哈希槽分区算法”计算出对应的哈希槽,如果该哈希槽不在该分片,而是在其他分片就访问不到。加上-c
就会自动把请求重定向到对应的节点cluster nodes
可以查看整个集群的情况在上述的拓扑结构中,redis1,redis2,redis3是主节点,挑选一个停掉
:::tips
docker stop redis1
:::
重新启动redis1
:::tips
docker start redis1
:::
slots
的位图)PFALL
状态(主观下线)PFALL
之后,会通过redis内置的Gossip协议,和其他节点进行沟通,向其他节点确认B的状态(每个节点都会维护一个自己的”下线列表“,由于视角不同,每个节点的下线列表也不一定相同)PFALL
,并且数目超过集群个数的一半,那么A就会把B标记FALL
(客观下线),并把消息同步给其他节点,其他节点收到后,也会把B标记为FALL
slaveof no one
,并且让D执行slaveof C
)哨兵模式,是先投票竞选出一个leader,让leader负责找一个从节点升级为主节点。而集群模式里直接投票选出新的主节点
现在搭建的集群的主机号101-109,9个主机,构成了3主6从结构的集群。现在将主机号为110和111也加入集群中,以110为master,111为slave,数据分片从3->4。
:::tips
redis-cli --cluster add-node 172.30.0.110:6379 172.30.0.101:6379
:::
:::tips
redis-cli --cluster reshard 172.30.0.101:6379
:::
有两种分配方式让你选择
- all,表示从其他每个持有slots的master节点都拿一些过来
- 手动指定,从某一个/几个节点来移动slots,输入完他们的id,以done结尾
当输入yes才是真正开始搬运
搬运key,大部分key是不用搬运的,针对这些未搬运的key,是可以正常访问的.针对这些正在搬运的key,是有可能出现访问出错的情况.
例如客服端访问key1,集群通过分片算法,得到key1是第一个分片的数据,就会重定向到第一个分片的节点,就可能在重定向过去之后,正好key1被搬走,自然就无法访问.
将主机号111添加到集群中,作为主机号110的从节点
:::tips
redis-cli --cluster add-node 172.30.0.111:6379 172.30.0.101:6379 --cluster-slave --cluster-master-id [172.30.1.110节点的nodeid]
:::