redis6.09 集群安装(docker-compose)--增强版

1,307 阅读4分钟

紧接之前 redis6.09 集群安装(docker-compose) 考虑和springboot进行集成。

1. 问题

上一版因为采用docker内网,导致本机springboot应用;出现无法连接的错误

1.1 解决方案

如何把redis集群的docker网段和springboot统一起来。

2. 优化增强docker集群

假设根目录为docker-redis

image.png

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

image.png

问题解决

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了。