redis读写分离.md 10 KB

redis读写分离

redis需要通过主从复制和哨兵模式来实现读写分离,节点设置为一主二从。下面以mall4j-aof-redis为例

1. 配置主从复制

配置主节点

redis主节点默认不需要额外配置,只需要确保其网络端口(系统配置为6389)对外可访问。 使用系统部署文档中的配置文件redis.conf即可

配置从节点

从节点需要配置主节点的IP、端口和密码,可使用主节点的conf文件配置,配置两个从节点,补充如下

replicaof <masterip><masterport>
masterauth <master-passwd>

对应为

replicaof 192.168.1.72 6389
masterauth hn02le.34lkdLKD

在docker-compose.yml中根据mall4j-aof-redis添加相同配置,分别修改端口为7001,7002启动

  mall4j-7001-redis:
    image: redis:7.0
    container_name: mall4cloud-7001-redis
    restart: always
    network_mode: "host"
    expose:
      - 7001
    volumes:
      - ./redis/redis7001.conf:/etc/redis/redis7001.conf
    command: redis-server /etc/redis/redis7001.conf --requirepass hn02le.34lkdLKD

启动docker-compose.yml

docker-compose up -d

可进入容器查看节点状态,以主节点为例

docker exec -it mall4j-aof-redis redis-cli -p 6389 
auth hn02le.34lkdLKD
info replication

主节点输出以下内容即正常

// 主节点输出
role:master
connected_slaves:2
slave0:ip=192.168.1.72,port=7001,state=online,offset=249031856,lag=1
slave1:ip=192.168.1.72,port=7002,state=online,offset=249031995,lag=1
        
// 从节点输出
role:slave 
master_host:192.168.1.72
master_port:6389
master_link_status:up

启动成功后,主从节点会自动进行同步,主节点会自动将数据同步到从节点。

2. 配置哨兵模式 sentinel

共计启动三个实例,分别运行于27001、27002、27003三个端口,以sentinel-27001.conf为例,配置信息如下,其余两个配置文件基本上一致,改一下端口以及pidfile、logfile即可。

port 27001
daemonize no
pidfile /root/mall4j-shop/redis/sentinel-27001.pid
logfile /root/mall4j-shop/redis/sentinel-27001.log

# 监控192.168.1.72:6389实例,实例取名为mymaster,当有两个哨兵认为实例下线后,自动进行故障转移
sentinel monitor mymaster 192.168.1.72 6389 2
sentinel auth-pass mymaster hn02le.34lkdLKD
# 服务不可达时间,心跳超过这个时间,sentinel将认为节点挂了
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
sentinel parallel-syncs mymaster 1

在docker-compose.yml配置中添加三个实例

  sentinel-27001-redis:
      image: redis:7.0
      container_name: sentinel-27001-redis
      restart: always
      network_mode: "host"
      expose:
        - 27001
      volumes:
      - ./redis/sentinel-27001.conf:/etc/redis/sentinel-27001.conf
      - /root/mall4j-shop/redis:/root/mall4j-shop/redis
      command: redis-sentinel /etc/redis/sentinel-27001.

启动docker-compose.yml,启动后进入容器查看

docker exec -it sentinel-27001-redis redis-cli -p 27001
info sentinel

输入结果为:

sentinel_masters:1
sentinel_tilt:0
sentinel_tilt_since_seconds:-1
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=192.168.1.72:7002,slaves=2,sentinels=3

启动成功后,三个实例会自动进行监控,当主节点挂掉后,会自动进行故障转移。

代码配置

在项目pom.yml中引入依赖

        <commons-pool2.version>2.12.1</commons-pool2.version>
        <lettuce.version>6.2.6.RELEASE</lettuce.version>

            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-pool2</artifactId>
                <version>${commons-pool2.version}</version>
            </dependency>
            <dependency>
                <groupId>io.lettuce</groupId>
                <artifactId>lettuce-core</artifactId>
                <version>${lettuce.version}</version>
            </dependency>

在yami-shop-common模块中的pom.yml添加依赖

        <dependency>
            <groupId>io.lettuce</groupId>
            <artifactId>lettuce-core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>

修改RedisCacheConfig类,并添加以下代码

    @Bean
    public RedisConnectionFactory createRedisConnectionFactory(RedisProperties redisProperties) {
        // 判断是否配置了哨兵模式
        if (redisProperties.getSentinel() != null && redisProperties.getSentinel().getMaster() != null) {
            // 哨兵模式配置
            var sentinelConfig = new RedisSentinelConfiguration()
                    .master(redisProperties.getSentinel().getMaster());
            // 添加哨兵节点(使用 Java 17 的 Stream API 优化)
            redisProperties.getSentinel().getNodes().stream()
                    .map(node -> node.split(":"))
                    .forEach(parts -> sentinelConfig.sentinel(parts[0], Integer.parseInt(parts[1])));
            if (Objects.nonNull(redisProperties.getDatabase())) {
                sentinelConfig.setDatabase(redisProperties.getDatabase());
            } else {
                sentinelConfig.setDatabase(0);
            }
            if (Objects.nonNull(redisProperties.getPassword())) {
                sentinelConfig.setPassword(RedisPassword.of(redisProperties.getPassword()));
            }
            // 配置读写分离策略:优先从副本读取
            var clientConfig = LettuceClientConfiguration.builder()
                    .readFrom(ReadFrom.REPLICA_PREFERRED)
                    .build();
            return new LettuceConnectionFactory(sentinelConfig, clientConfig);
        } else {
            // 单节点模式配置
            var standaloneConfig = new RedisStandaloneConfiguration();
            standaloneConfig.setHostName(redisProperties.getHost());
            standaloneConfig.setPort(redisProperties.getPort());
            standaloneConfig.setPassword(RedisPassword.of(redisProperties.getPassword()));
            standaloneConfig.setDatabase(redisProperties.getDatabase());
            var clientConfig = LettuceClientConfiguration.builder()
                    .build();
            return new LettuceConnectionFactory(standaloneConfig, clientConfig);
        }
    }

修改AofRedisConfig类的AofRedisConfig方法,添加以下代码

public AofRedisConfig(AofRedisBO aofRedisBO) {

        if (StrUtil.isBlank(aofRedisBO.getRedisAddr())) {
            throw new YamiShopBindException("请在yml配置好 aofRedis 的配置,见 com.yami.shop.common.bo.AofRedisBO");
        }

        Config config = new Config();
        if (BooleanUtil.isTrue(aofRedisBO.getSentinel())) {
            // 哨兵模式
            SentinelServersConfig sentinelServersConfig = config.useSentinelServers();
            sentinelServersConfig.setMasterName(aofRedisBO.getMasterName());
            List<String> list = Arrays.stream(aofRedisBO.getRedisAddr().split(",")).map(ip -> "redis://" + ip).toList();
            if (aofRedisBO.getDatabase() != null) {
                sentinelServersConfig.setDatabase(aofRedisBO.getDatabase());
            } else {
                sentinelServersConfig.setDatabase(0);
            }
            if (StrUtil.isNotBlank(aofRedisBO.getPassword())) {
                sentinelServersConfig.setPassword(aofRedisBO.getPassword());
            }
            sentinelServersConfig.setSentinelAddresses(list);
        } else {
            // 单机
            SingleServerConfig singleServerConfig = config.useSingleServer();
            if (StrUtil.isNotBlank(aofRedisBO.getPassword())) {
                singleServerConfig.setPassword(aofRedisBO.getPassword());
            }
            if (aofRedisBO.getDatabase() != null) {
                singleServerConfig.setDatabase(aofRedisBO.getDatabase());
            } else {
                singleServerConfig.setDatabase(0);
            }
            singleServerConfig.setAddress("redis://" + aofRedisBO.getRedisAddr());
        }

        redissonClient = Redisson.create(config);
        LOGGER.info("创建redisson, redisson={}", redissonClient);

        stringRedisTemplate = new StringRedisTemplate();

        stringRedisTemplate.setValueSerializer(StringRedisSerializer.UTF_8);
        stringRedisTemplate.setKeySerializer(StringRedisSerializer.UTF_8);
        stringRedisTemplate.setConnectionFactory(new RedissonConnectionFactory(redissonClient));

        stringRedisTemplate.afterPropertiesSet();
    }

修改服务的application-dev.yml配置文件中的redis连接配置,端口和密码配置根据实际情况配置

  data:
    redis:
      sentinel:
        master: mymaster
        nodes:
          - 192.168.1.72:27001
          - 192.168.1.72:27002
          - 192.168.1.72:27003
      password: hn02le.34lkdLKD
      database: 7

aofredis的配置

redis:
  aof:
    sentinel: true
    master-name: mymaster
    database: 8
    redis-addr: 192.168.1.72:27001,192.168.1.72:27002,192.168.1.72:27003
    password: hn02le.34lkdLKD

如果为java服务由docker-compose启动,yml环境为docker,修改项目中的application-docker.yml文件

  data:
    redis:
      sentinel:
        master: ${SENTINEL_MASTER:mymaster}
        nodes:
          - ${MONGO_NODE_1:-192.168.1.72:27001} # 默认值为192.168.1.72:27001,可通过环境变量MONGO_NODE_1覆盖
          - ${MONGO_NODE_2:-192.168.1.72:27002} # 默认值为192.168.1.72:27002,可通过环境变量MONGO_NODE_2覆盖
          - ${MONGO_NODE_3:-192.168.1.72:27003} # 默认值为192.168.1.72:27003,可通过环境变量MONGO_NODE_3覆盖
      database: ${REDIS_DATABASE:0}
      password: ${REDIS_PASSWORD:}

  redis:
    aof:
      sentinel: true
      master-name: ${REDIS_AOF_MASTER_NAME:mymaster}
      database: ${REDIS_AOF_DATABASE:0}
      redis-addr: ${REDIS_AOF_ADDR:192.168.1.72:27001,192.168.1.72:27002,192.168.1.72:27003}
      password: ${REDIS_AOF_PASSWORD:}

添加对应环境变量,重新打包代码。 在docker-compose.yml文件中添加mall4j-api、mall4j-multishop、mall4j-platform中对应配置名称的环境变量,重启服务。