紧接之前 redis6.09 集群安装(docker-compose) 考虑和springboot进行集成。
1. 问题
上一版因为采用docker内网,导致本机springboot应用;出现无法连接的错误
1.1 解决方案
如何把redis集群的docker网段和springboot统一起来。
2. 优化增强docker集群
假设根目录为docker-redis
2.1. 建立子目录redis-cluster
2.2. 配置文件
redis-cluster/redis-cluster.tmpl
port 6379 # 容器内端口
requirepass 1234 # 密码
masterauth 1234 # 认证密码
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.126 # 主机IP地址
cluster-announce-port 6379 # 外部映射端口
cluster-announce-bus-port 16379 # 开启集群总线端口,值=cluster-announce-port +10000
2.3. 创建目录脚本
redis-cluster/generateFolder.sh
for port in `seq 6379 6384`; do \
mkdir -p ${port}/conf \
&& PORT=${port} envsubst < redis-cluster.tmpl > ${port}/conf/redis.conf \
&& mkdir -p ${port}/data;\
done
并启动执行generateFolder.sh,就会在同级目录下生成6个子目录
2.4. 在根目录下创建docker-compose.yaml
# 描述 Compose 文件的版本信息
version: "3.8"
# 定义服务,可以多个
services:
redis-cluster:
image: redis:6.0.9
command: redis-cli -a 1234 --cluster create 192.168.1.126:6379 192.168.1.126:6380 192.168.1.126:6381 192.168.1.126:6382 192.168.1.126:6383 192.168.1.126:6384 --cluster-replicas 1 --cluster-yes
depends_on:
- redis-6379
- redis-6380
- redis-6381
- redis-6382
- redis-6383
- redis-6384
redis-6379: # 服务名称
image: redis:6.0.9 # 创建容器时所需的镜像
container_name: redis-6379 # 容器名称
restart: always # 容器总是重新启动
ports:
- 6379:6379
- 16379:16379
volumes: # 数据卷,目录挂载
- ./redis-cluster/etc_rc.local:/etc/rc.local
- ./redis-cluster/6379/conf/redis.conf:/etc/redis/redis.conf
- ./redis-cluster/6379/data:/data
command: redis-server /etc/redis/redis.conf # 覆盖容器启动后默认执行的命令
redis-6380:
image: redis:6.0.9
container_name: redis-6380
ports:
- 6380:6379
- 16380:16379
volumes:
- ./redis-cluster/etc_rc.local:/etc/rc.local
- ./redis-cluster/6380/conf/redis.conf:/etc/redis/redis.conf
- ./redis-cluster/6380/data:/data
command: redis-server /etc/redis/redis.conf
redis-6381:
image: redis:6.0.9
container_name: redis-6381
ports:
- 6381:6379
- 16381:16379
volumes:
- ./redis-cluster/etc_rc.local:/etc/rc.local
- ./redis-cluster/6381/conf/redis.conf:/etc/redis/redis.conf
- ./redis-cluster/6381/data:/data
command: redis-server /etc/redis/redis.conf
redis-6382:
image: redis:6.0.9
container_name: redis-6382
ports:
- 6382:6379
- 16382:16379
volumes:
- ./redis-cluster/etc_rc.local:/etc/rc.local
- ./redis-cluster/6382/conf/redis.conf:/etc/redis/redis.conf
- ./redis-cluster/6382/data:/data
command: redis-server /etc/redis/redis.conf
redis-6383:
image: redis:6.0.9
container_name: redis-6383
ports:
- 6383:6379
- 16383:16379
volumes:
- ./redis-cluster/etc_rc.local:/etc/rc.local
- ./redis-cluster/6383/conf/redis.conf:/etc/redis/redis.conf
- ./redis-cluster/6383/data:/data
command: redis-server /etc/redis/redis.conf
redis-6384:
image: redis:6.0.9
container_name: redis-6384
ports:
- 6384:6379
- 16384:16379
volumes:
- ./redis-cluster/etc_rc.local:/etc/rc.local
- ./redis-cluster/6384/conf/redis.conf:/etc/redis/redis.conf
- ./redis-cluster/6384/data:/data
command: redis-server /etc/redis/redis.conf
2.5 加载启动集群
docker-compose up -d
问题解决
Caused by: io.lettuce.core.cluster.PartitionSelectorException: Cannot determine a partition for slot nnnnn
解决方法请参考 juejin.cn/post/684490…
关键几处:
docker container ls
docker exec -it 4688b2715f4a /bin/bash
redis-cli --cluster fix 192.168.1.126:6381 -a 1234
3. spring-boot集成
3.1. pom依赖
pom.xml
<!-- Redis Cache -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
3.2 配置文件
application.yaml
spring:
# redis 配置
redis:
# Redis 默认数据库设置
database: 0
# Redis服务器连接密码(默认为空)
password: 1234
timeout: 60000ms
# Redis Cluster集群节点配置
cluster:
# Redis 集群地址信息
nodes:
- 192.168.1.126:6379
- 192.168.1.126:6380
- 192.168.1.126:6381
- 192.168.1.126:6382
- 192.168.1.126:6383
- 192.168.1.126:6384
# 获取失败 最大重定向次数
max-redirects: 3
lettuce:
pool:
max-active: 8 # 连接池最大连接数(使用负值表示没有限制)
max-idle: 8 # 连接池中的最大空闲连接
min-idle: 0 # 连接池中的最小空闲连接
max-wait: -1 # 连接池最大阻塞等待时间(使用负值表示没有限制)
3.3 配置类
ClusterConfigurationProperties.java
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
@ConfigurationProperties(prefix = "spring.redis.cluster")
public class ClusterConfigurationProperties {
/*
* spring.redis.cluster.nodes[0] = 127.0.0.1:7379 spring.redis.cluster.nodes[1]
* = 127.0.0.1:7380 ...
*/
private List<String> nodes;
/**
* spring.redis.cluster.max-redirects=3
*/
private int maxRedirects;
/**
* Get initial collection of known cluster nodes in format {@code host:port}.
*
* @return
*/
public List<String> getNodes() {
return nodes;
}
public void setNodes(List<String> nodes) {
this.nodes = nodes;
}
public int getMaxRedirects() {
return maxRedirects;
}
public void setMaxRedirects(int maxRedirects) {
this.maxRedirects = maxRedirects;
}
}
3.4 关键配置类
RedisClusterConfig.java
import io.lettuce.core.ReadFrom;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
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.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.scheduling.annotation.EnableAsync;
import java.lang.reflect.Method;
@Configuration
@EnableAsync
public class RedisClusterConfig {
@Autowired
private ClusterConfigurationProperties clusterProperties;
@Autowired
private RedisProperties redisProperties;
@Bean
LettuceConnectionFactory redisConnectionFactory(RedisClusterConfiguration redisConfiguration) {
LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
.readFrom(ReadFrom.REPLICA_PREFERRED).build();
return new LettuceConnectionFactory(redisConfiguration, clientConfig);
}
@Bean
RedisClusterConfiguration redisConfiguration() {
RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration(clusterProperties.getNodes());
redisClusterConfiguration.setPassword(redisProperties.getPassword());
redisClusterConfiguration.setMaxRedirects(clusterProperties.getMaxRedirects());
return redisClusterConfiguration;
}
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
@Primary
RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
template.setKeySerializer(new StringRedisSerializer());
// other settings...
return template;
}
@Bean
public KeyGenerator keyGenerator() {
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getName());
sb.append(method.getName());
for (Object obj : params) {
sb.append(obj.toString());
}
return sb.toString();
}
};
}
}
3.5 Redis访问工具类
RedisUtils.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import java.io.Serializable;
import java.util.*;
import java.util.concurrent.TimeUnit;
@Component
public class RedisUtils {
@Autowired
private RedisTemplate redisTemplate;
/**
* 批量删除对应的value
*
* @param keys
*/
public void remove(final String... keys) {
for (String key : keys) {
remove(key);
}
}
/**
* 批量删除key
*
* @param pattern
*/
public void removePattern(final String pattern) {
Set<Serializable> keys = redisTemplate.keys(pattern);
if (keys.size() > 0) {
redisTemplate.delete(keys);
}
}
/**
* 删除对应的value
*
* @param key
*/
public void remove(final String key) {
if (exists(key)) {
redisTemplate.delete(key);
}
}
/**
* 判断缓存中是否有对应的value
*
* @param key
* @return
*/
public boolean exists(final String key) {
return redisTemplate.hasKey(key);
}
/**
* 读取缓存
*
* @param key
* @return
*/
public Object get(final String key) {
Object result = null;
ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
result = operations.get(key);
return result;
}
/**
* 写入缓存
*
* @param key
* @param value
* @return
*/
public boolean set(final String key, Object value) {
boolean result = false;
try {
ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
operations.set(key, value);
result = true;
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/**
* 写入缓存
*
* @param key
* @param value
* @return
*/
public boolean set(final String key, Object value, Long expireTime) {
boolean result = false;
try {
ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
operations.set(key, value);
redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
result = true;
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
}
接下来在service中就可以通过RedisUtils来操作redis了。