搭建Redis“主-从-从”模式集群并使用 RedisTemplate 实现读写分离
一、理论相关我们知道,Redis具有高可靠性,其含义包括:
[*]数据只管少丢失 - AOF 和 RDB
[*]服务只管少中断 - 增加副本冗余量,将一份数据同时生存在多个实例上,即主从库模式
Redis主从库模式 - 保证数据副本的同等(读写分离):
[*]读操纵:主库、从库都可以吸收
[*]写操纵:起首到主库执行,然后,主库将写操纵同步给从库
https://img2024.cnblogs.com/blog/3531058/202410/3531058-20241005000023035-1274785053.png采用读写分离的原因:
[*]假如客户端对同一个数据举行多次修改,每一次的修改哀求都发送到不同的实例上,在不同的实例上执行,那么这个数据在多个实例上的副本就不同等了
[*]假如要对不同实例上的数据同等,就涉及到加锁、实例间协商是否完成修改等操纵,会带来巨额的开销
这时我们就引出主从库同步的原理。
1、主从库间如何举行第一次同步?
当我们启动多个 Redis 实例的时候,它们相互之间就可以通过 replicaof(Redis 5.0 之前使用 slaveof)命令形成主库和从库的关系,之后会按照三个阶段完成数据的第一次同步。
[*]主从库创建连接、协商同步,为全量复制做准备
replicaof 172.16.19.3 6379
[*]从库和主库创建连接,并告诉主库即将举行同步,主库确认复兴后,主从库间开始同步
[*]主库将所有数据同步给从库。从库收到数据后,在本地完成数据加载 - 依靠于内存快照生成的RDB文件
[*]从库吸收到RDB文件后,会先清空当前数据库 - 从库在通过replicaof命令开始和主库同步前,可能生存了别的数据
[*]主库将数据同步给从库的过程中,主库不会被阻塞,仍旧可以正常吸收哀求。为保证主从库的数据同等性,主库会在内存中用专门的 replication buffer,记载 RDB 文件生成后收到的所有写操纵
[*]主库把第二阶段执行过程中新收到的写命令,再发送给从库
https://img2024.cnblogs.com/blog/3531058/202410/3531058-20241006123112932-1424204719.png
[*]所有的从库都是和主库连接,所有的全量复制都是和主库举行的。
2、主从级联模式分担全量复制时的主库压力
一次全量复制中,对于主库需要完成两个耗时操纵:
[*]生成RDB文件 - fork操纵会阻塞主线程处置惩罚正常哀求
[*]传输RDB文件 - 占用主库网络带宽
至此,我们引出:“主 - 从 - 从”模式
[*]分担主库压力
[*]将主库生成RDB和传输RDB的压力,以级联的方式分散到从库上
[*]摆设主从集群时手动选择一个库(比如选择内存资源配置较高的从库),用于级联别的从库
[*]在从库执行命令replicaof 所选从库IP 6379,创建主从关系
https://img2024.cnblogs.com/blog/3531058/202410/3531058-20241006123415145-1442343689.png
[*]主从库间通过全量复制实现数据同步的过程,以及通过“主 - 从 - 从”模式分担主库压力
[*]一旦主从库完成了全量复制,它们之间就会一直维护一个网络连接,主库会通过这个连接将后续连续收到的命令操纵再同步给从库,这个过程也称为基于长连接的命令流传,可以避免频仍创建连接的开销。
[*]风险:网络断联或阻塞
3、主从库间网络断了怎么办?
在 Redis 2.8 之前,假如主从库在命令流传时出现了网络闪断,那么,从库就会和主库重新举行一次全量复制,开销非常大。
从 Redis 2.8 开始,网络断了之后,主从库会采用增量复制的方式继续同步。
[*]为避免环形缓冲区造成的主从库不同等,可以调整repl_backlog_size参数
[*]缓冲空间大小 = 主库写入命令速度 * 操纵大小 - 主从库间网络传输命令速度 * 操纵大小
[*]在实际应用中,思量到可能存在一些突发的哀求压力,我们通常需要把这个缓冲空间扩大一倍,即 repl_backlog_size = 缓冲空间大小 * 2
[*]也可以采用切片集群来分担单个主库的哀求压力
4、小结
[*]全量复制
[*]一个Redis实例的数据库不要太大,一个实例大小在几GB级别比较合适,可以减少RDB文件生成、传输和重新加载的开销
[*]避免多个从库同时和主库举行全量复制,给主库过大同步压力 - “主-从-从”
[*]基于长连接的命令流传
[*]增量复制
[*]留意repl_backlog_size配置参数
二、实践
运行环境:假造机操纵系统:centOS7,IP地址:192.168.88.130
已经安装好了 docker 和 docker-compose
采用Redis:7.4.0
至此,我们开始在假造机中搭建Redis“主-从-从”模式的主从库集群
[*]我们先创建好目录:
# mkdir /root/docker/redis-cluster
# cd /root/docker/redis-cluster
# mkdir redis0
# mkdir redis1
# mkdir redis2
# mkdir redis3
# mkdir redis4我们将redis0作为主库
redis1和redis2作为从库I和从库II(slave),redis3和redis4作为从库II的两个从库(主-从-从模式)
[*]redis0
# mkdir redis0/data
# vi redis0/redis.confprotected-mode no
bind 0.0.0.0
save 900 1
save 300 10
save 60 10000
rdbcompression yes
dbfilename dump.rdb
dir /data
# 关闭 aof 日志备份
appendonly no
# 自定义密码
requirepass root
# 启动端口
port 6379
# 换成自己的虚拟机的IP
replica-announce-ip 192.168.88.130
[*]redis1
# mkdir redis1/data
# vi redis1/redis.conf
[*]replicaof [主节点ip] [主节点端口] ,该配置主要是让当前节点作为从节点,配置具体的主节点的地址和端口(Redis 5.0 之前使用 slaveof [主节点ip] [主节点端口])
[*]masterauth [主节点的访问密码] ,该配置主要是在主节点设置密码的环境下,能够让从节点通过密码访问主节点
protected-mode no
bind 0.0.0.0
save 900 1
save 300 10
save 60 10000
rdbcompression yes
dbfilename dump.rdb
dir /data
# 关闭 aof 日志备份
appendonly no
# 启动端口
port 6479
# 将当前 redis 作为 redis0 的 slave
# 由于 docker 使用 host 模式,使用的是宿主机的 ip
replicaof 192.168.88.130 6379
# 自定义密码
requirepass root
# 访问 master 节点时需要提供的密码
masterauth root
masteruser redis0
replica-announce-ip 192.168.88.130
[*]redis2
# mkdir redis2/data
# vi redis2/redis.confprotected-mode no
bind 0.0.0.0
save 900 1
save 300 10
save 60 10000
rdbcompression yes
dbfilename dump.rdb
dir /data
# 关闭 aof 日志备份
appendonly no
# 启动端口
port 6579
# 将当前 redis 作为 redis0 的 slave
# 由于 docker 使用 host 模式,使用的是宿主机的 ip
replicaof 192.168.88.130 6379
# 自定义密码
requirepass root
# 访问 master 节点时需要提供的密码
masterauth root
replica-announce-ip 192.168.88.130
[*]redis3
# mkdir redis3/data
# vi redis3/redis.confprotected-mode no
bind 0.0.0.0
save 900 1
save 300 10
save 60 10000
rdbcompression yes
dbfilename dump.rdb
dir /data
# 关闭 aof 日志备份
appendonly no
# 启动端口
port 6679
# 将当前 redis 作为 redis2 的 slave
# 由于 docker 使用 host 模式,使用的是宿主机的 ip
replicaof 192.168.88.130 6579
# 自定义密码
requirepass root
# 访问 master 节点时需要提供的密码
masterauth root
replica-announce-ip 192.168.88.130
[*]redis4
# mkdir redis4/data
# vi redis4/redis.confprotected-mode no
bind 0.0.0.0
save 900 1
save 300 10
save 60 10000
rdbcompression yes
dbfilename dump.rdb
dir /data
# 关闭 aof 日志备份
appendonly no
# 启动端口
port 6779
# 将当前 redis 作为 redis2 的 slave
# 由于 docker 使用 host 模式,使用的是宿主机的 ip
replicaof 192.168.88.130 6579
# 自定义密码
requirepass root
# 访问 master 节点时需要提供的密码
masterauth root
replica-announce-ip 192.168.88.130接下来,我们在目录redis-cluster下新建文件docker-compose.yml:
services:
redis0:
image: redis
container_name: redis0
restart: always
privileged: true
network_mode: "host"
volumes:
- /root/docker/redis-cluster/redis0/data:/data
- /root/docker/redis-cluster/redis0/redis.conf:/etc/redis.conf
command:
redis-server /etc/redis.conf
redis1:
image: redis
container_name: redis1
restart: always
privileged: true
network_mode: "host"
volumes:
- /root/docker/redis-cluster/redis1/data:/data
- /root/docker/redis-cluster/redis1/redis.conf:/etc/redis.conf
command:
redis-server /etc/redis.conf
depends_on:
- redis0
redis2:
image: redis
container_name: redis2
restart: always
privileged: true
network_mode: "host"
volumes:
- /root/docker/redis-cluster/redis2/data:/data
- /root/docker/redis-cluster/redis2/redis.conf:/etc/redis.conf
command:
redis-server /etc/redis.conf
depends_on:
- redis0
redis3:
image: redis
container_name: redis3
restart: always
privileged: true
network_mode: "host"
volumes:
- /root/docker/redis-cluster/redis3/data:/data
- /root/docker/redis-cluster/redis3/redis.conf:/etc/redis.conf
command:
redis-server /etc/redis.conf
depends_on:
- redis2
redis4:
image: redis
container_name: redis4
restart: always
privileged: true
network_mode: "host"
volumes:
- /root/docker/redis-cluster/redis4/data:/data
- /root/docker/redis-cluster/redis4/redis.conf:/etc/redis.conf
command:
redis-server /etc/redis.conf
depends_on:
- redis2# vi docker-compose.yml
# docker-compose up -dhttps://img2024.cnblogs.com/blog/3531058/202410/3531058-20241005011905917-780804246.png摆设完成后,我们使用RDM连接摆设的所有redis:
https://img2024.cnblogs.com/blog/3531058/202410/3531058-20241005011939309-1828156482.png测试是否连接成功:
redis0:
https://img2024.cnblogs.com/blog/3531058/202410/3531058-20241006111352345-618570206.pngredis1:
https://img2024.cnblogs.com/blog/3531058/202410/3531058-20241006111529063-262122369.pngredis2:
https://img2024.cnblogs.com/blog/3531058/202410/3531058-20241006111656119-1742274173.pngredis3、redis4同理。
测试五个主从库读写操纵:
redis0:(可读可写)
https://img2024.cnblogs.com/blog/3531058/202410/3531058-20241006112206794-483231061.pngredis1、redis2:(可读不可写)
并且我们发现,redis1和redis2举行了主从库同步操纵,纵然我们没有在redis1和redis2中写入name:Monica,但它们和redis0创建连接后,主库会将数据同步给从库
https://img2024.cnblogs.com/blog/3531058/202410/3531058-20241006112343558-1769830412.pnghttps://img2024.cnblogs.com/blog/3531058/202410/3531058-20241006112353995-1528931277.pngredis3、redis4作为redis2的从库,同理,包含redis2的所有数据。
从RDM中我们也可以直观地看出,我们只对主库举行了一次写操纵,但其连接的所有从库(包括从库的从库)都包含了这个数据:
https://img2024.cnblogs.com/blog/3531058/202410/3531058-20241006112818150-2055383771.png通过以上验证表明:redis 的“主-从-从”模式集群已经搭建成功。
三、RedisTemplate 操纵 Redis 集群实现读写分离
1、新建项目
我们新建一个SpringBoot项目,项目结构如下:
https://img2024.cnblogs.com/blog/3531058/202410/3531058-20241006125501694-1389496292.png
[*]引入依靠
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
[*]配置application.yml文件
spring:
data:
redis:
# 这里只需配置主节点的信息即可
# RedisTemplate可以从主节点信息中获取从节点信息
host: 192.168.88.130
port: 6379
password: root
jedis:
pool:
# 最大连接数
max-active: 10
# 最大空闲连接数
max-idle: 5
# 最小空闲
min-idle: 1
# 连接超时时间(毫秒)
max-wait: 8000
[*]对RedisTemplate举行配置
package com.chen.redisdemo.redisConfig;
import io.lettuce.core.ReadFrom;
import org.springframework.boot.autoconfigure.data.redis.LettuceClientConfigurationBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* @version 1.0
* @Author feiye
* @Date 2024-10-06 12:15
* @className RedisConfig
* @since 1.0
*/
@Configuration
public class RedisConfig {
//你可以将读取策略,设置为 ReadFrom.REPLICA 表示只从 slave 节点读取数据
//然后你把 slave 节点全部停掉,然后看看是否能够读取成功
@Bean
public LettuceClientConfigurationBuilderCustomizer redisClientConfig() {
//配置 redisTemplate 优先从 slave 节点读取数据,如果 slave 都宕机了,则从 master 读取
return clientConfigurationBuilder -> clientConfigurationBuilder.readFrom(ReadFrom.REPLICA_PREFERRED);
//配置 redisTemplate 优先从 slave 节点读取数据,如果 slave 都宕机了,则抛出异常
//return clientConfigurationBuilder -> clientConfigurationBuilder.readFrom(ReadFrom.REPLICA);
}
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
//默认的Key序列化器为:JdkSerializationRedisSerializer
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setConnectionFactory(connectionFactory);
redisTemplate.setEnableTransactionSupport(true);
return redisTemplate;
}
}
[*]编写测试类
package com.chen.redisdemo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
@SpringBootTest
class RedisDemoApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void writeTest() {
redisTemplate.opsForValue().set("name", "Ross");
}
@Test
void getTest() {
Object name = redisTemplate.opsForValue().get("name");
if (name != null) {
System.out.println(name.toString());
}
}
}2、如何证明 RedisTemplate 是从 Slave 节点中获取数据的?
[*]起首我们修改一下 RedisConfig 类中的配置,让 RedisTemplate 只从 Slave 节点读取数据,不从 master 节点读取数据。
@Bean
public LettuceClientConfigurationBuilderCustomizer redisClientConfig() {
//配置 redisTemplate 优先从 slave 节点读取数据,如果 slave 都宕机了,则抛出异常
return clientConfigurationBuilder -> clientConfigurationBuilder.readFrom(ReadFrom.REPLICA);
}
[*]然后我们在 Linux 假造机上,执行以下命令,停掉所有 Slave 节点服务:
# docker-compose stop redis3
[+] Stopping 1/1
✔ Container redis3Stopped 0.3s
# docker-compose stop redis4
[+] Stopping 1/1
✔ Container redis4Stopped 0.2s
# docker-compose stop redis2
[+] Stopping 1/1
✔ Container redis2Stopped 0.2s
# docker-compose stop redis1
[+] Stopping 1/0
✔ Container redis1Stopped然后我们运行getTest()测试类,发现报错:
https://img2024.cnblogs.com/blog/3531058/202410/3531058-20241006131653244-1356273904.png
[*]接下来,我们启动redis3或redis4中的任意一个:
https://img2024.cnblogs.com/blog/3531058/202410/3531058-20241006131932042-1918750131.png我们发现,假如主节点和从节点全部宕机,只要启动此中一个从节点,主节点就会同时启动。
[*]我们再次启动测试类getTest():
https://img2024.cnblogs.com/blog/3531058/202410/3531058-20241006132053731-1284452163.png此时已经可以读取了。说明 RedisTemplate 就是从 Slave 节点中读取数据的。
测试完毕。
个人题目记载:
在举行摆设后发现主从库连接失败,详情如下:
redis0:
https://img2024.cnblogs.com/blog/3531058/202410/3531058-20241005011954275-908366049.pngredis1:
https://img2024.cnblogs.com/blog/3531058/202410/3531058-20241006111110203-323238291.png通过docker logs redis0查看日志,排查错误后发现是端口6379被占用。因为在之前我摆设过单机redis,使用了端口6379,但没有将其kill,导致端口被占用
本人采用最粗暴的方法就是直接把容器rm了^^
参考博文:
Redis 主从集群搭建并使用 RedisTemplate 实现读写分离
参考书籍:
《Redis核心技术与实战》
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]