简介
Redis3.0版本之前,可以通过Redis Sentinel(哨兵)来实现高可用(HA),从3.0版本之后,官方推出了Redis Cluster,它的主要用途是实现数据分片(Data Sharding),不过同样可以实现HA,是官方当前推荐的方案。
在Redis Sentinel模式中,每个节点需要保存全量数据,冗余比较多,而在Redis Cluster模式中,每个分片只需要保存一部分的数据,对于内存数据库来说,还是要尽量的减少冗余。在数据量太大的情况下,故障恢复需要较长时间。
Redis Cluster本身提供了自动将数据分散到Redis Cluster不同节点的能力,分区实现的关键点问题包括:
- 如何将数据自动的打散到不同的节点,使得不同节点的存储数据相对均匀;
- 如何保证客户端能够访问到正确的节点和数据;
- 如何保证重新分片的过程中不影响正常服务。
数据分区
(这部分的资料来自于:https://blog.csdn.net/qq2430/article/details/80716313 感谢作者的分享)
在介绍Redis Cluster之前,先简单介绍下分布式数据库的数据分区。所谓的数据分区就是将一个较大的数据集分布在不同的节点上进行储存。
常见的数据分区方式:
- 节点取余;
- 一致性哈希;
- 虚拟槽
【节点取余】
根据key的hash值和节点数取模的方式计算出节点ID,然后向对应的节点提交数据,如下:
对于这种分区方式,新增或者删除节点会造成大量的数据迁移。假设数据集为:1 2 3... 10,那么数据分布应如下所示:
如果新增一个节点,那么数据分布会变成什么样子呢?
比较结果:只有 1、2、3还分布在原来的节点上,其余所有的数据都进行了迁移。在这种分区方式下,如果新增的节点时原来节点的倍数时,迁移的节点数量会少很多。
【一致性哈希】
对于任何的哈希函数,都有其取值范围。我们可以用环形结构来标识范围。通过哈希函数,每个节点都会被分配到环上的一个位置,每个键值也会被映射到环上的一个位置,然后顺时针找到相邻的节点。如下图所示,例如key分布在range1内,那么数据存储在node2上。
对于这种分区方式,新增或者删除节点会造成数据分布不均匀。假设数据集 1 2 3 ... 12,数据范围也是1-12,那么数据分布应如下所示:
如果我们在node1和node2之间新增一个节点,那么数据分布应该变成什么样子呢?
可以看到,我们只是将数据3进行了迁移。但是造成了每个节点负责的数据范围不等。会造成数据分布不均等的问题。
【虚拟槽】
在 redis cluster中使用槽来存储一定范围内的数据集。每个redis节点上有一定数量的槽。当客户端提交数据时,要先根据CRC16(key)&16383来计算出数据要落到哪个虚拟槽内。
假设我们有3个节点,那么可以按如下分配槽:
与节点取余和一致性哈希分区不同,虚拟槽分区是服务端分区。客户端可以将数据提交到任意一个redis cluster 节点上,如果存储该数据的槽不在这个节点上,则返回给客户端目标节点信息,告知客户端向目标节点提交数据。
Redis Cluster 原理介绍
RedisCluster是由多个同时服务于一个数据集合的Redis实例组成的整体,对于用户来说,用户只关注这个数据集合,而整个数据集合的某个数据子集存储在哪个节点对于用户来说是透明的。
redis cluster采用无中心结构,节点间使用gossip协议进行通信。每个节点保存数据和所有节点对应槽的映射关系。其架构图如下:
Redis Cluster 特点如下:
- 所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽。
- 节点的fail是通过集群中超过半数的节点检测失效时才生效。
- 客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。
- redis-cluster把所有的物理节点映射到[0-16383]slot上,cluster 负责维护node -> slot -> value
- 集群消息通信通过集群总线通信,集群总线端口大小为客户端服务端口+10000,这个10000是固定值;
- 客户端和集群节点之间通信和通常一样,通过文本协议进行;
- 集群节点不会代理查询
Redis Cluster的具体实现细节是采用了Hash槽的概念,集群会预先分配16384个槽,并将这些槽分配给具体的服务节点,通过对Key进行CRC16(key)%16384运算得到对应的槽是哪一个,从而将读写操作转发到该槽所对应的服务节点。当有新的节点加入或者移除的时候,再来迁移这些槽以及其对应的数据。在这种设计之下,我们就可以很方便的进行动态扩容或缩容。
【Redis Cluster投票:容错】
- 投票过程是集群中所有master参与,如果半数以上master节点与master节点通信超时(cluster-node-timeout),认为当前master节点挂掉,对应master节点挂掉,slave会代替工作
- 如果集群超过半数以上master挂掉,无论是否有slave,集群进入fail状态.
Redis Cluster集群搭建
(因为资源有限,本次在同一台主机上实现Redis Cluster)
1. 安装不同端口的 6 个 redis 节点
[root@192.168.118.15 /usr/local/src]#yum install gcc* -y [root@192.168.118.15 /usr/local/src]#ls redis-4.0.10.tar.gz [root@192.168.118.15 /usr/local/src]#tar xf redis-4.0.10.tar.gz [root@192.168.118.15 /usr/local/src]#cd redis-4.0.10 [root@192.168.118.15 /usr/local/src/redis-4.0.10]#mkdir -pv /usr/local/redis/{conf,db,log} mkdir: created directory ‘/usr/local/redis’ mkdir: created directory ‘/usr/local/redis/conf’ mkdir: created directory ‘/usr/local/redis/db’ mkdir: created directory ‘/usr/local/redis/log’ [root@192.168.118.15 /usr/local/src/redis-4.0.10]#make PREFIX=/usr/local/redis/ install
编译完成后,修改配置文件
[root@192.168.118.15 /usr/local/src/redis-4.0.10]#mkdir -pv /usr/local/redis/conf/63{79..84} mkdir: created directory ‘/usr/local/redis/conf/6379’ mkdir: created directory ‘/usr/local/redis/conf/6380’ mkdir: created directory ‘/usr/local/redis/conf/6381’ mkdir: created directory ‘/usr/local/redis/conf/6382’ mkdir: created directory ‘/usr/local/redis/conf/6383’ mkdir: created directory ‘/usr/local/redis/conf/6384’ [root@192.168.118.15 /usr/local/src/redis-4.0.10]#for i in 6379 6380 6381 6382 6383 6384; do cp -a redis.conf /usr/local/redis/conf/$i/$i.conf ; done [root@192.168.118.15 /usr/local/src/redis-4.0.10]#yum install tree -y [root@192.168.118.15 /usr/local/src/redis-4.0.10]#cd /usr/local/redis/ [root@192.168.118.15 /usr/local/redis]#tree . ├── bin │ ├── redis-benchmark │ ├── redis-check-aof │ ├── redis-check-rdb │ ├── redis-cli │ ├── redis-sentinel -> redis-server │ └── redis-server ├── conf │ ├── 6379 │ │ └── 6379.conf │ ├── 6380 │ │ └── 6380.conf │ ├── 6381 │ │ └── 6381.conf │ ├── 6382 │ │ └── 6382.conf │ ├── 6383 │ │ └── 6383.conf │ └── 6384 │ └── 6384.conf ├── db └── log 10 directories, 12 files
编写修改配置文件脚本
[root@192.168.118.15 /usr/local/redis/conf]#vim modify_conf.sh #!/bin/bash for i in 6379 6380 6381 6382 6383 6384; do sed -i "s/daemonize no/daemonize yes/g" $i/$i.conf sed -i "s/bind 127.0.0.1/bind 0.0.0.0/g" $i/$i.conf sed -i "s/port 6379/port $i/g" $i/$i.conf sed -i "s/dbfilename dump.rdb/dbfilename dump-$i.rdb/g" $i/$i.conf sed -i "s/redis_6379.pid/redis_$i.pid/g" $i/$i.conf sed -i "s/dir \.\//dir \/usr\/local\/redis\/db\//g" $i/$i.conf sed -i "s/logfile \"\"/logfile \"\/usr\/local\/redis\/log\/redis-$i.log\"/g" $i/$i.conf sed -i "s/# cluster-enabled yes/cluster-enabled yes/g" $i/$i.conf sed -i "s/# cluster-config-file nodes-6379.conf/cluster-config-file nodes-$i.conf/g" $i/$i.conf sed -i "s/# cluster-node-timeout 15000/cluster-node-timeout 15000/g" $i/$i.conf done [root@192.168.118.15 /usr/local/redis/conf]#sh modify_conf.sh
sed -i "s/daemonize no/daemonize yes/g" $i/$i.conf # 修改为后台运行 sed -i "s/bind 127.0.0.1/bind 0.0.0.0/g" $i/$i.conf # 修改监听ip sed -i "s/port 6379/port $i/g" $i/$i.conf # 修改监听端口 sed -i "s/dbfilename dump.rdb/dbfilename dump-$i.rdb/g" $i/$i.conf # 修改RDB模式持久化文件名 sed -i "s/redis_6379.pid/redis_$i.pid/g" $i/$i.conf # 修改pid文件名 sed -i "s/dir \.\//dir \/usr\/local\/redis\/db\//g" $i/$i.conf # 修改文件保存目录 sed -i "s/logfile \"\"/logfile \"\/usr\/local\/redis\/log\/redis-$i.log\"/g" $i/$i.conf # 修改日志文件目录及文件名 sed -i "s/# cluster-enabled yes/cluster-enabled yes/g" $i/$i.conf # 开启集群模式 sed -i "s/# cluster-config-file nodes-6379.conf/cluster-config-file nodes-$i.conf/g" $i/$i.conf # 定义集群配置文件(该文件无需手动创建,启动自动生成) sed -i "s/# cluster-node-timeout 15000/cluster-node-timeout 15000/g" $i/$i.conf # 开启集群节点无法连接超时时间