登录
/
注册
首页
论坛
其它
首页
科技
业界
安全
程序
广播
Follow
关于
博客
发1篇日志+1圆
记录
发1条记录+2圆币
发帖说明
登录
/
注册
账号
自动登录
找回密码
密码
登录
立即注册
搜索
搜索
关闭
CSDN热搜
程序园
精品问答
技术交流
资源下载
本版
帖子
用户
软件
问答
教程
代码
VIP网盘
VIP申请
网盘
联系我们
道具
勋章
任务
设置
我的收藏
退出
腾讯QQ
微信登录
返回列表
首页
›
业界区
›
安全
›
搭建Redis“主-从-从”模式集群并使用 RedisTemplate 实 ...
搭建Redis“主-从-从”模式集群并使用 RedisTemplate 实现读写分离
[ 复制链接 ]
捐催制
2025-6-9 14:57:18
一、理论相关
我们知道,Redis具有高可靠性,其含义包括:
数据尽量少丢失
- AOF 和 RDB
服务尽量少中断
- 增加副本冗余量,将一份数据同时保存在多个实例上,即
主从库模式
Redis主从库模式 - 保证数据副本的一致(读写分离):
读操作:主库、从库都可以接收
写操作:首先到主库执行,然后,主库将写操作同步给从库
采用读写分离的原因:
如果客户端对同一个数据进行多次修改,每一次的修改请求都发送到不同的实例上,在不同的实例上执行,那么这个数据在多个实例上的副本就不一致了
如果要对不同实例上的数据一致,就涉及到加锁、实例间协商是否完成修改等操作,会带来巨额的开销
这时我们就引出
主从库同步的原理
。
1、主从库间如何进行第一次同步?
当我们启动多个 Redis 实例的时候,它们相互之间就可以通过 replicaof(Redis 5.0 之前使用 slaveof)命令形成主库和从库的关系,之后会按照三个阶段完成数据的第一次同步。
主从库建立连接、协商同步,为全量复制做准备
replicaof 172.16.19.3 6379
复制代码
从库和主库建立连接,并告诉主库即将进行同步,主库确认回复后,主从库间开始同步
主库将所有数据同步给从库。从库收到数据后,在本地完成数据加载 - 依赖于内存快照生成的RDB文件
从库接收到RDB文件后,会先清空当前数据库 - 从库在通过replicaof命令开始和主库同步前,可能保存了其它数据
主库将数据同步给从库的过程中,主库不会被阻塞,仍然可以正常接收请求。为保证主从库的数据一致性,主库会在内存中用专门的 replication buffer,记录 RDB 文件生成后收到的所有写操作
主库把第二阶段执行过程中新收到的写命令,再发送给从库
所有的从库都是和主库连接,所有的全量复制都是和主库进行的。
2、主从级联模式分担全量复制时的主库压力
一次全量复制中,对于主库需要完成两个耗时操作:
生成RDB文件 - fork操作会阻塞主线程处理正常请求
传输RDB文件 - 占用主库网络带宽
至此,我们引出:
“主 - 从 - 从”模式
分担主库压力
将主库生成RDB和传输RDB的压力,以级联的方式分散到从库上
部署主从集群时手动选择一个库(比如选择内存资源配置较高的从库),用于级联其它从库
在从库执行命令replicaof 所选从库IP 6379,建立主从关系
主从库间通过全量复制实现数据同步的过程,以及通过“主 - 从 - 从”模式分担主库压力
一旦主从库完成了全量复制,它们之间就会一直维护一个网络连接,主库会通过这个连接将后续陆续收到的命令操作再同步给从库,这个过程也称为基于长连接的命令传播,可以避免频繁建立连接的开销。
风险
:网络断联或阻塞
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“主-从-从”模式的主从库集群
我们先创建好目录:
[root@centos ~]# mkdir /root/docker/redis-cluster[root@centos ~]# cd /root/docker/redis-cluster[root@centos redis-cluster]# mkdir redis0[root@centos redis-cluster]# mkdir redis1[root@centos redis-cluster]# mkdir redis2[root@centos redis-cluster]# mkdir redis3[root@centos redis-cluster]# mkdir redis4
复制代码
我们将redis0作为主库
redis1和redis2作为从库I和从库II(slave),redis3和redis4作为从库II的两个从库(主-从-从模式)
redis0
[root@centos redis-cluster]# mkdir redis0/data[root@centos redis-cluster]# vi redis0/redis.conf
复制代码
protected-mode nobind 0.0.0.0save 900 1save 300 10save 60 10000rdbcompression yesdbfilename dump.rdbdir /data# 关闭 aof 日志备份appendonly no# 自定义密码requirepass root# 启动端口port 6379# 换成自己的虚拟机的IPreplica-announce-ip 192.168.88.130
复制代码
redis1
[root@centos redis-cluster]# mkdir redis1/data[root@centos redis-cluster]# vi redis1/redis.conf
复制代码
replicaof [主节点ip] [主节点端口] ,该配置主要是让当前节点作为从节点,配置具体的主节点的地址和端口(Redis 5.0 之前使用 slaveof [主节点ip] [主节点端口])
masterauth [主节点的访问密码] ,该配置主要是在主节点设置密码的情况下,能够让从节点通过密码访问主节点
protected-mode nobind 0.0.0.0save 900 1save 300 10save 60 10000rdbcompression yesdbfilename dump.rdbdir /data# 关闭 aof 日志备份appendonly no# 启动端口port 6479# 将当前 redis 作为 redis0 的 slave# 由于 docker 使用 host 模式,使用的是宿主机的 ipreplicaof 192.168.88.130 6379# 自定义密码requirepass root# 访问 master 节点时需要提供的密码masterauth rootmasteruser redis0replica-announce-ip 192.168.88.130
复制代码
redis2
[root@centos redis-cluster]# mkdir redis2/data[root@centos redis-cluster]# vi redis2/redis.conf
复制代码
protected-mode nobind 0.0.0.0save 900 1save 300 10save 60 10000rdbcompression yesdbfilename dump.rdbdir /data# 关闭 aof 日志备份appendonly no# 启动端口port 6579# 将当前 redis 作为 redis0 的 slave# 由于 docker 使用 host 模式,使用的是宿主机的 ipreplicaof 192.168.88.130 6379# 自定义密码requirepass root# 访问 master 节点时需要提供的密码masterauth rootreplica-announce-ip 192.168.88.130
复制代码
redis3
[root@centos redis-cluster]# mkdir redis3/data[root@centos redis-cluster]# vi redis3/redis.conf
复制代码
protected-mode nobind 0.0.0.0save 900 1save 300 10save 60 10000rdbcompression yesdbfilename dump.rdbdir /data# 关闭 aof 日志备份appendonly no# 启动端口port 6679# 将当前 redis 作为 redis2 的 slave# 由于 docker 使用 host 模式,使用的是宿主机的 ipreplicaof 192.168.88.130 6579# 自定义密码requirepass root# 访问 master 节点时需要提供的密码masterauth rootreplica-announce-ip 192.168.88.130
复制代码
redis4
[root@centos redis-cluster]# mkdir redis4/data[root@centos redis-cluster]# vi redis4/redis.conf
复制代码
protected-mode nobind 0.0.0.0save 900 1save 300 10save 60 10000rdbcompression yesdbfilename dump.rdbdir /data# 关闭 aof 日志备份appendonly no# 启动端口port 6779# 将当前 redis 作为 redis2 的 slave# 由于 docker 使用 host 模式,使用的是宿主机的 ipreplicaof 192.168.88.130 6579# 自定义密码requirepass root# 访问 master 节点时需要提供的密码masterauth rootreplica-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
复制代码
[root@centos redis-cluster]# vi docker-compose.yml[root@centos redis-cluster]# docker-compose up -d
复制代码
部署完成后,我们使用RDM连接部署的所有redis:
测试是否连接成功:
redis0:
redis1:
redis2:
redis3、redis4同理。
测试五个主从库读写操作:
redis0:(可读可写)
redis1、redis2:(可读不可写)
并且我们发现,redis1和redis2进行了主从库同步操作,即使我们没有在redis1和redis2中写入name:Monica,但它们和redis0建立连接后,主库会将数据同步给从库
redis3、redis4作为redis2的从库,同理,包含redis2的所有数据。
从RDM中我们也可以直观地看出,我们只对主库进行了一次写操作,但其连接的所有从库(包括从库的从库)都包含了这个数据:
通过以上验证表明:redis 的“主-从-从”模式集群已经搭建成功。
三、RedisTemplate 操作 Redis 集群实现读写分离
1、新建项目
我们新建一个SpringBoot项目,项目结构如下:
引入依赖
org.springframework.boot spring-boot-starter-data-redis
复制代码
配置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 */@Configurationpublic 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 redisTemplate(RedisConnectionFactory connectionFactory) { RedisTemplate 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;@SpringBootTestclass 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 节点读取数据。
@Beanpublic LettuceClientConfigurationBuilderCustomizer redisClientConfig() { //配置 redisTemplate 优先从 slave 节点读取数据,如果 slave 都宕机了,则抛出异常 return clientConfigurationBuilder -> clientConfigurationBuilder.readFrom(ReadFrom.REPLICA);}
复制代码
然后我们在 Linux 虚拟机上,执行以下命令,停掉所有 Slave 节点服务:
[root@centos redis-cluster]# docker-compose stop redis3[+] Stopping 1/1 ✔ Container redis3 Stopped 0.3s [root@centos redis-cluster]# docker-compose stop redis4[+] Stopping 1/1 ✔ Container redis4 Stopped 0.2s [root@centos redis-cluster]# docker-compose stop redis2[+] Stopping 1/1 ✔ Container redis2 Stopped 0.2s [root@centos redis-cluster]# docker-compose stop redis1[+] Stopping 1/0 ✔ Container redis1 Stopped
复制代码
然后我们运行getTest()测试类,发现报错:
接下来,我们启动redis3或redis4中的任意一个:
我们发现,如果主节点和从节点全部宕机,只要启动其中一个从节点,
主节点就会同时启动
。
我们再次启动测试类getTest():
此时已经可以读取了。说明 RedisTemplate 就是从 Slave 节点中读取数据的。
测试完毕。
个人问题记录:
在进行部署后发现主从库连接失败,详情如下:
redis0:
redis1:
通过docker logs redis0查看日志,排查错误后发现是端口6379被占用。因为在之前我部署过单机redis,使用了端口6379,但没有将其kill,导致端口被占用
本人采用最粗暴的方法就是直接把容器rm了^^
参考博文:
Redis 主从集群搭建并使用 RedisTemplate 实现读写分离
参考书籍:
《Redis核心技术与实战》
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复
使用道具
举报
提升卡
置顶卡
沉默卡
喧嚣卡
变色卡
千斤顶
照妖镜
高级模式
B
Color
Image
Link
Quote
Code
Smilies
您需要登录后才可以回帖
登录
|
立即注册
回复
本版积分规则
回帖并转播
回帖后跳转到最后一页
签约作者
程序园优秀签约作者
发帖
捐催制
2025-6-9 14:57:18
关注
0
粉丝关注
17
主题发布
板块介绍填写区域,请于后台编辑
财富榜{圆}
敖可
9984
黎瑞芝
9990
杭环
9988
4
猷咎
9988
5
凶契帽
9988
6
接快背
9988
7
氛疵
9988
8
恐肩
9986
9
虽裘侪
9986
10
里豳朝
9986
查看更多