缘由
项目中需要基于Docker-Compose 搭建Redis集群,因此通过这篇文章记录一下所遇到的问题。
1.redis配置文件的书写
port ${PORT}
requirepass root
masterauth root
protected-mode no
daemonize no
appendonly yes
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 15000
cluster-announce-ip 192.168.1.119
cluster-announce-port ${PORT}
cluster-announce-bus-port 1${PORT}
port:节点端口;(输入自己的端口)requirepass:添加访问认证;masterauth:如果主节点开启了访问认证,从节点访问主节点需要认证;requirepass是配置在主节点的,masterauth是配置在从节点的,两边配置要一样从节点才能和主节点连接上进行主从复制。protected-mode:保护模式,默认值 yes,即开启。开启保护模式以后,需配置bind ip或者设置访问密码;关闭保护模式,外部网络可以直接访问;daemonize:是否以守护线程的方式启动(后台启动),默认 no;appendonly:是否开启 AOF 持久化模式,默认 no;cluster-enabled:是否开启集群模式,默认 no;cluster-config-file:集群节点信息文件;cluster-node-timeout:集群节点连接超时时间;cluster-announce-ip:集群节点 IP,填写宿主机的 IP;cluster-announce-port:集群节点映射端口;cluster-announce-bus-port:集群节点总线端口。(这个端口十分重要,在下面会讲到) 每个 Redis 集群节点都需要打开两个 TCP 连接。一个用于为客户端提供服务的正常 Redis TCP 端口,例如 6379。还有一个基于 6379 端口加 10000 的端口,比如 16379。
1、是否只设置requirepass就可以?masterauth是否需要同步设置?
redis启用密码认证一定要requirepass和masterauth同时设置。
如果主节点设置了requirepass登录验证,在主从切换,slave在和master做数据同步的时候首先需要发送一个ping的消息给主节点判断主节点是否存活,再监听主节点的端口是否联通,发送数据同步等都会用到master的登录密码,否则无法登录,log会出现响应的报错。也就是说slave的masterauth和master的requirepass是对应的,所以建议redis启用密码时将各个节点的masterauth和requirepass设置为相同的密码,降低运维成本。当然设置为不同也是可以的,注意slave节点masterauth和master节点requirepass的对应关系就行。
2、requreipass和master的作用?\
masterauth作用:主要是针对master对应的slave节点设置的,在slave节点数据同步的时候用到。
requirepass作用:对登录权限做限制,redis每个节点的requirepass可以是独立、不同的。
2.docker-compose文件
redis-1-master:
image: redis:5.0
container_name: se-redis-1-master
command: redis-server /data/redis.conf
restart: always
# network_mode: "host"
privileged: true
volumes:
- /D/dockerFile/se/redis-1-master/data:/data #数据文件挂载
- /D/dockerFile/se/redis-1-master/dir:/usr/local/redis/db #数据文件挂载
ports:
- 6385:6385
- 16385:16385
# expose:
# - 6385
# - 16385
redis-1-slave:
image: redis:5.0
container_name: se-redis-1-slave
command: redis-server /data/redis.conf
restart: always
volumes:
- /D/dockerFile/se/redis-1-slave/data:/data #数据文件挂载
- /D/dockerFile/se/redis-1-slave/dir:/usr/local/redis/db #数据文件挂载
ports:
- 6382:6382
- 16382:16382
# expose:
# - 6382
# - 16382
redis-2-master:
image: redis:5.0
container_name: se-redis-2-master
command: redis-server /data/redis.conf
restart: always
privileged: true
volumes:
- /D/dockerFile/se/redis-2-master/data:/data #数据文件挂载
- /D/dockerFile/se/redis-2-master/dir:/usr/local/redis/db #数据文件挂载
ports:
- 6384:6384
- 16384:16384
redis-2-slave:
image: redis:5.0
container_name: se-redis-2-slave
command: redis-server /data/redis.conf
restart: always
privileged: true
volumes:
- /D/dockerFile/se/redis-2-slave/data:/data #数据文件挂载
- /D/dockerFile/se/redis-2-slave/dir:/usr/local/redis/db #数据文件挂载
ports:
- 6381:6381
- 16381:16381
# expose:
# - 6381
# - 16381
redis-3-master:
image: redis:5.0
container_name: se-redis-3-master
command: redis-server /data/redis.conf
restart: always
privileged: true
volumes:
- /D/dockerFile/se/redis-3-master/data:/data #数据文件挂载
- /D/dockerFile/se/redis-3-master/dir:/usr/local/redis/db #数据文件挂载
ports:
- 6383:6383
- 16383:16383
# expose:
# - 6383
# - 16383
redis-3-slave:
image: redis:5.0
container_name: se-redis-3-slave
command: redis-server /data/redis.conf
restart: always
privileged: true
volumes:
- /D/dockerFile/se/redis-3-slave/data:/data #数据文件挂载
- /D/dockerFile/se/redis-3-slave/dir:/usr/local/redis/db #数据文件挂载
ports:
- 6380:6380
- 16380:16380
# expose:
# - 6380
# - 16380
这里有一些非常重要的东西需要注意,如果没注意在之后的集群搭建过程中将出现问题。
- docker默认使用的network模式是bridge,所以我们在容器中IP地址是不断会发生变化的,这对于集群的搭建有很大的问题,也许会想到用docker的域名,但是测试后发现redis在集群部署过程中只能使用IP。在这里我采用基于宿主机host文件中
192.168.1.199 host.docker.internal中这个域名ip进行集群的搭建。在这里需要初一定要将这两个端口映射出来,因为在之后集群所走的ip都是192.168.1.199,基于宿主机,如果不映射将在集群创建过程中出现连接阻塞。
ports:
- 6380:6380
- 16380:16380
- 采用host模式(似乎只能在Linux下使用),这个也是网上大部分文章所写的。但是这里面有一些关键点需要注意,因为host模式下,虽然各个容器共享宿主机网络,但是如果你想通过外部访问这些redis实例,你需要将以下端口暴露出来。否则系统将无法访问。
expose:
- 6380
- 16380
注意:需要修改配置文件中的cluster-announce-ip:127.0.0.1
3.集群搭建
- 方法一:集群的三个从节点随机分配给三个主节点作为主从复制。(默认情况下前面三个节点为主节点)
redis-cli -a root --cluster create 192.168.1.199:6383 192.168.1.199:6384 192.168.1.199:6385 192.168.1.199:6380 192.168.1.199:6381 192.168.1.199:6382 --cluster-replicas 1
- 方法二:指定主从节点
- 创建3个主节点
redis-cli -a root --cluster create 192.168.1.199:6383 192.168.1.199:6384 192.168.1.199:6385 --cluster-replicas 0
- 指定三个从节点
redis-cli --cluster add-node 192.168.1.199:6380 192.168.1.199:6383 --cluster-slave --cluster-master-id ff14757cd7206c244228e74dc38b6f513a0fa297
- 192.168.1.199:6380 要添加的从节点
- 192.168.1.199:6383 原集群中任意主节点
- --cluster-master-id 主节点的id
673fe5ecba911e39b8225c21229f5b75c5d921e6 192.168.1.199:6385@16385 master - 0 1634734818000 3 connected 10923-16383
ff14757cd7206c244228e74dc38b6f513a0fa297 192.168.1.199:6383@16383 myself,master - 0 1634734817000 1 connected 0-5460
34de77ba4d85358137a9026b6698908bae339e35 192.168.1.199:6381@16381 slave e7f01ad7ce012b28f74ab8ee1358da4e291dab00 0 1634734816692 2 connected
a5668b04c86430062b9b54962e2bda6bd3a37c5f 192.168.1.199:6382@16382 slave 673fe5ecba911e39b8225c21229f5b75c5d921e6 0 1634734818700 3 connected
dcd8cf2cdcdcad62df28c89116494a07c409da18 192.168.1.199:6380@16380 slave ff14757cd7206c244228e74dc38b6f513a0fa297 0 1634734818000 1 connected
e7f01ad7ce012b28f74ab8ee1358da4e291dab00 192.168.1.199:6384@16384 master - 0 1634734817695 2 connected 5461-10922
- 查看集群状态
cluster info
- 查看节点信息
cluster nodes
4.yml文件配置
redis:
cluster:
nodes: 127.0.0.1:6381,127.0.0.1:6383,127.0.0.1:6385,127.0.0.1:6380,127.0.0.1:6382,127.0.0.1:6384
password: root
lettuce:
pool:
# 最大活跃连接数 默认8
max-active: 8
# 最大空闲连接数 8
max-idle: 8
# 最小空闲连接数 0
min-idle: 0
5.config配置文件 (多源地址链接:SpringBoot2.X整合Redis(单机+集群+多数据源)-Lettuce版 - 掘金 (juejin.cn))
package com.my.equipment.config.RedisCluster;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class RedisConfig {
@Autowired
private Environment environment;
/**
* 配置 lettuce 连接池
* @return
*/
@Bean
@Primary
@ConfigurationProperties(prefix = "spring.redis.lettuce.pool")
public GenericObjectPoolConfig redisPool() {
return new GenericObjectPoolConfig();
}
@Primary
@Bean("redisClusterConfig")
public RedisClusterConfiguration redisClusterConfig(){
Map<String,Object> source=new HashMap<>();
source.put("spring.redis.cluster.nodes",environment.getProperty("spring.redis.cluster.nodes"));
RedisClusterConfiguration redisClusterConfiguration=new RedisClusterConfiguration(new MapPropertySource("RedisClusterConfiguration", source));
redisClusterConfiguration.setPassword(environment.getProperty("spring.redis.password"));
return redisClusterConfiguration;
}
/**
* 配置(第一个)数据源的连接工厂
* 这里注意:需要添加@Primary 指定bean的名称,目的是为了创建两个不同名称的LettuceConnectionFactory
* @param redisPool
* @param redisClusterConfig
* @return
*/
@Bean("lettuceConnectionFactory")
@Primary
public LettuceConnectionFactory lettuceConnectionFactory(GenericObjectPoolConfig redisPool, @Qualifier("redisClusterConfig") RedisClusterConfiguration redisClusterConfig){
LettuceClientConfiguration clientConfiguration = LettucePoolingClientConfiguration.builder().poolConfig(redisPool).build();
return new LettuceConnectionFactory(redisClusterConfig,clientConfiguration);
}
@Bean("redisTemplate")
@Primary
public RedisTemplate redisTemplate(@Qualifier("lettuceConnectionFactory") RedisConnectionFactory redisConnectionFactory){
return getRedisTemplate(redisConnectionFactory);
}
private RedisTemplate getRedisTemplate(RedisConnectionFactory factory){
RedisTemplate<String,Object> template=new RedisTemplate<>();
template.setConnectionFactory(factory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer=new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
// 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常
// 存储到redis里的数据将是有类型的json数据,否则将是无类型的数据,处理起来比较麻烦
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
StringRedisSerializer stringRedisSerializer=new StringRedisSerializer();
// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value序列化方式采用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value也采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
//非spring注入需要调用该函数,使template初始化生效
template.afterPropertiesSet();
return template;
}
}
6.调用
@Override
@Transactional
// @Cacheable(key = "#id")
public SlaveSimData getSlaveData(int id) {
SlaveSimData slaveSimData = null;
if (redisTemplate.hasKey(id+"")) {
slaveSimData=(SlaveSimData) redisTemplate.opsForValue().get(id+"");
} else {
slaveSimData = slaveSimDataMapper.selectByPrimaryKey(id);
redisTemplate.opsForValue().set(id+"",slaveSimData);
System.out.println("生成数据缓存" + id);
}
return slaveSimData;
}