Redis Cluster集群搭建Cluster集群扩缩容底层原理
Posted 胡尚
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis Cluster集群搭建Cluster集群扩缩容底层原理相关的知识,希望对你有一定的参考价值。
文章目录
Cluster集群与核心原理
Cluster集群介绍
哨兵集群的一些缺点:
- 写操作只能在一台master节点上进行
- 单台redis的使用内存一般不超过10G,因为内存如果太大持久化时会影响性能
- master宕机后,重新选举,这一段时间中整个redis集群是不可用的
而Redis3.0版本开始就提供了RedisCluster的集群模式
-
整个服务数据是分片存储在多个master节点上的,每个master中存储的数据是不一样的,各个master节点下又可以加多个从节点,这样就组成了一个一个的小集群,多个小集群就组成了整个Cluster集群。
-
每个节点负责一定范围的哈希槽,默认总共有16384个slots槽位
-
这种方式搭建的集群读写都只能在master节点上行进行,slave节点一般只是做数据备份,不能读。
-
如果某个master节点宕机后,在哪个小集群中会进行选举,重新选举出一个master节点来。
-
一个小集群全挂了,不能对外提供服务了,默认情况下,整个cluster集群就都不能用了,但是可以通过一个配置项
cluster-require-full-coverage
改为no
,这样其他小集群就还能对外提供服务。
Cluster集群搭建
我们首先需要关闭防火墙,或者是开发redis提供服务的端口和gossip的端口(提供服务端口+10000)
比如我当前一台服务器要部署两个redis服务,使用6379和6380端口,那么我就需要开放6379 6380 16379 16380端口
# 临时关闭防火墙
systemctl stop firewalld
# 禁止开机启动
systemctl disable firewalld
接下来的一个环境搭建案例是使用三台服务器,其中运行六个redis实例
对各个实例的redis.conf文件中的基本配置项做一些相应的修改
# 后台运行
daemonize yes
# 端口
port 6379
# 把pid进程号写入pidfile配置的文件
pidfile /var/run/redis_6379.pid
# 数据文件存放位置,rdb和aof文件使用的文件目录就是这个配置项
dir /usr/local/redis-cluster/6379/
# 绑定ip可以选择注释掉
# bind 127.0.0.1
# 关闭保护模式
protected-mode no
# aof持久化 可以选择开不开启
appendonly yes
# 指定redis连接访问密码,一般生产环境都会配置一个密码
requirepass hs
# 配置了上面的密码,那么也就是要指定一下集群访问的密码,和上面要一致,不然集群各个节点访问不通
masterauth hs
# 接下来三个是cluster集群相关的配置
# 启动集群模式
cluster-enabled yes
# 集群节点信息文件,这里6379建议和port对应上
# 这个文件保存了整个Cluster集群各个节点信息,当服务器关机后我们启动redis实例后 不需要在执行一遍--cluster create创建集群的命令。只需要启动redis服务即可,它会根据这个文件中的信息自动搭建好cluster集群
cluster-config-file nodes-6379.conf
# 节点超时时间
cluster-node-timeout 10000
六个redis实例的配置文件都准备好后就都先启动
redis-server redis.conf
此时六个redis-server都是毫无关联的,接下来将它们配置成为一个cluster集群
# -a 指定访问redis的密码
# -- cluster-replicas 表示每个redis小集群中,master下有几个slave,这里配置为1 就表示各个mastere只有一个slave
# 最后就是6个redis实例的ip和端口
redis-cli -a hs --cluster create --cluster-replicas 1 192.168.75.50:6379 192.168.75.50:6380 ......
验证集群
连接任意一个客户端即可
# -a 访问密码 -c 表示集群模式 -h -p为连接主机ip和端口
# redis-cli -c -h -p
redis-cli -a hs -c -h 192.168.75.50 6379
# 查看集群信息
cluster info
# 查看节点列表
cluster nodes
关闭
关闭集群则需要逐个进行关闭
redis-cli -a hs -c -h 192.168.75.50 -p 6379 shutdown
集群扩缩容
查看redis集群帮助
- create:创建一个集群环境
- check:检查集群状态
- reshard:重新分片
- add-node:添加新节点到cluster集群中,第一个参数为新节点的ip:port,第二个参数为集群中任意一个已经存在的节点的ip:port
- del-node:移除一个节点
- call:可以执行redis命令
扩容
我们首先按照上面Cluster集群搭建部分的内容,先启动两个redis实例192.168.0.61:8007(主)
和192.168.0.61:8008(从)
然后执行下面的命令
# -a redis连接密码
# 第一个ip为当前新启动的redis实例ip
# 后面一个ip为当前cluster集群中任意一个正常运行的节点ip
/usr/local/redis-5.0.3/src/redis-cli -a hs --cluster add-node 192.168.0.61:8007 192.168.0.61:8001
查看节点状态
/usr/local/redis-5.0.3/src/redis-cli -a hs -c -h 192.168.0.61 -p 8001
192.168.0.61:8001> cluster nodes
新加入的节点都是master,并且不会分配任何slot槽位,我们要手动为新节点分配hash槽
使用redis-cli --cluster reshard
命令为新加入的节点分配槽位,需要使用集群中任意一个master节点对其进行重新分片工作
/usr/local/redis-5.0.3/src/redis-cli -a hs --cluster reshard 192.168.0.61:8001
接下来的命令交互如下:
… …
How many slots do you want to move (from 1 to 16384)? 600
(ps:需要多少个槽移动到新的节点上,自己设置,比如600个hash槽)
… …
What is the receiving node ID? 2728a594a0498e98e4b83a537e19f9a0a3790f38
(ps:把这600个hash槽移动到哪个节点上去,需要指定节点id)
… …
Please enter all the source node IDs.
Type ‘all’ to use all the nodes as source nodes for the hash slots.
Type ‘done’ once you entered all the source nodes IDs.
Source node 1:all
(ps:输入all为从所有主节点(8001,8002,8003)中分别抽取相应的槽数指定到新节点中,抽取的总槽数为600个)
… …
Do you want to proceed with the proposed reshard plan (yes/no)? yes
(ps:输入yes确认开始执行分片任务)
接下来再查看最新的集群节点信息
/usr/local/redis-5.0.3/src/redis-cli -a hs -c -h 192.168.0.61 -p 8001
192.168.0.61:8001> cluster nodes
此时还只是添加了一台master节点到集群中,我们接下来在添加192.168.0.61:8008
节点来作为192.168.0.61:8007
节点的从节点
# 使用集群中任意一个节点来进行添加新节点
/usr/local/redis-5.0.3/src/redis-cli -a hs --cluster add-node 192.168.0.61:8008 192.168.0.61:8001
新加入的节点是master,并且不会分配slot槽位
接下来需要登录刚刚添加的新节点,使用replicate
命令来指定当前节点要作为哪一个节点的slave节点,
# 先登录从节点,然后在replicate命令中指定主节点的id
/usr/local/redis-5.0.3/src/redis-cli -a hs -c -h 192.168.0.61 -p 8008
192.168.0.61:8008> cluster replicate 2728a594a0498e98e4b83a537e19f9a0a3790f38 #后面这串id为8007的节点id
现在8008端口的redis实例就变为了slave了。
缩容
接下来将上面新增加的两个节点删除
删除8008从节点
用redis-cli --cluster del-node
删除从节点8008,指定删除节点ip和端口,以及节点id
/usr/local/redis-5.0.3/src/redis-cli -a hs --cluster del-node 192.168.0.61:8008 a1cfe35722d151cf70585cee21275565393c0956
再次查看集群状态,如下图所示,8008这个slave节点已经移除,并且该节点的redis服务也已被停止
删除8807主节点
主节点的里面是有分配了hash槽的,所以我们这里必须先把8007里的hash槽放入到其他的可用主节点中去,然后再进行移除节点操作,不然会出现数据丢失问题
目前只能把master的数据迁移到一个节点上,暂时做不了平均分配功能
# 任选一个主节点进行重新分片
/usr/local/redis-5.0.3/src/redis-cli -a hs --cluster reshard 192.168.0.61:8007
接下来的命令交互如下:
… …
How many slots do you want to move (from 1 to 16384)? 600
(ps:需要多少个槽移动到新的节点上)
… …
What is the receiving node ID? 2728a594a0498e98e4b83a537e19f9a0a3790f38
(ps:把这600个hash槽移动到哪个节点上去,这里使用8001的主节点id)
… …
Please enter all the source node IDs.
Type ‘all’ to use all the nodes as source nodes for the hash slots.
Type ‘done’ once you entered all the source nodes IDs.
Source node 1:2728a594a0498e98e4b83a537e19f9a0a3790f38
(ps:这里是需要数据源,也就是我们的8007节点id。这里这次就不写all了)
… …
Source node 2:done
(ps:这里直接输入done 开始生成迁移计划)
… …
Do you want to proceed with the proposed reshard plan (yes/no)? yes
(ps:这里输入yes开始迁移)
至此,我们已经成功的把8007主节点的数据迁移到8001上去了,我们可以看一下现在的集群状态如下图,你会发现8007下面已经没有任何hash槽了,证明迁移成功!
最后我们直接使用del-node命令删除8007主节点即可
# 指定要删除的节点实例ip 端口 节点id
/usr/local/redis-5.0.3/src/redis-cli -a hs --cluster del-node 192.168.0.61:8007 2728a594a0498e98e4b83a537e19f9a0a3790f38
现在就是回到了最当开始六个实例的时候了
java操作Cluster集群模式
Jedis方式
引入依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
java代码如下
public class JedisClusterTest
public static void main(String[] args) throws IOException
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(20);
config.setMaxIdle(10);
config.setMinIdle(5);
// 指定集群所有节点
Set<HostAndPort> jedisClusterNode = new HashSet<HostAndPort>();
jedisClusterNode.add(new HostAndPort("192.168.0.61", 8001));
jedisClusterNode.add(new HostAndPort("192.168.0.62", 8002));
jedisClusterNode.add(new HostAndPort("192.168.0.63", 8003));
jedisClusterNode.add(new HostAndPort("192.168.0.61", 8004));
jedisClusterNode.add(new HostAndPort("192.168.0.62", 8005));
jedisClusterNode.add(new HostAndPort("192.168.0.63", 8006));
JedisCluster jedisCluster = null;
try
//connectionTimeout:指的是连接一个url的连接等待时间
//soTimeout:指的是连接上一个url,获取response的返回等待时间
jedisCluster = new JedisCluster(jedisClusterNode, 6000, 5000, 10, "hs", config);
System.out.println(jedisCluster.set("cluster", "hs"));
System.out.println(jedisCluster.get("cluster"));
catch (Exception e)
e.printStackTrace();
finally
if (jedisCluster != null)
jedisCluster.close();
SpringBoot相关配置
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
yaml配置
server:
port: 8080
spring:
redis:
database: 0
timeout: 3000
password: hs
cluster:
nodes: 192.168.0.61:8001,192.168.0.62:8002,192.168.0.63:8003,192.168.0.61:8004,192.168.0.62:8005,192.168.0.63:8006
lettuce:
pool:
max-idle: 50
min-idle: 10
max-active: 100
max-wait: 1000
java代码访问还是直接通过redisTemplate对象来访问
@RestController
public class IndexController
private static final Logger logger = LoggerFactory.getLogger(IndexController.class);
@Autowired
private StringRedisTemplate stringRedisTemplate;
@RequestMapping("/test_cluster")
public void testCluster() throws InterruptedException
stringRedisTemplate.opsForValue().set("hs", "666");
System.out.println(stringRedisTemplate.opsForValue().get("hs"));
原理分析
客户端如何确认当前命令连接哪一个redis节点
RedisCluster将所有数据划分为16384个slots槽位,每个master节点负责一部分槽位
当redis客户端连接redis服务端时,会将各个节点负责的槽位信息获取到并缓存在本地。当客户端执行一条set命令时,客户端会根据hash计算出当前key的槽位,然后在得到对应的redis-server的ip,这样就直接定位到目标节点了
槽位定位算法
底层默认会使用crc16算法得到一个整数,然后在对16384取余,最终得到槽位结果
HASH_SLOT = CRC16(key) mod 16384
跳转重定向
redis客户端执行一条set命令,该节点会发现这个key的槽位不归自己管理,然后它会向客户端发送一个跳转指令并携带目标节点地址
客户端收到指令后除了跳转到目标节点上去之外,还会同步更新纠正本地的槽位映射表缓存,后续所有key将使用更新后的槽位映射表。
这里之所以客户端会更新槽位映射表的原因是,假如服务端对槽位进行了重新分配,但是客户端还不知道,还是安装之前旧的缓存中的映射表去找节点,这时服务器就会知道这个槽位已经不归我管了,客户端此时再更新一下最新的槽位映射表即可
节点通信机制
Redis Cluster集群架构使用的是gossip协议进行通信
维护集群元数据(集群节点信息,主从角色,节点数量,各节点共享的数据等)有两种方式:集中式、gossip
-
集中式
优点是对于节点元数据更新和读取的实时性更好,更新操作后会立刻更新集中式的存储中,其他节点就能立刻感知到
缺点是元数据的更新压力全集中在一个地方,可能会导致元数据的存储压力
很多中间件都会利用zookeeper集中式存储元数据
-
gossip
gossip包含多种消息,包括ping、pong、meet、fail等等
- ping命令:每个节点会频繁的给其他节点发送ping,其中包括自己的状态还有维护的集群元数据,互相通过ping交互元数据
- meet命令:某个节点发送meet命令给新加入的节点,让新节点加入集群中,然后新节点就会与其他节点开始通信
- pong命令:对于ping命令与meet命令的返回,包括自己的状态和其他信息,也可以用户信息广播和更新
- fail命令:某个节点判断另一个节点fail后,就会发送fail给其他节点,通知其他节点指定的节点宕机了
网络抖动
可能某个master与slave之间的因为网络抖动,一段时间不能互通了,那么可能slave就会认为这个master宕机了,就会触发主节点重新选举。
我们可以通过配置cluster-node-timeout
参数,设定一个超时时间,单位是毫秒,如果超过这个超时时间后才会去触发主节点重新选举
集群选举原理
当一个主节点宕机后,并且经过上面cluster-node-timeout
参数设定的超时时间后,这个master节点下的所有slave节点就会想整个集群中的其他节点发送一个命令,但是只有其他小集群中的master节点才会响应,并且只会响应给一个slave节点。
因为一个master节点会收到宕机小集群下的所有slave节点的消息,它只会响应给最先请求到节点。
当某个slave得到的响应超过了所有master节点数量的一半后,那么它就会成为新的master节点。一段时间后宕机的老的master启动后会变为这个节点的slave节点。
如果多个slave节点得到的响应都没有超过一半那么就会重新再来一遍,但其实Redis底层通过slave延时发送机制来避免这个问题。
详情如下:
当slave发现自己的master变为FAIL状态时,便尝试进行Failover,以期成为新的master。由于挂掉的master可能会有多个slave,从而存在多个slave竞争成为master节点的过程, 其过程如下:
-
slave发现自己的master变为FAIL
-
将自己记录的集群currentEpoch加1,并广播FAILOVER_AUTH_REQUEST 信息
-
其他节点收到该信息,只有master响应,判断请求者的合法性,并发送FAILOVER_AUTH_ACK,对每一个epoch只发送一次ack
-
尝试failover的slave收集master返回的FAILOVER_AUTH_ACK
-
.slave收到超过半数master的ack后变成新Master
(这里解释了集群为什么至少需要三个主节点,如果只有两个,当其中一个挂了,只剩一个主节点是不能选举成功的,因为得到的结果永远都是1,不会大于1)
-
slave广播Pong消息通知其他集群节点。
从节点并不是在主节点一进入 FAIL 状态就马上尝试发起选举,而是有一定延迟,一定的延迟确保我们等待FAIL状态在集群中传播,slave如果立即尝试选举,其它masters或许尚未意识到FAIL状态,可能会拒绝投票
延迟计算公式:
DELAY = 500ms + random(0 ~ 500ms) + SLAVE_RANK * 1000ms
SLAVE_RANK
表示此slave已经从master复制数据的总量的rank。Rank越小代表已复制的数据越新。这种方式下,持有最新数据的slave将会首先发起选举(理论上)。
脑裂问题–数据丢失
当发生网路分区,某个master节点不能和slave节点通信了,这时master节点没有宕机,但是slave会认为master宕机了,然后又触发了主节点重新选举。
然后就会出现一个小集群中存在了两个master节点。这时客户端就会分开向两个master节点中写数据,当网络分区恢复后,老master会作为新master节点是从节点,这时就会触发一次主从复制机制,老master的数据就会全清理掉,那么这一段时间中客户端往老master节点上写的数据就是丢失
可以通过下面配置来解决脑裂问题
# 写数据成功最少同步的slave数量,这个数量可以模仿大于半数机制配置,比如集群总共三个节点可以配置1,加上leader就是2,超过了半数
min-replicas-to-write 1
这个配置一定程度上会 影响集群的可用性,假如slave节点宕机了,导致配置的这个数量达不到,那么客户端就会写入不成功。
根据具体服务具体分析,看是否要开启这个配置
集群是否完整才能对外提供服务
当redis.conf的配置cluster-require-full-coverage
为no
时,表示当负责一个插槽的主库下线且没有相应的从库进行故障恢复时,集群仍然可用,如果为yes
则集群不可用。
为什么至少需要三个master,为什么推荐节点数为单数
如果少于了三个master,那么主节点重新选举是不会成功的,因为slave得到其他master的ack应答永远不会大于一半。
比如集群中就两个master,现在是需要大于1,但是得到的ack应答数永远都是1,不会大于1.
推荐节点数为单数的原因是节省服务器资源
因为三个master节点的情况下,只能允许最多一个master节点宕机,而如果四个master节点的情况下,也最多只能允许一个master节点宕机。
当然这只是不考虑需要增加并发访问量而增加redis节点的情况,当然也可以把master节点数加为偶数,只是推荐单数。
Cluster集群对批量命令的操作支持
我们知道可以通过mset key value [key value...]
来批量添加多个数据。
但使用cluster集群如果多个key的哈希槽计算结果一样才会成功,否则执行不成功
但是我们可以在key加一个相同的前缀,这个前缀使用包起来,这样redis就只会计算包起来值的hash槽,这样多个key就一样了
mset user1:1:name zhuge user1:1:age 18
哨兵leader选举流程
当一个master服务器被某sentinel视为下线状态后,该sentinel会与其他sentinel协商选出sentinel的leader进行故障转移工作。每个发现master服务器进入下线的sentinel都可以要求其他sentinel选自己为sentinel的leader,选举是先到先得。同时每个sentinel每次选举都会自增配置纪元(选举周期),每个纪元中只会选择一个sentinel的leader。如果所有超过一半的sentinel选举某sentinel作为leader。之后该sentinel进行故障转移操作,从存活的slave中选举出新的master,这个选举过程跟集群的master选举很类似。
哨兵集群只有一个哨兵节点,redis的主从也能正常运行以及选举master,如果master挂了,那唯一的那个哨兵节点就是哨兵leader了,可以正常选举新master。
不过为了高可用一般都推荐至少部署三个哨兵节点。为什么推荐奇数个哨兵节点原理跟集群奇数个master节点类似。
redis集群redis-cluster搭建
redis集群搭建--参考微信公众号(诗情画意程序员):https://mp.weixin.qq.com/s/s5eJE801TInHgb8bzCapJQ
这是来自redis官网的一段介绍,大概意思就是:
Redis是一个开源(BSD许可)的内存数据结构存储,用作数据库、缓存和消息代理。它支持诸如字符串、散列、列表、集、带范围查询的排序集、位图、hyperloglogs、带半径查询和流的地理空间索引等数据结构。Redis具有内置的复制、Lua脚本、LRU清除、事务和不同级别的磁盘持久性,并通过Redis Sentinel和带有Redis集群的自动分区提供高可用性。
and so on ...
不过这不是今天的重点,今天的重点是redis的集群搭建。
搭建集群呢,先安装个单击版的,其实呢安装挺简单。
redis的安装单机版:
-
redis是c语言开发的。安装redis需要c语言的编译环境。如果没有gcc需要在线安装。yum install gcc-c++
-
将redis的安装包上传到Linux并解压缩。
-
进入redis源码目录进行编译。输入命令:make
-
编译完后进行安装,输入命令:make install PREFIX=/usr/local/redis
PREFIX指定redis的安装目录,我的安装在了/usr/local/redis
这时单击版的就已经安装好了,接下来启动一下:
在redis的安装目录直接启动:
输入命令:[root@localhost bin]# ./redis-server
出现这个界面表示启动成功。
可以通过查看redis进程:
输入命令:ps aux|grep redis
但是这是前台启动,退出后redis就关闭了,下面设置后台启动的方法。
-
把/root/redis-3.0.0/redis.conf复制到/usr/local/redis/bin目录下
- 进入/usr/local/redis/bin目录下,修改redis.conf配置文件,将daemonize no 改为 yes,保存并退出
-
这时再次启动redis,但是要指定配置文件以后台启动
输入命令:[root@localhost bin]# ./redis-server redis.conf
可以看到并没有出现redis的启动图标,那是否启动成功了呢?这时我们查看一下redis进程。
输入命令:ps aux|grep redis
可以看到redis已经成功启动。
下面我们开始搭建集群版,下面是redis官网的一段截取。
更多的介绍请到redis官网查看。
由于redis-cluster采用投票容错的方式来判断该节点是否挂掉,投票容错简单点说就是投票超总数的一半即判定该节点挂掉,因此最少需要三个节点,但是由于redis-cluster要保证高可用,因此每个主节点需要一个备份机,也就是说至少需要六个节点。
这里在redis官网也提到了。
六个节点需要六台服务器,这里为了演示就先搭建一个伪分布式,操作步骤和在六台服务器上搭建完全一样。
集群环境搭建
步骤一
使用ruby脚本搭建集群,需要ruby的运行环境
安装ruby
yum install ruby
yum install rubygems
步骤二
安装ruby脚本运行所需的依赖包 redis-3.0.0.gem
[root@localhost wl]# gem install redis-3.0.0.gem
搭建redis集群
搭建伪分布式,需要6个redis的实例,分别运行在7001,7002,7003,7004,7005,7006端口
步骤一
创建6个redis实例,把之前的单机版复制6份就可以。
在/usr/local/下创建redis-cluster目录
将/usr/local/redis目录下的单机版复制6分到/usr/local/redis-cluster/
步骤二
每个实例运行在不同的端口。需要修改redis.conf配置文件。
配置文件中还需要把cluster-enabled yes前的注释去掉。
进入redis01目录下,打开redis.conf配置文件
修改端口号为7001,并把cluster-enabled yes前的注释去掉
其他5个做相同的设置,注意端口不一样。
步骤三
-
启动每个redis实例
-
使用ruby脚本搭建集群,需要使用redis-trib.rb,此文件在源码src目录下。
-
进入src目录,将redis-trib.rb复制到/usr/local/redis-cluster目录下
cp redis-trib.rb /usr/local/redis-cluster
进入/usr/local/redis-cluster目录执行命令:
./redis-trib.rb create --replicas 1 192.168.25.131:7001 192.168.25.131:7002 192.168.25.131:7003 192.168.25.131:7004 192.168.25.131:7005 192.168.25.131:7006
-
到此redis集群就搭建完成了,这时启动一下。
逐个启动太费事,编写个脚本
在redis-cluster目录下创建一个.sh的脚本,
-
创建完成后修改文件的权限
chmod u+x start_redis.sh
运行文件 sh start_redis.sh,启动redis集群
查看后台进程 ps aux|grep redis
-
可以看到redis集群成功启动。
以上是关于Redis Cluster集群搭建Cluster集群扩缩容底层原理的主要内容,如果未能解决你的问题,请参考以下文章
Redis Cluster 4.0 on CentOS 6.9 搭建
Redis Cluster集群搭建Cluster集群扩缩容底层原理