单机版的 Redis 能够承载并发访问的能力有限,对于绝大多数的系统而言,都是读多写少,系统之所以宕机,一般都是因为并发读操作太高导致的宕机,因此搭建 Redis 主从集群,实现读写分离,是一种有效的提高并发访问能力的方案。
本篇博客介绍在一台虚拟机上,使用 docker-compose 模拟搭建一个【一主两从】的 Redis 集群,然后搭建一个 SpringBoot 工程,使用 RedisTemplate 连接 Redis 主从集群,实现读写分离功能,并且在博客的最后提供源代码下载。
一、集群搭建
在上篇博客中,我们已经使用 docker-compose 搭建了单机版的 Redis,本篇博客仍然使用这台虚拟机环境。
我的虚拟机操作系统是 CentOS7(ip 地址是 192.168.136.128),已经安装好了 docker 和 docker-compose
首先我们先创建好目录 /app/redis-cluster 并在其下面创建 3 个子文件夹 redis1、redis2、redis3
在每个 redis 目录下,创建一个配置文件 redis.conf,以及创建一个 data 文件夹用来存储备份数据
我们使用 redis1 作为主节点(master),redis2 和 redis3 作为从节点(slave),首先我们先编辑好配置文件
先列出 redis1 的 redis.conf 配置文件,作为主节点,配置文件比较简单
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
# 自定义密码
requirepass root
# 启动端口
port 6379
# 虚拟机会有多个 ip,这里指定具体一个 ip 地址
replica-announce-ip 192.168.136.128
然后看一下 redis2 的 redis.conf 配置文件内容,最重要的 2 项配置如下:
- 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 作为 redis1 的 slave
# 由于 docker 使用 host 模式,使用的是宿主机的 ip
slaveof 192.168.136.128 6379
# 自定义密码
requirepass root
# 访问 master 节点时需要提供的密码
masterauth root
# 虚拟机会有多个 ip,这里指定具体一个 ip 地址
replica-announce-ip 192.168.136.128
redis3 的配置文件 redis.conf 如下所示,基本跟 redis2 的配置文件一样,唯一不同的就是启动端口:
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 6579
# 将当前 redis 作为 redis1 的 slave
# 由于 docker 使用 host 模式,使用的是宿主机的 ip
slaveof 192.168.136.128 6379
# 自定义密码
requirepass root
# 访问 master 节点时需要提供的密码
masterauth root
# 虚拟机会有多个 ip,这里指定具体一个 ip 地址
replica-announce-ip 192.168.136.128
最后总结一下 3 个 redis 的配置文件特点:
- 三个 redis 节点,如果要是设置访问密码的话,最好设置相同的密码,比如本篇博客设置的密码都是 root
- 由于在同一台虚拟机上部署,为了方便 docker 外部的 java 代码访问,因此后面将使用 docker 的 host 部署模式,也就是三个节点的 redis 将使用虚拟机的 ip 和端口,因此这三个 redis 的配置文件,必须配置不同的服务端口。本篇博客中 redis1、redis2、redis3 分别使用 6379、6479、6579 端口。
- 三个 redis 节点,都是用默认的 rdb 数据备份存储方式,关闭了 aof 的日志备份方式,可以提高性能
- 如果主节点设置的访问密码,从节点需要在配置文件使用 masterauth [主节点的访问密码] 配置,确保能够访问主节点
- 通过在从节点的配置文件中使用 slaveof [主节点ip] [主节点端口] 配置,指定该从节点的主节点
- 使用 docker 的 host 模式后,三个节点使用的是虚拟机的 ip ,由于虚拟机可能有多个网卡,会存在多个 ip 地址,因为必须让 3 个节点绑定在同一个网卡上,也就是绑定同一个 ip 地址,使用 replica-announce-ip [ip地址] 进行配置即可
最后我们在 /app/redis-cluster 目录下创建一个 docker-compose.yml 文件,填写内容如下:
version: "3.5"
services:
redis1:
image: redis
container_name: redis1
restart: always
privileged: true
network_mode: "host"
volumes:
- /app/redis-cluster/redis1/data:/data
- /app/redis-cluster/redis1/redis.conf:/etc/redis.conf
command:
redis-server /etc/redis.conf
redis2:
image: redis
container_name: redis2
restart: always
privileged: true
network_mode: "host"
volumes:
- /app/redis-cluster/redis2/data:/data
- /app/redis-cluster/redis2/redis.conf:/etc/redis.conf
command:
redis-server /etc/redis.conf
depends_on:
- redis1
redis3:
image: redis
container_name: redis3
restart: always
privileged: true
network_mode: "host"
volumes:
- /app/redis-cluster/redis3/data:/data
- /app/redis-cluster/redis3/redis.conf:/etc/redis.conf
command:
redis-server /etc/redis.conf
depends_on:
- redis1
然后在 docker-compose.yml 所在目录运行 docker-compose up -d
命令启动 3 个 redis 服务即可。
启动成功后,可以使用 docker-compose ps
或 docker ps
查看服务的启动状态:
使用 RDM 客户端工具,分别连接 redis1、redis2、redis3
它们的 ip 地址都是 192.168.136.128,密码都是 root,连接端口分别是 6379、6479、6579
连接上任何一个 redis 节点,都可以运行 info replication
命令查看集群节点状况,比如我们连接主节点 redis1
然后我们对 3 个 redis 节点进行操作,发现 redis1 主节点可读可写,而 redis2 和 redis3 这俩从节点,只能读,不能写。
redis1 主节点上写入的数据,很快就同步到了 redis2 和 redis3 这俩从节点上。
OK,通过以上验证表明:redis 的一主量从集群,已经搭建成功。
二、RedisTemplate 操作 Redis 集群实现读写分离
新建一个 SpringBoot 工程,名称为 springboot_redis_cluster1,结构如下图所示:
代码非常简单,这里就直接把细节列出来,首先看一下 application.yml 配置文件:
spring:
redis:
# 这里只配置主节点的连接信息即可,
# 因为 RedisTemplate 可以从主节点中获取从节点的信息
host: 192.168.136.128
port: 6379
password: root
jedis:
pool:
# 最大连接数
max-active: 10
# 最大空闲连接数
max-idle: 5
# 最小空闲
min-idle: 1
# 连接超时时间(毫秒)
max-wait: 3000
这里只需要配置 redis1 主节点的连接信息即可,因为 RedisTemplate 可以从主节点中获取从节点的信息。
然后我们需要对 RedisTemplate 进行一下配置:
package com.jobs.config;
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;
@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;
}
}
由于 RedisTemplate 底层使用的是 Lettuce ,因此这里配置客户端读取方式:ReadFrom.REPLICA_PREFERRED 表示优先从 slave 节点中读取数据,如果 slave 节点都挂掉了,或者响应超时,则从主节点读取数据。
最后就是编写一个测试类 RedisClusterTest ,测试对 Redis 集群的读写操作,如下所示:
package com.jobs;
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
public class RedisClusterTest {
@Autowired
private RedisTemplate redisTemplate;
@Test
void writeTest() {
redisTemplate.opsForValue().set("name", "jobs");
}
@Test
void getTest() {
Object name = redisTemplate.opsForValue().get("name");
if (name != null) {
System.out.println(name.toString());
}
}
}
运行以上 2 个测试方法,即可从 Redis 集群中读写数据,代码及其简单,跟访问单机版的 Redis 一模一样。
三、如何证明 RedisTemplate 是从 Slave 节点中获取数据的
写入数据是操作 redis1 这个 master 节点的,因为上面 Redis 集群搭建好后,我们已经验证过 Slave 节点只读不可写。
首先我们先运行一下 writeTest 方法,向 Redis 集群中写入数据,因为测试读取的时候,需要用到。
运行以上的 getTest 测试方法,通过控制台的日志,并不能看出 RedisTemplate 是从 Slave 节点中读取数据的。
为了能够证明 RedisTemplate 是从 Slave 节点中读取数据的,需要进行以下操作:
首先我们修改一下 RedisConfig 类中的配置,让 RedisTemplate 只从 Slave 节点读取数据,不从 master 节点读取数据。
@Bean
public LettuceClientConfigurationBuilderCustomizer redisClientConfig() {
//配置 redisTemplate 优先从 slave 节点读取数据,如果 slave 都宕机了,则抛出异常
return clientConfigurationBuilder -> clientConfigurationBuilder.readFrom(ReadFrom.REPLICA);
}
然后我们在 Linux 虚拟机上,执行以下命令,停掉 redis2 和 redis3 这两个 Slave 节点服务:
docker-compose stop redis2
docker-compose stop redis3
此时再执行测试类 RedisClusterTest 下的 getTest 方法,你会发现报错,原因是无法找到一个节点来读取数据。
此时你只要把 redis2、redis3 任意一个 Slave 节点启动起来,或者两个 Slave 节点都启动起来,再运行 getTest 方法,就能够获取数据成功。
docker-compose start redis2
docker-compose start redis3
通过以上操作,就足够可以证明,RedisTemplate 就是从 Slave 节点中读取数据的。
RedisTemplate 对 Redis 集群的访问,支持的非常好,从而使我们的代码,像操作单机版 Redis 一样简单,轻松实现读写分离。
OK,以上就是 Redis 主从集群的搭建,以及使用 RedisTemplate 操作集群实现读写分离的介绍。但是这中 Redis 集群具有一个缺点,那就 master 节点和 slave 节点是固定的,如果 master 节点宕机,现有的 slave 节点无法选举出一个自动变成 master 节点。后面我们会介绍 Redis 的哨兵集群搭建,以及 Redis 的分片集群搭建,这两种集群可以很好的解决 master 节点宕机问题。
本篇博客的源代码下载地址为:https://files.cnblogs.com/files/blogs/699532/springboot_redis_cluster1.zip