学习笔记—分布式缓存(3)—Redis分片集群

分片集群

  有了主从、有了哨兵,解决了高可用和高并发读的问题。但是,即便如此,仍然有两个问题没有解决。

  第一,是海量数据的储存问题

  第二,是高并发写的问题

  那么,使用分片集群,便可以解决这样的两个问题。

  对于一个分片集群来说,集群中有多个master,每个master保存不一样的数据,对于每个master来说,有多个slave节点。

  master之间可以通过ping来监测彼此的健康状态。

  客户端发出的对于redis的请求,可以访问集群的任意节点,最终,都会被转发到正确的节点中去。

  对于一个分片集群来说,一个节点就是一个运行在集群模式下的Redis服务器。启动Redis服务器时,通过判断cluster-enabled选项,选择是否开启集群模式。(Yes开启集群,No则单机模式普通服务器)

1
2
3
4
5
6
7
8
#开启redis的集群模式
cluster-enabled yes

#集群内节点之间支持最长响应时间
cluster-node-timeout 15000

#配置集群模式下的配置文件
cluster-config-file nodes-6379.conf

  每个节点使用的端口各不相同,可以设置。每个节点最开始可以看做一个只有自己节点的集群,节点间通过命令相互握手,组建集群。

  握手命令如下:

1
2
3
4
5
# 和ip为127.0.0.1,端口为6378的节点进行握手
cluster meet 127.0.0.1:6378

# 显示当前集群的节点信息
cluster nodes

分片集群的搭建与使用

  此处还是采用docker的方式来进行演示分片集群的搭建与使用。

  首先,还是使用redis:bullseye的镜像。

1
docker pull redis:bullseye

  而后,于此以不挂载的方式进行搭建,挂载的方式其实也是一样的。

  第一步是创建一个network网络:

1
2
3
4
docker network create redis-cluster-network

# 可以查看network的列表
docker network ls

  Redis分片集群至少需要6个Redis实例,3个为主节点,3个为副本节点。

1
2
3
4
5
6
docker run -d --name redis-node-1 --net redis-cluster-network redis:bullseye redis-server --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000 --appendonly yes --port 7001
docker run -d --name redis-node-2 --net redis-cluster-network redis:bullseye redis-server --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000 --appendonly yes --port 7002
docker run -d --name redis-node-3 --net redis-cluster-network redis:bullseye redis-server --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000 --appendonly yes --port 7003
docker run -d --name redis-node-4 --net redis-cluster-network redis:bullseye redis-server --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000 --appendonly yes --port 7004
docker run -d --name redis-node-5 --net redis-cluster-network redis:bullseye redis-server --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000 --appendonly yes --port 7005
docker run -d --name redis-node-6 --net redis-cluster-network redis:bullseye redis-server --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000 --appendonly yes --port 7006

  接着可以检查redis的实例是否全部启动成功:

1
docker ps

  随后,集群需要使用IP地址来进行节点间通信,可以使用以下命令来获得每个Redis实例的IP地址:

1
2
3
4
5
6
docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' redis-node-1
docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' redis-node-2
docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' redis-node-3
docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' redis-node-4
docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' redis-node-5
docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' redis-node-6

  于此,得到了几个redis实例的IP地址:

1
2
3
4
5
6
172.24.0.2
172.24.0.3
172.24.0.4
172.24.0.5
172.24.0.6
172.24.0.7

  基于此,可以开始启动集群了:

1
2
3
4
5
6
7
8
docker run -it --rm --net redis-cluster-network redis:bullseye redis-cli --cluster create \
172.24.0.2:7001 \
172.24.0.3:7002 \
172.24.0.4:7003 \
172.24.0.5:7004 \
172.24.0.6:7005 \
172.24.0.7:7006 \
--cluster-replicas 1

  Port就是上面启动时使用的Port,此命令将会创建一个有三个主节点、三个从节点的Redis集群,其中 –cluster-replicas 1 表示每个主节点都会有一个副本节点。启动的时候显示如下:

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
40
41
42
43
44
45
46
47
48
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 172.24.0.6:7005 to 172.24.0.2:7001
Adding replica 172.24.0.7:7006 to 172.24.0.3:7002
Adding replica 172.24.0.5:7004 to 172.24.0.4:7003
M: a85caca5fada00aa43bc3ff7baf47e8eda13c814 172.24.0.2:7001
slots:[0-5460] (5461 slots) master
M: dae07fbbbf0e8ba7c9959408ac931d70deaaef0b 172.24.0.3:7002
slots:[5461-10922] (5462 slots) master
M: 43e2c65d9be8750131a631b6fe79a19bdb6deaee 172.24.0.4:7003
slots:[10923-16383] (5461 slots) master
S: a044956680f88d49209b54aaa282279c2b955679 172.24.0.5:7004
replicates 43e2c65d9be8750131a631b6fe79a19bdb6deaee
S: 71f225cb0e495187062782dc668b3b9e2d1e6fcd 172.24.0.6:7005
replicates a85caca5fada00aa43bc3ff7baf47e8eda13c814
S: 7108324df122bc424d6f5c0502311e4b7f19701f 172.24.0.7:7006
replicates dae07fbbbf0e8ba7c9959408ac931d70deaaef0b
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
..
>>> Performing Cluster Check (using node 172.24.0.2:7001)
M: a85caca5fada00aa43bc3ff7baf47e8eda13c814 172.24.0.2:7001
slots:[0-5460] (5461 slots) master
1 additional replica(s)
M: 43e2c65d9be8750131a631b6fe79a19bdb6deaee 172.24.0.4:7003
slots:[10923-16383] (5461 slots) master
1 additional replica(s)
S: a044956680f88d49209b54aaa282279c2b955679 172.24.0.5:7004
slots: (0 slots) slave
replicates 43e2c65d9be8750131a631b6fe79a19bdb6deaee
M: dae07fbbbf0e8ba7c9959408ac931d70deaaef0b 172.24.0.3:7002
slots:[5461-10922] (5462 slots) master
1 additional replica(s)
S: 71f225cb0e495187062782dc668b3b9e2d1e6fcd 172.24.0.6:7005
slots: (0 slots) slave
replicates a85caca5fada00aa43bc3ff7baf47e8eda13c814
S: 7108324df122bc424d6f5c0502311e4b7f19701f 172.24.0.7:7006
slots: (0 slots) slave
replicates dae07fbbbf0e8ba7c9959408ac931d70deaaef0b
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

  搭建好后的Redis分片集群如下:

Redis分片集群
  在集群创建成功后,使用以下命令查看集群状态:

1
docker exec -it redis-node-1 redis-cli -p 7001 cluster nodes
1
2
3
4
5
6
43e2c65d9be8750131a631b6fe79a19bdb6deaee 172.24.0.4:7003@17003 master - 0 1727075791763 3 connected 10923-16383
a044956680f88d49209b54aaa282279c2b955679 172.24.0.5:7004@17004 slave 43e2c65d9be8750131a631b6fe79a19bdb6deaee 0 1727075792766 3 connected
dae07fbbbf0e8ba7c9959408ac931d70deaaef0b 172.24.0.3:7002@17002 master - 0 1727075791000 2 connected 5461-10922
71f225cb0e495187062782dc668b3b9e2d1e6fcd 172.24.0.6:7005@17005 slave a85caca5fada00aa43bc3ff7baf47e8eda13c814 0 1727075790558 1 connected
7108324df122bc424d6f5c0502311e4b7f19701f 172.24.0.7:7006@17006 slave dae07fbbbf0e8ba7c9959408ac931d70deaaef0b 0 1727075791000 2 connected
a85caca5fada00aa43bc3ff7baf47e8eda13c814 172.24.0.2:7001@17001 myself,master - 0 1727075791000 1 connected 0-5460

  或者也可通过以下命令查看集群状态:

1
docker exec -it redis-node-1 redis-cli -p 7001 cluster info
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:6
cluster_my_epoch:1
cluster_stats_messages_ping_sent:37
cluster_stats_messages_pong_sent:39
cluster_stats_messages_sent:76
cluster_stats_messages_ping_received:34
cluster_stats_messages_pong_received:37
cluster_stats_messages_meet_received:5
cluster_stats_messages_received:76

  在分片集群搭建完毕后,如果说,采用原始的方式进入分片集群的一个节点,进行设置。比如,通过redis-cli 命令连接到集群中的任意节点,插入数据并验证是否自动分片。

1
docker exec -it redis-node-1 redis-cli -p 7001

  会出现如下显示:

1
2
3
4
127.0.0.1:7001> set key1 "value1"
(error) MOVED 9189 172.24.0.3:7002
127.0.0.1:7001> get key1
(error) MOVED 9189 172.24.0.3:7002

  出现 (error) MOVED 错误是因为 Redis 集群将键分片到不同的主节点上,Redis 集群使用哈希槽(hash slots)将键映射到特定的节点。当你直接连接到某个节点时,如果该键不属于这个节点,Redis 会返回 MOVED 错误,并告知你正确的主节点地址。

  解决方法是通过 redis-cli 的集群模式连接,这样 redis-cli 会自动跟踪集群中的正确节点,不需要手动处理 MOVED 错误。在运行redis-cli的时候加上-c的选项。

1
docker exec -it redis-node-1 redis-cli -p 7001 -c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
127.0.0.1:7001> set key1 1099
-> Redirected to slot [9189] located at 172.24.0.3:7002
OK
172.24.0.3:7002> get key1
"1099"
172.24.0.3:7002> set key1 "gagaduck"
OK
172.24.0.3:7002> get key1
"gagaduck"
172.24.0.3:7002> set test_key "gagaduck"
-> Redirected to slot [15118] located at 172.24.0.4:7003
OK
172.24.0.4:7003> get test_key
"gagaduck"

  此时,Redis CLI 应该自动处理键的分片,并且不会再出现 MOVED 错误。

客户端连接Redis分片集群

springboot连接分片Redis集群

  首先,要确保pom.xml中添加有redis的依赖:

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

  随后,配置集群信息:

1
2
3
spring.data.redis.cluster.nodes=172.24.0.2:7001,172.24.0.3:7002,172.24.0.4:7003,172.24.0.5:7004,172.24.0.6:7005,172.24.0.7:7006
spring.data.redis.password=
spring.data.redis.timeout=2000

  spring.redis.cluster.nodes 列出所有Redis集群节点的IP和端口,Spring Boot将会连接这些节点,并通过Redis集群协议获取集群的信息和哈希槽分片。

  spring.redis.timeout 设置连接的超时时间。

  Spring Boot支持两种主要的Redis客户端:Lettuce 和 Jedis。这两者都支持Redis分片集群。默认情况下,Spring Boot使用 Lettuce,它是非阻塞的,性能更好。这里也采用Lettuce作为客户端为例。

  对客户端进行配置:

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
@Configuration
public class RedisClusterConfig {

@Bean
public LettuceConnectionFactory redisConnectionFactory() {
// 配置Redis集群节点
RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration(
Arrays.asList("172.24.0.2:7001", "172.24.0.3:7002", "172.24.0.4:7003",
"172.24.0.5:7004", "172.24.0.6:7005", "172.24.0.7:7006"));

// 配置拓扑刷新选项
ClusterTopologyRefreshOptions refreshOptions = ClusterTopologyRefreshOptions.builder()
.enablePeriodicRefresh(Duration.ofMinutes(10)) // 每10分钟刷新集群拓扑
.enableAllAdaptiveRefreshTriggers() // 启用自适应刷新
.build();

// 配置集群客户端选项
ClusterClientOptions clusterClientOptions = ClusterClientOptions.builder()
.topologyRefreshOptions(refreshOptions)
.build();

// 配置 Lettuce 客户端配置
LettuceClientConfiguration clientConfiguration = LettuceClientConfiguration.builder()
.clientOptions(clusterClientOptions)
.build();

// 创建 Lettuce 连接工厂
return new LettuceConnectionFactory(redisClusterConfiguration, clientConfiguration);
}
}

  正确配置好了之后,便可以通过 RedisTemplate 或 StringRedisTemplate 来操作Redis数据。Spring Boot会根据键的哈希值自动将请求路由到正确的Redis节点。

1
2
stringRedisTemplate.opsForValue().set("key1", "value1");
stringRedisTemplate.opsForValue().get("key1");

  具体可以参考https://github.com/gagaducko/learning_demos/tree/main/distributed-redis-example

  如果需要显示集群的一些信息等等情况,也可以采用RedisConnectionFactory来进行操作:

1
2
@Autowired
private RedisConnectionFactory redisConnectionFactory;

  当进行请求时,如/clusterInfo便可以获得cluster的一些信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Cluster Info:
Node ID: a044956680f88d49209b54aaa282279c2b955679
Address: 172.24.0.5
Master: false
Slots: []
-----------------------
Node ID: 71f225cb0e495187062782dc668b3b9e2d1e6fcd
Address: 172.24.0.6
Master: false
Slots: []
-----------------------
Node ID: dae07fbbbf0e8ba7c9959408ac931d70deaaef0b
Address: 172.24.0.3
Master: true
Slots: ……
……

  当setKey时:

1
Key 'key1' set to 'value1'

  当getKey时:

1
Value for 'key1': value1

python连接分片Redis集群

  首先,需要安装依赖:

1
pip install redis

  而后配置集群节点:

1
2
3
4
5
6
7
8
startup_nodes = [
ClusterNode("172.24.0.2", 7001),
ClusterNode("172.24.0.3", 7002),
ClusterNode("172.24.0.4", 7003),
ClusterNode("172.24.0.5", 7004),
ClusterNode("172.24.0.6", 7005),
ClusterNode("172.24.0.7", 7006)
]

  最后创建redis连接:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
try:
# 使用 startup_nodes 正确连接 Redis 集群
redis_client = redis.RedisCluster(startup_nodes=startup_nodes, decode_responses=True)

# 设置键值对
redis_client.set("name", "Python Redis Cluster Example")

# 获取键值
value = redis_client.get("name")
print(f"Stored value: {value}")

except RedisError as e:
print(f"Error connecting to Redis Cluster: {e}")
except Exception as e:
print(f"An unexpected error occurred: {e}")

  运行后输出:

1
Stored value: Python Redis Cluster Example

  详细可见https://github.com/gagaducko/learning_demos/tree/main/distributed_redis_example_py


学习笔记—分布式缓存(3)—Redis分片集群
https://gagaducko.github.io/2024/09/23/学习笔记—分布式缓存-3-—Redis分片集群/
作者
gagaduck
发布于
2024年9月23日
许可协议