所谓的集群,就是通过添加服务器的数量,提供相同的服务,从而让服务器达到一个稳定、高效的状态(高可用)。
问题:我们已经部署好了redis,并且能启动一个redis,实现数据的读写,为什么还要学习redis集群?
redis集群是为了强化redis的读写能力。
所以,学习redis集群,就是从学习redis主从复制模型开始的。
主从复制模型中,有多个redis节点。其中,有且仅有一个为主节点Master。从节点Slave可以有多个。只要网络连接正常,Master会一直将自己的数据更新同步给Slaves,保持主从同步。
因此,主从模型可以提高读的能力,在一定程度上缓解了写的能力。因为能写仍然只有Master节点一个,可以将读的操作全部移交到从节点上,变相提高了写能力。
节点 | 端口 |
---|---|
主节点 | 6380 |
从节点(两个) | 6381、6382 |
/usr/local
目录下,创建一个 /redis/master-slave
目录master-slave
目录下,创建三个子目录 6380、6381、6382依次启动主从节点,主节点的日志中会显示从节点的连入。经测试可以看到,主节点可以读写,从节点默认只能读不能写。
当主节点宕机了,整个集群就没有可写的节点了。由于从节点上备份了主节点的所有数据,那在主节点宕机的情况下,如果能够将从节点变成一个主节点,是不是就可以解决这个问题了呢?是的,这个就是Sentinel哨兵的作用。
Redis 的 Sentinel 系统用于管理多个 Redis 服务器(instance), 该系统执行以下三个任务:
监控同一个Master的Sentinel会自动连接,组成一个分布式的Sentinel网络,互相通信并交换彼此关于被监视服务器的信息。下图中,三个监控s1的Sentinel,自动组成Sentinel网络结构。
前提:已经存在一个正在运行的主从模式。
配置三个Sentinel实例,监控同一个Master节点。
/usr/local
目录下,创建 /redis/sentinels/
目录/sentinels
目录下,以次创建 s1、s2、s3 三个子目录依次启动三个哨兵后,可以看到日志输出
在哨兵模式中,仍然只有一个Master节点。当并发写请求较大时,哨兵模式并不能缓解写压力。我们知道只有主节点才具有写能力,那如果在一个集群中,能够配置多个主节点,是不是就可以缓解写压力了呢?是的。这个就是redis-cluster集群模式。
在Redis-Cluster集群中,可以给每一个主节点添加从节点,主节点和从节点直接遵循主从模型的特性。当用户需要处理更多读请求的时候,添加从节点可以扩展系统的读性能。
Redis集群的主节点内置了类似Redis Sentinel的节点故障检测和自动故障转移功能,当集群中的某个主节点下线时,集群中的其他在线主节点会注意到这一点,并对已下线的主节点进行故障转移。
集群进行故障转移的方法和Redis Sentinel进行故障转移的方法基本一样,不同的是,在集群里面,故障转移是由集群中其他在线的主节点负责进行的,所以集群不必另外使用Redis Sentinel。
Redis-cluster分片策略,是用来解决key存储位置的。
集群将整个数据库分为16384个槽位slot,所有key-value数据都存储在这些slot中的某一个上。一个slot槽位可以存放多个数据,key的槽位计算公式为:slot_number=crc16(key)%16384
,其中 crc16 为 16 位的循环冗余校验和函数。
集群中的每个主节点都可以处理0个至16383个槽,当16384个槽都有某个节点在负责处理时,集群进入上线状态,并开始处理客户端发送的数据命令请求。
由于Redis集群无中心节点,请求会随机发给任意主节点;主节点只会处理自己负责槽位的命令请求,其它槽位的命令请求,该主节点会返回客户端一个转向错误;客户端根据错误中包含的地址和端口重新向正确的负责的主节点发起命令请求。
redis 集群管理工具 redis-trib.rb 依赖 ruby 环境,首先需要安装 ruby 环境:
yum -y install ruby
yum -y install rubygems
拷贝redis-3.0.0.gem至/usr/local下,执行安装:
gem install /usr/local/redis-3.0.0.gem
或者直接用 gem 在线安装
gem install reids
官方提供了此工具用于挂历 redis 集群,该工具就在解压目录的 src 目录下
Redis集群最少需要6个节点,可以分布在一台或者多台主机上。以下测试为在一台主机上创建伪分布式集群,不同的端口表示不同的redis节点,如下:
在 /usr/local/redis 下创建 redis-cluster 目录,其下创建7001、7002...7006目录,复制 redis.conf 配置文件到每个文件夹,并配置
# 必选配置
port 700X
bind 192.168.X.X
cluster-enabled yes
# 可选配置
daemonized yes
logfile /usr/local/redis/redis-cluster/700X/node.log
依次以700X下的redis.conf,启动redis节点。(必须指定redis.conf文件)
注意,需要分别进入各个文件夹启动,不然会报 cluster config file 已经被使用的错误
进入到 redis 源码存放目录 src 目录下,执行redis-trib.rb,此脚本是ruby脚本,它依赖ruby环境。
./redis-trib.rb create --replicas 1 192.168.163.88:7001 192.168.163.88:7002 192.168.163.88:7003 192.168.163.88:7004 192.168.163.88:7005 192.168.163.88:7006
这里发现,最新版 redis 已经取消对 redis-trib.rb 的支持,采用示例方法创建集群
新版本的命令大全
集群创建成功登陆任意redis结点查询集群中的节点情况。
集群创建成功后可以向集群中添加节点,下面是添加一个master主节点,添加7007节点。
配置和启动新节点的步骤略
执行下边命令添加节点(第一个地址为新节点,第二个地址为 cluster 集群中的任意一个节点地址):
./redis-trib.rb add-node 192.168.23.3:7007 192.168.23.3:7001 //已过时
redis-cli --cluster add-node 192.168.163.88:7007 192.168.163.88:7002
运行 redis-cli --cluster check 192.168.163.88:7001
检查状态,发现新节点作为主节点加入,但没有 slot 分配给它。
添加完新的主节点后,需要对主节点进行slot槽分配,这样该主节才可以存储数据。
连接集群中任意一个可用节点都行
redis-cli --cluster reshard 192.168.163.88:7001
输入 500表示要分配500个槽
这里输入的是新加入的节点 7007
这里我选择从 7001 作为源节点获取 500 个 slot,也可以输入 all 表示从所有主节点中平均获取。可以输入多个源节点,输入done 表示输入结束。
如果只是从一个源里转移,可以使用一句语句完成操作
redis-cli --cluster reshard 192.168.163.88:7001 --cluster-from 95252ffbf34bb114b859ed7da8a312e28347d5c1 --cluster-to e272188208df9d9080d41a89a0fffd49e503879c --cluster-slots 500
为新增的主节点添加从节点,将 7008 作为 7007 的从节点。
redis-cli --cluster add-node 192.168.163.88:7008 192.168.163.88:7001 --cluster-slave --cluster-master-id e272188208df9d9080d41a89a0fffd49e503879c
若不指定 --cluster-master-id 同时声明了 --cluster-slave,则默认会添加为第二个地址的从节点
若 7008 下面已有 nodes.conf ,添加时可能会报错, 解决方法是删除该文件后再添加
使用命令
redis-cli --cluster del-node 192.168.163.88:7001 d5d9af031a714c4fe334e8950de46add16c0e6df
第一个地址为 cluster 任一节点, 后面 id 为需要删除的节点 id
需要注意的是,若删除的节点为主节点,需要将其所拥有的 slot 分配出去后才能删除,不然会报如下错误
将 7007 的 slot 转移回 7001 后删除
import org.junit.Test;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import java.util.HashSet;
import java.util.Set;
public class ClusterTest {
@Test
public void ClusterConnectionTest() {
//创建 set 集合封装所有节点信息
Set<HostAndPort> nodes = new HashSet<>();
//只需要添加一个节点即可,会自动搜索其它节点
nodes.add(new HostAndPort("192.168.163.88", 7001));
/*nodes.add(new HostAndPort("192.168.163.88", 7002));
nodes.add(new HostAndPort("192.168.163.88", 7003));
nodes.add(new HostAndPort("192.168.163.88", 7004));
nodes.add(new HostAndPort("192.168.163.88", 7005));
nodes.add(new HostAndPort("192.168.163.88", 7006));*/
//使用节点创建一个 JedisCluster 对象
JedisCluster jedisCluster = new JedisCluster(nodes);
//测试连接结果
System.out.println(jedisCluster.get("wtf"));
}
@Test
public void RedisConnectionTest(){
Jedis jedis = new Jedis("192.168.163.88", 7001);
System.out.println(jedis.ping());
String hello = jedis.get("hello"); //hello 存在 7001
System.out.println(hello);
//采用普通方式连接,若数据不是存在此节点,会报错
/*String wtf = jedis.get("wtf"); //wtf 存在 7002
System.out.println(wtf);*/
}
}
连接Redis集群时,需要修改防火墙,开方每一个redis节点的端口。
说明:如果要开发一个范围的端口,可以使用冒号来分割,即: 7001:7008,表示开发7001-7008之间所有的端口,或者使用 setup 工具设置