本章概要
- 整合 Redis 集群
6.1.4 Redis 集群整合 Spring Boot
1. 搭建 Redis 集群
(1)集群原理
在 Redis 集群中,所有 Redis 节点彼此互联,节点内部使用二进制协议优化传输速度和宽带。当一个节点挂掉后,集群中超过半数的节点检测失效时才认为该节点已经失效。不同于 Tomcat 集群需要使用反向代理服务器,Redis 集群中的任意节点都可以直接和 Java 客户端连接。Redis 集群上的数据分配则是采用哈希槽(HASH SLOT),Redis 集群中内置了 16384 个哈希槽,当有数据需要存储时,Redis 会首先使用 CRC16 算法对 key 进行计算,将计算获得的结果对 16384 取余,这样每一个 key 都会对应一个取值在 0~16383 之间的哈希槽,Redis 根据这个余数将该条数据存储到对应的 Redis 节点上,开发者可根据每个 Redis 实例的性能来调整每个 Redis 实例上哈希槽的分布范围。
(2)集群规划
在同一台服务器上用不同的端口标识不同的 Redis 服务器(伪分布式集群)
主节点:ip:8001,ip:8002,ip:8003
从节点:ip:8004,ip:8005,ip:8006
(3)集群配置
Redis 集群管理工具 redis-trib.rb 依赖 Ruby 环境,首先需要安装 Ruby 环境,由于 Centos 7 yum 库中默认的 Ruby 版本较低,因此建议采用如下步骤进行安装。
安装基本工具
yum -y install ruby ruby-devel rubygems rpm-build
查看当前版本
ruby -v
安装yum 源
yum install -y centos-release-scl-rh
安装指定版本 ruby
yum install -y rh-ruby24
使升级后的配置生效
scl enable rh-ruby24 bash
查看当前 ruby 版本
ruby -v
最后安装 Redis 依赖
yum install gem -y
gem install redis
创建 redisCluster 文件夹,将Redis压缩文件(详见CentOS 安装 Redis)复制到redisCluster 目录下,然后编译安装
mkdir /opt/soft/redisCluster
cp /opt/package/redis-6.2.6.tar.gz /opt/soft/redisCluster
tar -zxvf /opt/package/redis-6.2.6.tar.gz -C /opt/soft/redisCluster/
cd /opt/soft/redisCluster/redis-6.2.6
make PREFIX=/opt/soft/redisCluster/redis-6.2.6 install
安装成功后,将redis-6.2.6/src 目录下的 redis-trib.rb 文件复制到 /opt/soft/redisCluster/ 目录下(Redis版本较高,此步骤可以跳过,后面不用 redis-trib.rb 而是使用 redis-cli)
cp /opt/soft/redisCluster/redis-6.2.6/src/redis-trib.rb /opt/soft/redisCluster/
在 redisCluster 文件夹下创建6个文件夹,分别为 8001,8002,8003,8004,8005,8006,再将 redis.config 文件分别往这6个目录中复制一份
mkdir /opt/soft/redisCluster/8001/
mkdir /opt/soft/redisCluster/8002/
mkdir /opt/soft/redisCluster/8003/
mkdir /opt/soft/redisCluster/8004/
mkdir /opt/soft/redisCluster/8005/
mkdir /opt/soft/redisCluster/8006/
cp /opt/soft/redisCluster/redis-6.2.6/redis.conf /opt/soft/redisCluster/8001/
cp /opt/soft/redisCluster/redis-6.2.6/redis.conf /opt/soft/redisCluster/8002/
cp /opt/soft/redisCluster/redis-6.2.6/redis.conf /opt/soft/redisCluster/8003/
cp /opt/soft/redisCluster/redis-6.2.6/redis.conf /opt/soft/redisCluster/8004/
cp /opt/soft/redisCluster/redis-6.2.6/redis.conf /opt/soft/redisCluster/8005/
cp /opt/soft/redisCluster/redis-6.2.6/redis.conf /opt/soft/redisCluster/8006/
修改每个redis.conf 信息,,以8001目录下的为例,如下:
port 8001
# bind 127.0.0.1
cluster-enabled yes
cluster-config-file nodes-8001.conf
protected-mode no
daemonize yes
requirepass root
masterauth root
pidfile /var/run/redis_8001.pid
说明:
- 端口号为8001
- cluster-enabled yes 表示开启集群
- cluster-config-file nodes-8001.conf 表示集群节点的配置文件
- 每个节点都配置了密码,设置主节点密码 masterauth root ,使从节点可以登录主节点
- pidfile /var/run/redis_8001.pid pid 文件路径
全部修改完毕后,分别启动 6 个 Redis 实例
/opt/soft/redisCluster/redis-6.2.6/bin/redis-server /opt/soft/redisCluster/8001/redis.conf
/opt/soft/redisCluster/redis-6.2.6/bin/redis-server /opt/soft/redisCluster/8002/redis.conf
/opt/soft/redisCluster/redis-6.2.6/bin/redis-server /opt/soft/redisCluster/8003/redis.conf
/opt/soft/redisCluster/redis-6.2.6/bin/redis-server /opt/soft/redisCluster/8004/redis.conf
/opt/soft/redisCluster/redis-6.2.6/bin/redis-server /opt/soft/redisCluster/8005/redis.conf
/opt/soft/redisCluster/redis-6.2.6/bin/redis-server /opt/soft/redisCluster/8006/redis.conf
(4)创建 Redis 集群
注意:使用 ip:port ,不能使用 localhost:port。如果使用的是云服务器,此处的 ip 应该是外网 ip ,我就是在创建集群的时候用了云服务器的内网 ip 导致redis 连不上,耗费了大量时间。
/opt/soft/redisCluster/redis-6.2.6/bin/redis-cli --cluster create ip:8001 ip:8002 ip:8003 ip:8004 ip:8005 ip:8006 --cluster-replicas 1 -a root
其中,–cluster-replicas 表示每个主节点的 slave 数量。在集群的创建过程中会分配主机和从机,每个集群在创建过程中都将分配一个唯一的id 并分配到一段 slot 。
创建成功后,登录任意 Redis 实例
/opt/soft/redisCluster/redis-6.2.6/bin/redis-cli -p 8001 -a root -c
-p 表示要登录的集群的端口,-a 表示要登录的集群的密码,-c 表示以集群的方式登录。登录成功后,通过 cluster info 命令可以查询集群状态信息。
通过 cluster nodes 命令可以查询集群节点信息 ,在集群节点信息中,可以看到每一个节点的id,该节点是 slave 还是master ,如果是slave ,那么它的 master 的 id 是什么,如果是 master ,那么每一个 master 的slot 范围是多少,这些信息都会显示出来。
(5)添加主节点
当集群创建成功后,随着业务的增长,有可能需要添加主节点,添加主节点需要先构建主节点实例,将 redisCluster 目录下的 8001 目录再复制一份,名为 8007 ,根据第(3)步 的集群配置修改 8007 目录下的 redis.conf 文件,修改完后,再运行如下命令启动该节点
/opt/soft/redisCluster/redis-6.2.6/bin/redis-server /opt/soft/redisCluster/8007/redis.conf
启动成功后,将该节点添加到集群中
/opt/soft/redisCluster/redis-6.2.6/bin/redis-cli --cluster add-node ip:8007 ip:8001 -a root
登录任意一个Redis实例,查看 Redis 集群信息
/opt/soft/redisCluster/redis-6.2.6/bin/redis-cli -p 8001 -a root -c
cluster nodes
可以看到实例已经被添加进集群中,但是由于slot 已经被之前的实例分配完了,新添加的实例没有 slot ,也就意味着新添加的实例没有存储数据的机会,此时需要从另外三个实例中拿出一部分slot分配给新实例,先对 slot 重新分配(连接集群中的任意一个实例即可)
/opt/soft/redisCluster/redis-6.2.6/bin/redis-cli --cluster reshard ip:8001 -a root
在命令执行过程中有四个核心配置需要手动配置,如图
第一个配置是要拿出多少个slot分配给新的实例,此处配置了 1000 个。
第二个是把拿出来的 1000 个 slot 配置给谁,输入接收这 1000 个 slot 的 Redis 实例的 id,这个id在节点添加成功后就可以看到,也可以进入集群控制台后 利用 cluster nodes 查看。
第三个配置是这 1000 个 slot 由哪个实例出,例如从端口为 8001 的实例中拿出 1000 个 slot 分给端口 为 8007 的实例,那么这里输入 8001 的 id 后回车即可,如果想将 1000 个 slot 均摊到所有的实例中,这里输入 all 按回车即可。
第四个配置指这 1000 个 slot 已经从集群中均摊出来了,是否开始分给指定 Redis 实例,输入yes即可
再次查看节点信息,可以看到新实例也有slot 了,如图
(6)添加从节点
先将 redisCluster 目录下的 8001 目录复制一份,命名为 8008 ,然后修改相应配置 redis.conf。修改完毕后启动该实例,然后输入一下命令添加从节点:
/opt/soft/redisCluster/redis-6.2.6/bin/redis-cli --cluster add-node ip:8008 ip:8001 --cluster-slave --cluster-master-id e3fccbce09b63dab42b400e8676ffbabbe4c8eb4 -a root
说明:
- 10.0.12.5:8008 从节点的地址
- 10.0.12.5:8001 集群中的任意一个实例地址
- e3fccbce09b63dab42b400e8676ffbabbe4c8eb4 把这个从节点分配给哪个主节点
再次查看集群节点信息
(7)删除节点
如果删除的节点是从节点,则可使用以下命令直接删除
/opt/soft/redisCluster/redis-6.2.6/bin/redis-cli --cluster del-node ip:8004 73bcf87ddd65228c9e7b79fb24e24def06f28867 -a root
查看集群信息,可以看到已经删除了
如果删除主节点需要先把它的从节点重新指定到其它主节点,登录将将要删除的主节点的从节点,重新指定它的主节点
cluster replicate af130b04eecd85692fdc2f53c1bd42c8de47d843
可以看到 8005 的主节点已由 8003 变为了 8001 ,而此时 8003 下面无任何从节点。然后再把将要删除的 master 的 slot 给分配给其它主节点
/opt/soft/redisCluster/redis-6.2.6/bin/redis-cli --cluster reshard ip:8003 -a root
把所有 slot 分出去
设置接收 slot 的 id
然后输入总slot数 ,接着输入 all ,然后输入 yes
最后查看节点信息,可以发现此 master 上已经没有 slot 了
接着删除 master
/opt/soft/redisCluster/redis-6.2.6/bin/redis-cli --cluster del-node ip:8003 1131d08bc3cc448367d7f5914c63ec8638c195e0 -a root
再次查看,此 master 已经被删除了
(8)重建集群
如果是重建的的话,先关闭所有redis服务,删除以下文件,可以先通过 find / -name appendonly.aof 命令查找文件所在位置
- appendonly.aof
- dump.rdb
- nodes-*.conf(nodes-8001.conf、nodes-8002.conf…)
然后再重新创建集群即可
2.配置 Spring Boot
不同于单机版 Redis 整合 Spring Boot ,Redis 集群整合 Spring Boot 需要开发者手动配置,配置步骤如下:
(1)创建 Spring Boot 项目
首先创建一个 Spring Boot Web 项目,添加如下依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
(2)配置集群信息
由于集群节点有多个,可以保存在一个集合中,因此这里的配置文件使用YAML格式的,删除 resources 目录下的application.properties 文件,创建 application.yml 配置文件,文件内容如下:
spring:
redis:
cluster:
ports:
- 8001
- 8002
- 8003
- 8004
- 8005
- 8006
- 8007
- 8008
host:ip地址
poolConfig:
max-total:8
max-idle:8
max-wait-millis:-1
min-idle:0
由于本案例 Redis 实例的 host 都是一样的,因此这里配置了一个 host ,而 port 配置成了一个集合,这些 port 将被注入一个集合中。poolConfig 则是基本的连接池信息配置。
(3)配置 Redis
创建 RedisConfig,完成对 Redis 的配置,如下:
@Configuration
@ConfigurationProperties("spring.redis.cluster")
public class RedisConfig {
List<Integer> ports;
String host;
JedisPoolConfig poolConfig;
@Bean
RedisClusterConfiguration redisClusterConfiguration() {
RedisClusterConfiguration configuration = new RedisClusterConfiguration();
List<RedisNode> nodes = new ArrayList<>();
for (Integer port : ports) {
nodes.add(new RedisNode(host, port));
}
configuration.setPassword(RedisPassword.of("root"));
configuration.setClusterNodes(nodes);
return configuration;
}
@Bean
RedisTemplate redisTemplate() {
RedisTemplate redisTemplate = new RedisTemplate();
redisTemplate.setConnectionFactory(jedisConnectionFactory());
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
return redisTemplate;
}
@Bean
StringRedisTemplate stringRedisTemplate() {
StringRedisTemplate stringRedisTemplate = new StringRedisTemplate(jedisConnectionFactory());
stringRedisTemplate.setKeySerializer(new StringRedisSerializer());
stringRedisTemplate.setKeySerializer(new StringRedisSerializer());
return stringRedisTemplate;
}
@Bean
JedisConnectionFactory jedisConnectionFactory() {
JedisConnectionFactory factory = new JedisConnectionFactory(redisClusterConfiguration(),poolConfig);
return factory;
}
public List<Integer> getPorts() {
return ports;
}
public void setPorts(List<Integer> ports) {
this.ports = ports;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public JedisPoolConfig getPoolConfig() {
return poolConfig;
}
public void setPoolConfig(JedisPoolConfig poolConfig) {
this.poolConfig = poolConfig;
}
}
代码解释:
- 通过 @ConfigurationProperties 注解声明配置文件前缀,配置文件中定义的 ports 数组、host 以及 连接池信息都将被注入 port、host、poolConfig 三个属性中
- 配置 RedisClusterConfiguration 实例,设置 Redis 登录密码以及 Redis 节点信息
- 根据 RedisClusterConfiguration 实例以及连接池配置信息创建 Jedis 连接工厂 JedisConnectionFactory
- 根据 JedisConnectionFactory 创建 RedisTemplate 和 StringRedisTemplate,同时配置 key 和 value 的序列化方式。有了 RedisTemplate 和 StringRedisTemplate ,剩下的用法就和单实例的用法一致了
(4)创建 Controller 和 Book 实例
@RestController
public class BookController {
@Autowired
RedisTemplate redisTemplate;
@Autowired
StringRedisTemplate stringRedisTemplate;
@GetMapping("/test1")
public void test1() {
ValueOperations ops = redisTemplate.opsForValue();
Book book = new Book();
book.setName("水浒传");
book.setAuthor("施耐庵");
ops.set("b1", book);
System.out.println(ops.get("b1"));
ValueOperations<String, String> ops2 = stringRedisTemplate.opsForValue();
ops2.set("k1", "v1");
System.out.println(ops2.get("k1"));
}
}
public class Book implements Serializable {
private String name;
private String author;
@Override
public String toString() {
return "Book{" +
"name='" + name + '\'' +
", author='" + author + '\'' +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
}
(5)测试
启动项目,http://localhost:8081/test1,控制台打印日志如下:
Book{name='水浒传', author='施耐庵'}
v1
然后登录任意一个 Redis 实例,查询数据,结果如下:
从日志中可以看出,查询时只需要登录任意一个 Redis 实例, RedisCluster 会负责将查询请求 Redirected 到相应的实例上去。