什么是集群? 其实多台主机提供相同的服务的一组序列就叫集群,简单的来说集群就是一组或者若干组相互独立的计算机,利用高速通信网络组成的一个较大的计算机服务系统,每个集群节点(下面会介绍到)都是运行各自服务的独立服务器。在某种意义上,他们可以被看作是一台计算机。这些服务器之间可以彼此通信,协同向用户提供应用程序,系统资源和数据。用通俗一点的话来讲就是很多个人做同一件事(这里的“事”是一个代指,也可细分)。
而Redis中的集群主要是用来解决一个Redis水平扩容的问题, Redis3.0加入了Redis的集群模式,实现了数据的分布式存储,对数据进行分片,将不同的数据存储在不同的master节点上面,从而解决了海量数据的存储问题。
采用了去中心化的思想,没有中心节点的说法,对于客户端来说整个集群可以看成一个整体,可以连接任意一个节点进行操作,就像操作单一Redis实例一样,不需要任何代理中间件,当客户端操作的key没有分配到该node上时,Redis会返回转向指令,指向正确的node。
Redis也内置了高可用机制,支持N个master节点,每个master节点都可以挂载多个slave节点,当master节点挂掉时,集群会提升它的某个slave节点作为新的master节点。
当然也可以配合上一篇文章中的模式进行配置使用如,主从模式
,薪火相传
模式等。
而在Redis3.0之前,一直在使用代理主机的方式实现Redis。
假设现在一个服务需要一个Redis集群的支撑,用户的用户功能对应Redis集群中特定处理用户业务缓存的主机,订单功能对应Redis集群中特定处理订单业务缓存的主机,商品功能对应Redis集群中特定处理商品功能的业务缓存主机。但是此时Redis中还没有出现去中心化的集群方式,使用的还是代理主机的方式,通过代理主机将对应业务的消息转发到对应的业务主机进行处理。
而在Redis3.0时Redis推出了去中心化的Redis集群搭建方式,对于客户端来说整个集群可以看成一个整体,可以连接任意一个节点进行操作,就像操作单一Redis实例一样,不需要任何代理中间件,当客户端操作的key没有分配到该node上时,Redis会返回转向指令,指向正确的node。
首先在根目录下创建一个名为myredis的文件夹用来存放我们演示的配置信息
// 创建文件夹
mkdir /myredis
// 进入文件夹
cd /myredis
然后将本机中的Redis配置文件redis.conf
复制到我们的配置目录
cp /usr/src/redis-6.2.6/redis.conf /myredis
将复制过来的配置文件中的以下选项修改为对应的值
appendonly no // appendonly修改为no 默认就是no
紧接着我们创建6个配置文件,分别对应每一台Redis服务,本次的搭建演示需要创建6个Redsi实例,6个Redsi实例分别占用,6379、6380、6381、6382、6383、6384端口,为了方便识别我们的配置文件名后缀使用端口命名方便识别,然后使用include /myredis/redis.conf是引入redis.conf
配置文件的公共部分,6份配置文件的文件名分别为redis6379.conf
、redis6380.conf
、redis6381.conf
、redis6382.conf
、redis6383.conf
、redis6384.conf
,分别是三台主机,和与之对应的三台从机。
可以使用以下命令进行创建。
touch redis6379.conf
touch redis6380.conf
touch redis6381.conf
touch redis6382.conf
touch redis6383.conf
touch redis6384.conf
创建完6个配置文件后,分别将以下内容添加到对应的配置文件中。
//redis6379.conf文件内容
include /myredis/redis.conf
pidfile /var/run/redis_6379.pid
port 6379
dbfilename dump6379.rdb
cluster-enabled yes
cluster-config-file nodes-6379.conf
cluster-node-timeout 15000
protected-mode no
masterauth 331213
//redis6380.conf文件内容
include /myredis/redis.conf
pidfile /var/run/redis_6380.pid
port 6380
dbfilename dump6380.rdb
cluster-enabled yes
cluster-config-file nodes-6380.conf
cluster-node-timeout 15000
protected-mode no
masterauth 331213
//redis6381.conf文件内容
include /myredis/redis.conf
pidfile /var/run/redis_6381.pid
port 6381
dbfilename dump6381.rdb
cluster-enabled yes
cluster-config-file nodes-6381.conf
cluster-node-timeout 15000
protected-mode no
masterauth 331213
//redis6382.conf文件内容
include /myredis/redis.conf
pidfile /var/run/redis_6382.pid
port 6382
dbfilename dump6382.rdb
cluster-enabled yes
cluster-config-file nodes-6382.conf
cluster-node-timeout 15000
protected-mode no
masterauth 331213
//redis6383.conf文件内容
include /myredis/redis.conf
pidfile /var/run/redis_6383.pid
port 6383
dbfilename dump6383.rdb
cluster-enabled yes
cluster-config-file nodes-6383.conf
cluster-node-timeout 15000
protected-mode no
masterauth 331213
//redis6384.conf文件内容
include /myredis/redis.conf
pidfile /var/run/redis_6384.pid
port 6384
dbfilename dump6383.rdb
cluster-enabled yes
cluster-config-file nodes-6384.conf
cluster-node-timeout 15000
protected-mode no
masterauth 331213
修改完配置文件后记得保存
相关配置内容作用如下
include /myredis/redis.conf// 引入配置文件的公共部分
pidfile /var/run/redis_6379.pid // Redis以守护进程方式运行时,Redis默认会把pid写入/var/run/redis.pid文件
port 6379 // 服务占用端口
dbfilename dump6379.rdb //持久化文件名
cluster-enabled yes // 打开集群模式
cluster-config-file nodes-6379.conf //设定节点配置文件名
cluster-node-timeout 15000 //设定节点失联时间,超过该时间(毫秒),集群自动进行主从切换。
protected-mode no // 关闭保护模式 可以免密码登陆 可选
masterauth 111111 // 设置集群节点间访问密码 没有设置密码的话就不需要设置
修改完配置文件后,我们需要指定刚刚修改好的配置文件启动6个Redis服务,可以执行以下命令将6个服务启动起来。
注意
:如果原本系统中就有一个占用6379服务的Redsi实例需要先执行下面的命令关闭再使用后面的命令启动
redis-cli -p 6379
auth 331213 // Redis密码
shutdown
然后我们再使用下面的命令启动Redis服务
redis-server /myredis/redis6379.conf
redis-server /myredis/redis6380.conf
redis-server /myredis/redis6381.conf
redis-server /myredis/redis6382.conf
redis-server /myredis/redis6383.conf
redis-server /myredis/redis6384.conf
执行完后可以执行以下命令查看服务启动是否成功
ps -ef | grep redis
组合之前,请确保所有redis实例启动后,nodes-xxxx.conf文件都生成正常。
正常启动后都会在启动目录生成一个nodes-xxxx.conf 文件
在合并集群之前我们首先要进入到Redis安装目录下的 src 目录下
注:如果是比较老的Redis版本还需要安装一个环境。
cd /usr/src/redis-6.2.6/src
这个命令集成了那个环境,所以我们需要使用到它,所以我们得在src目录下执行后续的命令。
紧接着我们执行以下命令合并启动的服务成为一个集群
redis-cli --cluster create --cluster-replicas 1 192.168.43.42:6379 192.168.43.42:6380 192.168.43.42:6381 192.168.43.42:6382 192.168.43.42:6383 192.168.43.42:6384 -a 331213
–replicas 1 采用最简单的方式配置集群,一台主机,一台从机,正好三组。
注意
:此处不要用127.0.0.1, 请用真实IP地址
-a 331213 Redis服务密码 (设置了密码就要指定否则报错)
然后会出现下面的页面
输入 yes
然后回车即可,这是询问是否同意它的分配,可以看到 6379 6380 6381 被分配为主服务器,下面是分配的从服务器,输入yes回车后可以看到以下界面,证明集群已经完成搭建了。
All 16384 slots covered. 这个的意思是请参考下文
集群的连接和普通Redis服务的连接方式不同,集群要使用 -c 参数,示例如下
redis-cli -c -p 6379 // 连接6379端口Redis服务
用任何一个节点连接都是可以的,然后我们可以使用以下命令查看集群的信息,使用前需要权限验证一下。
auth 331213 // Redis密码
cluster nodes
myself,master 是当前连接的Redsi服务
一个集群至少要有三个主节点。
选项 --cluster-replicas 1 表示我们希望为集群中的每个主节点创建一个从节点。
分配原则尽量保证每个主数据库运行在不同的IP地址,每个从库和主库不在一个IP地址上。
一个 Redis 集群包含 16384 个插槽(hash slot), 数据库中的每个键都属于这 16384 个插槽的其中一个,
集群使用公式 CRC16(key) % 16384 来计算键 key 属于哪个槽, 其中 CRC16(key) 语句用于计算键 key 的 CRC16 校验和 。
集群中的每个节点负责处理一部分插槽。 举个例子, 如果一个集群可以有主节点, 其中:
节点 A 负责处理 0 号至 5460 号插槽。
节点 B 负责处理 5461 号至 10922 号插槽。
节点 C 负责处理 10923 号至 16383 号插槽。
也可以使用上面的 cluster nodes 命令查看服务占用的插槽范围
当我们插入一条数据时,首先会计算键的插槽值所在范围,再将其存入对应范围的Redis服务,如果插入的值不在当前服务的插槽范围内,则会自动跳转到对应范围的插槽服务器进行存储。
但是注意 集群不能同时直接加入多个值
。会出现 (error)CROSSSLOT Keysin requestdon’t hashto thesameslot的错误
可以通过{}来定义组的概念,从而使key中{}内相同内容的键值对放到一个slot中去。{}中是组的名字
如果主节点下线?从节点能否自动升为主节点?注意
:15秒超时
比如在6381中执行以下的命令停止当前占用6381的主节点
shutdown
然后我们连接6380端口的Redis服务
redis-cli -c -p 6380
再使用命令查看集群状态
auth 331213 // Redis密码
cluster nodes
可以看到6381已经挂掉了,但是还是有3台主机证明有从机上位了
这时如果再启动6381 那么6381会成为新晋主机的从机,这就是集群故障恢复的特点
如果所有某一段插槽的主从节点都宕掉,redis服务是否还能继续?
如果某一段插槽的主从都挂掉,而cluster-require-full-coverage 为yes ,那么 ,整个集群都挂掉
如果某一段插槽的主从都挂掉,而cluster-require-full-coverage 为no ,那么,该插槽数据全都不能使用,也无法存储。
redis.conf中的参数 cluster-require-full-coverage
/**
* 演示redis集群操作
*/
public class RedisClusterDemo {
public static void main(String[] args) {
//创建对象
HostAndPort hostAndPort = new HostAndPort("192.168.44.168", 6379);
JedisCluster jedisCluster = new JedisCluster(hostAndPort);
//进行操作
jedisCluster.set("b1","value1");
String value = jedisCluster.get("b1");
System.out.println("value: "+value);
jedisCluster.close();
}
}