目录
基本概念
数据分片算法
哈希求余
编辑一致性哈希算法
哈希槽分区算法
搭建集群环境
创建目录和配置
编写 docker-compose.yml
启动容器
构建集群
基本概念
广义的集群,只要是多个机器构成了分布式系统,都可以成为是一个“集群”。
但是redis提供的集群模式是狭义的集群,这个集群模式之下,主要是解决,存储空间不足的问题(拓展存储空间)。
哨兵模式提高了系统的可用性。哨兵模式中本质上还是redis主从节点存储数据。其中就要求一个主节点/从节点,就得存储整个数据的“全集”。
此处关键问题,就是引入多台机器,每台机器存储一部分数据。每个存储数据的机器还需要搭配若干个从节点。
每个 Slave 都是对应 Master 的备份(当 Master 挂了, 对应的 Slave 会补位成 Master).
每个红框部分都可以称为是⼀个 分片(Sharding).
把数据分成多份,具体该如何去分呢?
数据分片算法
有三中主流的分片方式。
哈希求余
借鉴了哈希表的基本思想。借助hash函数,把一个key映射到一个数组下标上。再针对数组的长度求余,就可以得到一个数组下标。
比如有三个分片,编号0,1,2。此时就可以针对要插入的数据的key(redis都是键值对结构的数据)计算hash值。(比如md5)再把这个hash值余上分片个数,就得到了一个下标。此时就可以把这个数据放到该下标对应的分片中了。
后续查询key的时候,也是同样的算法。
拓展:MD5是一个非常广泛使用的hash算法。
特点:1.md5计算结果是定长的。无论输入的原字符串多长,最终算出的结果就是固定长度。
2.md5计算结果是分散的。两个原字符串,哪怕大部分都相同,只有一个小的地方不同,算出来的md5值也会差别很大。
3.md5计算结果是不可逆的。给你原字符串,可以很容易算出md5的值。给你md5值,很难还原出原始的字符串的。
一旦服务器集群需要扩容,就需要更高的成本。分片主要目的就是为了能提高存储能力。分片越多,能存的数据越多,成本也更高。随着业务的增长,数据变多了,需要更多的分片。
当引入更多的分片的时候,N(分片数量)就改变了。如果发现某个数据,在扩容之后,不应该在当前的分片中了,就需要重新进行分配(搬运数据)。绝大部分数据都需要进行搬运,成本过高。
一致性哈希算法
1)把 0 -> 2^32-1 这个数据空间, 映射到⼀个圆环上. 数据按照顺时针⽅向增⻓。
2)假设当前存在三个分⽚, 就把分⽚放到圆环的某个位置上。
3) 假定有⼀个 key, 计算得到 hash 值 H, 那么这个 key 映射到哪个分⽚呢? 规则很简单, 就是从 H
所在位置,顺时针往下找, 找到的第⼀个分⽚, 即为该 key 所从属的分⽚。
这就相当于, N 个分⽚的位置, 把整个圆环分成了 N 个管辖区间。Key 的 hash 值落在某个区间内, 就归对应区间管理。
此时,如果增加分片,会怎样呢?
原有分⽚在环上的位置不动, 只要在环上新安排⼀个分片位置即可。
此时, 只需要把 0 号分片上的部分数据,搬运给 3 号分片即可。1 号分片和 2 号分片管理的区间都是不变的。
优点: 大大降低了扩容时数据搬运的规模, 提⾼了扩容操作的效率。
缺点: 数据分配不均匀 (有的多有的少, 数据倾斜)。
哈希槽分区算法
这是redis真正采用的分片算法。采用以下公式:
hash_slot = crc16(key) % 16384
crc16也是一种计算hash的算法,这里不多展开。
16384 = 16* 1024
得到结果hash_slot,称为哈希槽。
会把这些哈希槽分配到不同的分片上。
此时,这三个分片上的数据就是比较均匀的了。会先根据key算出哈希槽,再根据哈希槽,映射到分片。这种算法,本质就是把上述两种方式结合了一下。
这里只是一种可能的方式,实际上分片是非常灵活的。
每个分片持有的槽位号,可以是连续的,也可以是不连续的。
此时,每个分片都会使用“位图”这样的数据结构,表示出当前有多少槽位号。
16384个bit位,用每一位0 / 1来区分自己这个分片当前是否持有该槽位号。
进行扩容:
上述过程中,只有被移动的槽位,对应的数据才需要搬运。降低了搬运成本。
redis中,当前某个分片包含哪些槽位,都是可以手动配置的。
问题1:Redis集群最多有16384个分片吗?
如果⼀个分片只有⼀个槽位, 这对于集群的数据均匀其实是难以保证的。
实际上 Redis 的作者建议集群分片数不应该超过 1000。而且, 16000 这么⼤规模的集群, 本⾝的可用性也是⼀个⼤问题. ⼀个系统越复杂, 出现故障的概率是越⾼的。
问题2:为什么是16384个槽位?
节点之间通过心跳包通信,心跳包中包含该节点持有那些槽位。如果16384个槽位,每个分片需要2KB大小的位图,来表示持有的槽位。如果槽位更多,就需要消耗更大的空间,更大的网络带宽。16384是比较均衡的值,个数上基本够用,也不会占据大的硬件资源(网络带宽)。
搭建集群环境
这里使用docker来搭建redis集群。
创建目录和配置
创建 redis-cluster ⽬录。内部创建两个⽂件。
redis-cluster/
├── docker-compose.yml
└── generate.sh
generate.sh 内容如下:
for port in $(seq 1 9); \
do \
mkdir -p redis${port}/
touch redis${port}/redis.conf
cat << EOF > redis${port}/redis.conf
port 6379
bind 0.0.0.0
protected-mode no
appendonly yes
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
cluster-announce-ip 172.30.0.10${port}
cluster-announce-port 6379
cluster-announce-bus-port 16379
EOF
done
# 注意 cluster-announce-ip 的值有变化.
for port in $(seq 10 11); \
do \
mkdir -p redis${port}/
touch redis${port}/redis.conf
cat << EOF > redis${port}/redis.conf
port 6379
bind 0.0.0.0
protected-mode no
appendonly yes
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
cluster-announce-ip 172.30.0.1${port}
cluster-announce-port 6379
cluster-announce-bus-port 16379
EOF
done
在linux上,以.sh后缀结尾的文件,称为“shell脚本”。使用linux的时候,都是通过一些命令来进行操作的。使用命令操作,就非常适合把命令给写到一个文件中,批量化执行。同时还能加入条件,循环,函数等机制。就可以基于这些来完成更复杂的工作了。
此时需要创建11个redis节点,这些redis的配置文件内容,大同小异。此时就可以使用脚本来批量生成。
• cluster-enabled yes 开启集群。
• cluster-config-file nodes.conf 集群节点⽣成的配置。
• cluster-node-timeout 5000 节点失联的超时时间。
• cluster-announce-ip 172.30.0.101 节点⾃⾝ ip。
• cluster-announce-port 6379 节点⾃⾝的业务端⼝。
• cluster-announce-bus-port 16379 节点⾃⾝的总线端⼝. 集群管理的信息交互是通过这个端口进行的。
编写 docker-compose.yml
• 先创建 networks, 并分配⽹段为 172.30.0.0/24
• 配置每个节点. 注意配置⽂件映射, 端⼝映射, 以及容器的 ip 地址. 设定成固定 ip ⽅便后续的观察和操作。
version: '3.7'
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
启动容器
docker-compose up -d
构建集群
接下来,要把上述redis节点构建成集群。
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 2
列出每个参与构建集群的ip和端口。(端口都是容器内部的端口)
cluster create: 表⽰建⽴集群. 后⾯填写每个节点的 ip 和地址。
cluster-replicas 2: 描述集群的每个主节点,应该有2个从节点。一共9个节点,设置了之后,redis就知道了3个节点是一伙的(一个分片)。
执⾏之后, 容器之间会进⾏加⼊集群操作。⽇志中会描述哪些是主节点, 哪些从节点跟随哪个主节点。
>>> Performing hash slots allocation on 9 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 172.30.0.105:6379 to 172.30.0.101:6379
Adding replica 172.30.0.106:6379 to 172.30.0.101:6379
Adding replica 172.30.0.107:6379 to 172.30.0.102:6379
Adding replica 172.30.0.108:6379 to 172.30.0.102:6379
Adding replica 172.30.0.109:6379 to 172.30.0.103:6379
Adding replica 172.30.0.104:6379 to 172.30.0.103:6379
以上,关于redis集群,希望对你有所帮助。