SpringCloud-(五)Redis分布式环境的搭建

本文最后更新于:May 13, 2023 pm

Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。

目录

测试以本地搭建为例,服务器上同理。测试版本 7.0.4 。

下载地址:https://github.com/redis/redis/releases

开启多个Redis,直接复制即可。只要把端口号进行更改,然后分别开启即可。本文是以本地测试为例,所以,主要以端口号进行区分。

安装

进入redis项目:

1
2
3
make  # 编译
make install # 安装
make test

卸载Redis。⚠️非必要不要使用:

1
sudo apt-get autoremove --purge redis-server

在执行 make test 报错如下(一般在执行了上面的卸载命令后会出现,啊在阿里云的服务器上也会出现):You need tcl 8.5 or newer in order to run the Redis test
make: [test] Error 1

安装TCL,并进入unix目录执行命令:

1
2
3
4
5
6
wget http://downloads.sourceforge.net/tcl/tcl8.6.1-src.tar.gz
tar xzvf tcl8.6.1-src.tar.gz -C /usr/local/
cd /usr/local/tcl8.6.1/unix/
./configure
make
make install

修改配置

修改redis.conf文件,常见修改参数如下:(根据需求进行修改)

  • 后台运行:redis默认是前台启动。将daemonize no,修改成yes即可。
  • 修改端口:port 6379。
  • 远程连接:将 bind 127.0.0.1 -::1 注释掉,并将 protected-mode yes 修改为 no 即可。
  • 修改密码:找到requirepass这个参数,修改为自己的密码即可。
  • 选举权重:找到 replica-priority。越小优先级越高。被推举为主节点的概率越高。
  • 开启集群模式:找到 cluster-enabled yes,并将注释去掉。
  • 增加设置集群密码:masterauth password。在Java中连接集群时用到。

常用命令

启动Redis

1
redis-server # 默认端口

启动指定位置的Redis,实现集群效果:

1
redis-server /Users/master/redis.conf 

启动命令窗口

1
redis-cli

连接远程服务器启动:

redis-cli -h host -p port -a password

如没有设置密码可直接执行:

1
redis-cli -h 127.0.0.1 -p 6001 # 启动指定端口的redis,若指定端口没有开启redis则会启动失败

或者:

1
redis-cli -p 6001

都可以进入Redis命令窗口。

关闭Redis

1
2
redis-cli shutdown
sudo pkill redis-server # 或者这种方式

实现主从复制

开启两个Redis实例,端口分别为:6001、6002。

查看Redis主从状态

1
info replication

结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
127.0.0.1:6001> info replication
# Replication
role:master
connected_slaves:0
master_failover_state:no-failover
master_replid:7fa7bdd9463d3b34dd35229eb0ddebb434770c6a
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0

其中:

  • role:表示当前的Redis是主节点还是从节点。Redis启动默认为主节点(master)。
  • connected_slaves:表示从节点个数。只有是主节点(master)时才会有从节点的个数。

设置主从关系

在端口号为6002的Redis中执行命令:

1
REPLICAOF 127.0.0.1 6001

成功执行后,端口号为6002的Redis会将端口号为6001的Redis作为主节点。即该命令的作用是:认贼作父。

然后再次查看主从状态。

1
info replication

会发现端口号为6002的Redis的role变成了slave。表示6002现在已经是从节点了,而它的主节点就是6001。并且,如果你在同步前就往6001中存储了数据,那么在同步后,6002中一样也会有。可以获取key进行查看。

⚠️注意:默认情况,从节点只有读的权限,没有写的权限。即使主节点挂了,从节点依然可以进行读取数据。

取消主从关系

在从节点中执行一下命令,即可解除主从关系:

1
replicaof no one

然后再次查看主从状态。

1
info replication

会发现端口号为6002的Redis的role变成了master。并且,之前同步的数据依然存在。

固定主从

还可以通过修改配置文件的方式实现主从关系。即,将设置主从关系的命令添加的配置文件 redis.conf 中去。

1
REPLICAOF 127.0.0.1 6001

扩展

  • 除了作为master的从节点之外,还可以成为从节点的从节点。
  • 当主节点挂掉后,从节点将会一直进行尝试重连,直到主节点上线并且和主节点连通。

设置哨兵

多增加一个从节点,从而实现:一主二从三哨兵。

哨兵也是直接将Redis的文件进行复制一份,然后删除配置文件 redis.conf 的全部内容。再添加如下内容:

格式:sentinel monitor 自定义主节点名称 主节点地址 主节点端口 同意的哨兵数

1
2
sentinel monitor ttf 127.0.0.1 6001 2
port 20001 # 设置哨兵的端口,每个哨兵要不一样

同样的操作,再重复两次,上述内容一样。实现三哨兵效果。只要三个哨兵中有两个哨兵对某一从节点投票,则该从节点成为主节点。(在主节点挂掉的前提下)

启动哨兵

1
2
3
redis-server /Users/sentinel-1/redis.conf --sentinel
redis-server /Users/sentinel-2/redis.conf --sentinel
redis-server /Users/sentinel-3/redis.conf --sentinel

然后再关闭6001服务和redis-cli窗口。可以查看哨兵中切换了主节点。

Java操作Redis

在哨兵重新选举新主节点后,在Java中需要感知更新。

导入依赖

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.2.1</version>
</dependency>

插入数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
void testJ(){
HashSet<String> objects = new HashSet<>(); // 集群哨兵地址和端口
objects.add("127.0.0.1:20001");
objects.add("127.0.0.1:20002");
objects.add("127.0.0.1:20003");

try (JedisSentinelPool pool = new JedisSentinelPool("ttf", objects)) {
Jedis resource = pool.getResource(); // 得到master节点
resource.set("java", "ttf");

} catch (Exception e) {
e.printStackTrace();
}
}

集群搭建

单体内存的容量是定的,是没办法进行扩展的。如果还需要进行存储数据,可以让N台Redis来存储数据,每台Redis存储一部分(1/N),这样就实现了容量的横向扩容。同时,每台Redis还可以配一个从节点,这样就可以更好的保证数据的安全性。

但是,用户插入数据时,怎么确定插入到哪一个节点服务器上呢?这就和集群机制有关:

  • 一个Redis集群包含16384个插槽(0-16383),集群中的每个Redis实例负责维护一部分插槽以及插槽所映射的键值数据。即将数据进行分散存储。
  • 插槽:即键的Hash值。采用CRC16(CRC循环冗余校验),能得到16个bit位的数据,即0-65535之间,再进行取模得到最后的结果。
  • 计算方式:slot = CRC16(key)%16384。结果即为应该存放到对应维护的Redis下。如:Redis节点1号负责0-25565的插槽。加入插入的数据a=10,a在经过hash后结果为777,那么a就应该存放到Redis节点1号中。

搭建如图集群:(注意修改配置文件里面的开启集群)

注意点

修改方式见上面的 修改配置 节。

  • 修改并开启集群模式。

  • ⚠️重点:要使用以下方式分别启动每一个实例:

    1
    2
    cd /Users/master # cd进对应的文件夹目录
    redis-server redis.conf # 再启动

搭建

将下载下来的Redis-7.0.4.zip 进行解压,并复制6分,分别命名为:master-1(6001)、master-2(6002)、master-3(6003)、salve-1(7001)、salve-2(7002)、salve-3(7003)。

然后分别去启动:

1
2
cd /Users/master # cd进对应的文件夹目录
redis-server redis.conf # 再启动

再随便进一个实例目录中(不是redis-cli中),执行命令:

1
redis-cli --cluster create 127.0.0.1:6001 127.0.0.1:6002 127.0.0.1:6003 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 --cluster-replicas 1
  • –cluster-replicas x:表示为每一个主节点分配多少个从节点。
  • 如果集群设置有密码,则还需在后面加上 -a password。密码是requirepass的值。(集群所有的密码均需设置为一样的)

⚠️注意:再执行完上述命令后,会让你输入 yes 以确定主动分配的方案,但是这里不能只输入y,必须输入完整的yes。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 127.0.0.1:7002 to 127.0.0.1:6001
Adding replica 127.0.0.1:7003 to 127.0.0.1:6002
Adding replica 127.0.0.1:7001 to 127.0.0.1:6003

>>> Trying to optimize slaves allocation for anti-affinity
[WARNING] Some slaves are in the same host as their master
M: 3c6742aea06d7c332ea9ab58fe76cf9cb2c2028d 127.0.0.1:6001
slots:[0-5460] (5461 slots) master
M: 8b29c8b87fa6bf6e0a732b1a98768ae6887f5944 127.0.0.1:6002
slots:[5461-10922] (5462 slots) master
M: 777d4f29a2f7615593849b92dfcfd67501ed9b52 127.0.0.1:6003
slots:[10923-16383] (5461 slots) master

S: 4eab9b4f0aad5a531fbbcfb002497ea3bf771976 127.0.0.1:7001
replicates 3c6742aea06d7c332ea9ab58fe76cf9cb2c2028d
S: 1ac3c0186097a207c18d75fed21717d7c1eaa42e 127.0.0.1:7002
replicates 8b29c8b87fa6bf6e0a732b1a98768ae6887f5944
S: 31e0e7b208b4996b241fffe5af66cde9bb788534 127.0.0.1:7003
replicates 777d4f29a2f7615593849b92dfcfd67501ed9b52
Can I set the above configuration? (type 'yes' to accept): yes # 输入的是yes,而不是y
  • 在输入完命令后,会展示如上。包含了每一个主节点分配的插槽,以及主从之间的关系。
  • 左侧的M表示主节点(master),S表示从节点(salve)。
  • 下面的 replicates 即该从节点的主节点是谁。

可能遇到的问题

  • 修改开启 cluster-config-file nodes.conf。nodes替换为唯一名称即可(如对应的端口号)。否则报错如下:

    1
    Sorry, the cluster configuration file nodes.conf is already used by a different Redis Cluster node. Please make sure that different nodes use different cluster configuration files.

测试使用

随便进入一个redis-cli窗口进行数据的插入。

1
2
3
☁  savle-3  redis-cli -p 6003
127.0.0.1:6003> set z 100
(error) MOVED 8157 127.0.0.1:6002

(不同的环境同样的值可能会有不一样的效果,可多插入几个值进行测试)

发现并没有插入成功。而是提示我们切换到6002去。这主要是因为z在经过hash和取模后得到的结果为8157,而在上面的分配方案中8157是归属于6002端口维护的,所以需要到6002中去插入。

当然,我们不可能插入数据时还要去算一下Key的结果然后再切换到对应的redis中去插入。所以,启动时我们选择以集群的方式启动即可,如下:

1
2
3
4
5
☁  savle-3  redis-cli -p 6003 -c
127.0.0.1:6003> set z 100
-> Redirected to slot [8157] located at 127.0.0.1:6002
OK
127.0.0.1:6002>

可以发现,已经插入成功了。是因为自动帮我们进行了重定向,可以看最后一行,已经帮我们主动切换了端口。插入也是同样的道理,会给我们自动进行计算和切换。

查看集群信息

在redis-cli中执行命令:

1
cluster nodes

结果如下:

1
2
3
4
5
6
7
8
9
10
127.0.0.1:6003> cluster nodes

4eab9b4f0aad5a531fbbcfb002497ea3bf771976 127.0.0.1:7001@17001 slave 777d4f29a2f7615593849b92dfcfd67501ed9b52 0 1663717264000 3 connected
3c6742aea06d7c332ea9ab58fe76cf9cb2c2028d 127.0.0.1:6001@16001 master - 0 1663717262000 1 connected 0-5460
8b29c8b87fa6bf6e0a732b1a98768ae6887f5944 127.0.0.1:6002@16002 master - 0 1663717265020 2 connected 5461-10922
1ac3c0186097a207c18d75fed21717d7c1eaa42e 127.0.0.1:7002@17002 slave 3c6742aea06d7c332ea9ab58fe76cf9cb2c2028d 0 1663717266026 1 connected
777d4f29a2f7615593849b92dfcfd67501ed9b52 127.0.0.1:6003@16003 myself,master - 0 1663717264000 3 connected 10923-16383
31e0e7b208b4996b241fffe5af66cde9bb788534 127.0.0.1:7003@17003 slave 8b29c8b87fa6bf6e0a732b1a98768ae6887f5944 0 1663717265000 2 connected

127.0.0.1:6003>

可以看见谁是主、谁是从节点。现在我们将6001关掉。再次查看集群信息:

1
2
3
4
5
6
7
8
9
10
127.0.0.1:6003> cluster nodes

4eab9b4f0aad5a531fbbcfb002497ea3bf771976 127.0.0.1:7001@17001 slave 777d4f29a2f7615593849b92dfcfd67501ed9b52 0 1663717615551 3 connected
3c6742aea06d7c332ea9ab58fe76cf9cb2c2028d 127.0.0.1:6001@16001 master,fail - 1663717558568 1663717551000 1 disconnected
8b29c8b87fa6bf6e0a732b1a98768ae6887f5944 127.0.0.1:6002@16002 master - 0 1663717616561 2 connected 5461-10922
1ac3c0186097a207c18d75fed21717d7c1eaa42e 127.0.0.1:7002@17002 master - 0 1663717617580 7 connected 0-5460
777d4f29a2f7615593849b92dfcfd67501ed9b52 127.0.0.1:6003@16003 myself,master - 0 1663717616000 3 connected 10923-16383
31e0e7b208b4996b241fffe5af66cde9bb788534 127.0.0.1:7003@17003 slave 8b29c8b87fa6bf6e0a732b1a98768ae6887f5944 0 1663717615000 2 connected

127.0.0.1:6003>

可以看见6001 fail了,而7002成为了master。再次启动6001。

1
2
3
4
5
6
7
8
9
10
127.0.0.1:6003> cluster nodes

4eab9b4f0aad5a531fbbcfb002497ea3bf771976 127.0.0.1:7001@17001 slave 777d4f29a2f7615593849b92dfcfd67501ed9b52 0 1663717856000 3 connected
3c6742aea06d7c332ea9ab58fe76cf9cb2c2028d 127.0.0.1:6001@16001 slave 1ac3c0186097a207c18d75fed21717d7c1eaa42e 0 1663717857640 7 connected
8b29c8b87fa6bf6e0a732b1a98768ae6887f5944 127.0.0.1:6002@16002 master - 0 1663717856000 2 connected 5461-10922
1ac3c0186097a207c18d75fed21717d7c1eaa42e 127.0.0.1:7002@17002 master - 0 1663717856633 7 connected 0-5460
777d4f29a2f7615593849b92dfcfd67501ed9b52 127.0.0.1:6003@16003 myself,master - 0 1663717857000 3 connected 10923-16383
31e0e7b208b4996b241fffe5af66cde9bb788534 127.0.0.1:7003@17003 slave 8b29c8b87fa6bf6e0a732b1a98768ae6887f5944 0 1663717858646 2 connected

127.0.0.1:6003>

可以看见6001已经是从节点了。

当一主一从都挂掉时,则数据将无法进行插入。

Java使用

依赖和上面之前的一样。

1
2
3
4
5
6
7
8
9
@Test
void testJp(){
// 和客户端一样,随便连一个即可,也可以写多个,构造方法有多种
try(JedisCluster jedisCluster = new JedisCluster(new HostAndPort("127.0.0.1", 6001))) {
System.out.println("集群数量:"+jedisCluster.getClusterNodes().size());
jedisCluster.set("cluster","22222");
System.out.println(jedisCluster.get("cluster"));
}
}

输出:

1
2
集群数量:6
22222

服务器部署问题

在使用多台服务器进行搭建时,会出现程序发生阻塞,一直等待节点加入的问题(Waiting for the cluster to join),如下:

1
2
3
4
5
6
Can I set the above configuration? (type 'yes' to accept): yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join
..........................................
  • 造成这种的原因:遇到这种情况大部分是因为集群总线的端口没有开放!

集群总线

每个Redis集群中的节点都需要打开两个TCP连接。一个连接用于正常的给Client提供服务,比如6379,还有一个额外的端口(通过在这个端口号上加10000)作为数据端口,例如:redis的端口为6379,那么另外一个需要开通的端口是:6379 + 10000, 即需要开启 16379。16379端口用于集群总线,这是一个用二进制协议的点对点通信信道。这个集群总线(Cluster bus)用于节点的失败侦测、配置更新、故障转移授权,等等。

  • 解决办法:只需要将开启Redis端口对应的 集群总线端口即可。例如: 6379 + 10000 = 16379。所以开放每个集群节点的客户端端口和集群总线端口才能成功创建集群!

IP解析问题

搭建集群时,nodes.conf文件中默认使用的是内网IP,需要将其手动修改为能访问的外网IP。

分布式锁

原开发者实现:当线程加锁成功,开始执行业务时,会在后台另开一个分线程。分线程中开启一个定时任务,每过一段时间就去检查一下主线程所持有的锁在Redis中是否还存在。如果还存在则会重新设置一个过期时间(覆盖。原本设置的5秒,过了2秒检查发现依旧存在,则又重新设置为5秒),定时任务的执行时间间隔不能超过重新设置的时间。为什么这样做了,这样就保证了在业务没有执行完的情况下,锁不会释放,只有业务执行完毕后锁才会被释放。而Redisson就是这种。

Redisson - 是一个高级的分布式协调Redis客服端,能帮助用户在分布式环境中轻松实现一些Java的对象,Redisson、Jedis、Lettuce 是三个不同的操作 Redis 的客户端,Jedis、Lettuce 的 API 更侧重对 Reids 数据库的 CRUD(增删改查),而 Redisson API 侧重于分布式开发。

导入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- https://mvnrepository.com/artifact/org.redisson/redisson -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.17.4</version>
</dependency>

<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.2.1</version>
</dependency>

启动两个Redis实例,一台作为数据存储(6379),一台用作分布锁(6380)。

测试使用

⚠️注意:这里必须是在main方法中执行,否则无法实现效果。主要原因可能在于创建线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package com.tothefor;

import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import redis.clients.jedis.Jedis;

/**
* @Author DragonOne
* @Date 2022/9/21 23:40
* @Title
* @Description
*/
public class JEMain {

public static void main(String[] args) {
Config config = new Config();
//配置连接的Redis分布式锁的实例,也可以指定集群
config.useSingleServer().setAddress("redis://127.0.0.1:6380");
//创建RedissonClient客户端
RedissonClient client = Redisson.create(config);
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try(Jedis jedis = new Jedis("127.0.0.1", 6379)){
RLock lock = client.getLock("testLock"); //指定锁的名称,拿到锁对象
for (int j = 0; j < 100; j++) {
lock.lock(); //加锁
int a = Integer.parseInt(jedis.get("a")) + 1;
jedis.set("a", a+"");
lock.unlock(); //解锁
}
}
System.out.println("结束!");
}).start();
}
}
}

也可以使用另外一种方法操作Redis数据,可参考:《SpringBoot-(二十四)SpringBoot简单整合操作Redis 》

其他依赖参考

1
2
3
4
5
6
7
8
9
10
11
<!-- https://mvnrepository.com/artifact/org.redisson/redisson-spring-boot-starter -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.17.0</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>