SpringBoot+Docker-Compose搭建redis集群

788 阅读8分钟

缘由

项目中需要基于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

这里有一些非常重要的东西需要注意,如果没注意在之后的集群搭建过程中将出现问题。

  1. 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
  1. 采用host模式(似乎只能在Linux下使用),这个也是网上大部分文章所写的。但是这里面有一些关键点需要注意,因为host模式下,虽然各个容器共享宿主机网络,但是如果你想通过外部访问这些redis实例,你需要将以下端口暴露出来。否则系统将无法访问。
   expose:
     - 6380
     - 16380

注意:需要修改配置文件中的cluster-announce-ip:127.0.0.1

3.集群搭建

  1. 方法一:集群的三个从节点随机分配给三个主节点作为主从复制。(默认情况下前面三个节点为主节点)
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
  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;
    }