【Redis】主从配置和读写分离实现

主从配置

docker pull redis:6.0.3

Master配置修改

IP:192.168.0.100,端口:6378

bind 0.0.0.0
port 6378
requirepass 123456
# 关闭持久化
appendonly no
save "" # 允许远程连接 protected
-mode no

SlaveA配置修改

IP:192.168.0.100,端口:6377

bind 0.0.0.0
port 6377
requirepass 123456
# 主密码
masterauth 123456
# 关闭持久化
appendonly no
save "" # 允许远程连接 protected
-mode no # 主服务器的地址 replicaof 192.168.0.100 6378 # 从机只读模式默认是开启的 replica-read-only yes

SlaveB配置修改

IP:192.168.0.100,端口:6376

bind 0.0.0.0
port 6376
requirepass 123456
# 主密码
masterauth 123456
# 关闭持久化
appendonly no
save "" # 允许远程连接 protected
-mode no # 主服务器的地址 replicaof 192.168.0.100 6378 # 从机只读模式默认是开启的 replica-read-only yes

启动容器

docker run -d --restart=always -m=1g --name redisMaster -p 6378:6378 --privileged=true -v /ycx/data/redis/conf/master.conf:/usr/local/redis.conf -v /ycx/data/redis/data/master:/data docker.io/redis:6.0.3 redis-server /usr/local/redis.conf

docker run -d --restart=always -m=1g --name redisSlaveA -p 6377:6377 --privileged=true -v /ycx/data/redis/conf/slaveA.conf:/usr/local/redis.conf -v /ycx/data/redis/data/slaveA:/data docker.io/redis:6.0.3 redis-server /usr/local/redis.conf

docker run -d --restart=always -m=1g --name redisSlaveB -p 6376:6376 --privileged=true -v /ycx/data/redis/conf/slaveB.conf:/usr/local/redis.conf -v /ycx/data/redis/data/slaveB:/data docker.io/redis:6.0.3 redis-server /usr/local/redis.conf

查看主

Master-192.168.1.100:0>info replication
"# Replication
role:master
connected_slaves:2
slave0:ip=192.168.0.100,port=6377,state=online,offset=1063,lag=0
slave1:ip=192.168.0.100,port=6376,state=online,offset=1063,lag=1
master_replid:5f7d94600462d4861fafef71fd824cdeca59dc25
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:1063
master_repl_meaningful_offset:377
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:1063
"

查看从

SlaveA-192.168.1.100:0>info replication
"# Replication
role:slave
master_host:192.168.0.100
master_port:6378
master_link_status:up
master_last_io_seconds_ago:6
master_sync_in_progress:0
slave_repl_offset:1077
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:5f7d94600462d4861fafef71fd824cdeca59dc25
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:1077
master_repl_meaningful_offset:377
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:1077
"

监控 monitor

 

错误解决

1、Error condition on socket for SYNC: Operation now in progress

#查看所有打开的端口
firewall-cmd --zone=public --list-ports
# 开放端口
firewall-cmd --zone=public --permanent --add-port=6379/tcp
firewall-cmd --zone=public --permanent --add-port=6378/tcp
firewall-cmd --zone=public --permanent --add-port=6377/tcp
firewall-cmd --zone=public --permanent --add-port=6376/tcp
# 重新载入规则
firewall-cmd --reload

 

读写分离实现

通过自定义LettuceConnectionFactory实现

配置YAML

spring:
  redis:
    database: 5
    password: 123456
    host: 192.168.0.100
    port: 6378
    # 主从复制读写分类配置
    replicaNodeList:
      - host: 192.168.0.100
        port: 6377
      - host: 192.168.0.100
        port: 6376
    timeout: 60000ms
    lettuce:
      pool:
        max-active: 100
        max-idle: 20
        max-wait: 3000ms

LettuceConnectionFactory

@Bean
@ConditionalOnList(name = "spring.redis.replicaNodeList", prop = {"host", "port"})
public LettuceConnectionFactory redisConnectionFactory(RedisConfigurationInformation redisConfigurationInformation) {
    RedisStaticMasterReplicaConfiguration redisConfig = new RedisStaticMasterReplicaConfiguration(redisConfigurationInformation.getHost(), redisConfigurationInformation.getPort());
    redisConfig.setDatabase(redisConfigurationInformation.getDatabase());
    redisConfig.setPassword(redisConfigurationInformation.getPassword());
    for (ReplicaNode node : redisConfigurationInformation.getReplicaNodeList()) {
        redisConfig.addNode(node.getHost(), node.getPort());
    }
    ReadFromCustomized readFrom = new ReadFromCustomized();
    LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder().readFrom(readFrom).build();
    return new LettuceConnectionFactory(redisConfig, clientConfig);
}

ReadFrom

import cn.hutool.core.collection.CollectionUtil;
import io.lettuce.core.ReadFrom;
import io.lettuce.core.RedisURI;
import io.lettuce.core.internal.LettuceLists;
import io.lettuce.core.models.role.RedisInstance;
import io.lettuce.core.models.role.RedisNodeDescription;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;

/**
 * redis 读信息
 *
 * @author ycx
 */
public class ReadFromCustomized extends ReadFrom {
    @Override
    public List<RedisNodeDescription> select(Nodes nodes) {
        List<RedisNodeDescription> masterList = new ArrayList<>();
        List<RedisNodeDescription> slaveList = new ArrayList<>();
        for (RedisNodeDescription node : nodes) {
            if (node.getRole() == RedisInstance.Role.MASTER) {
                masterList.add(node);
            } else if (node.getRole() == RedisInstance.Role.SLAVE) {
                slaveList.add(node);
            }
        }
        if (CollectionUtil.isNotEmpty(slaveList)) {
            Random random = new Random();
            int index = random.nextInt(slaveList.size());
            return LettuceLists.newList(slaveList.get(index));
        }
        if (CollectionUtil.isNotEmpty(masterList)) {
            return LettuceLists.newList(masterList.get(0));
        }
        return Collections.emptyList();
    }
}

@ConditionalOnList注解实现

import org.springframework.context.annotation.Conditional;

import java.lang.annotation.*;

/**
 * 列表条件注解
 *
 * @author ycx
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(OnListCondition.class)
public @interface ConditionalOnList {
    String name() default "";
    String[] prop() default {};
}

注解实现类

import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.MultiValueMap;

import java.util.*;

/**
 * 列表条件实现
 *
 * @author ycx
 */
@Slf4j
@Order(Ordered.HIGHEST_PRECEDENCE + 40)
public class OnListCondition extends SpringBootCondition {
    @Override
    public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
        List<AnnotationAttributes> allAnnotationAttributes = annotationAttributesFromMultiValueMap(metadata.getAllAnnotationAttributes(ConditionalOnList.class.getName()));
        String name = null;
        String[] prop = null;
        for (AnnotationAttributes annotationAttributes : allAnnotationAttributes) {
            name = annotationAttributes.getString("name");
            prop = (String[]) annotationAttributes.get("prop");
        }
        if (Objects.isNull(name) || "".equals(name.trim())) {
            throw new IllegalStateException("The name attribute of @ConditionalOnList must be specified");
        }
        if (Objects.isNull(prop) || prop.length == 0) {
            throw new IllegalStateException("The prop attribute of @ConditionalOnList must be specified");
        }
        List<String> keys = new ArrayList<>(prop.length);
        for (String item : prop) {
            if (Objects.isNull(item) || "".equals(item.trim())) {
                throw new IllegalStateException("The prop attribute of @ConditionalOnList must be nonempty element");
            }
            keys.add(name + "[0]." + item);
        }
        Environment environment = context.getEnvironment();
        boolean hasValue = false;
        for (String key : keys) {
            String value = environment.getProperty(key);
            if (Objects.nonNull(value)) {
                hasValue = true;
                break;
            }
        }
        if (hasValue) {
            for (String key : keys) {
                String value = environment.getProperty(key);
                if (StrUtil.isEmpty(value)) {
                    throw new IllegalStateException("The value of prop(" + key + ") attribute of @ConditionalOnList must be specified");
                }
            }
            log.info("【Redis】启动主从复制读写分离");
            System.out.println("【Redis】启动主从复制读写分离");
            return new ConditionOutcome(true, "启动主从复制读写分离 ");
        }
        log.info("【Redis】启动单节点");
        System.out.println("【Redis】启动单节点");
        return new ConditionOutcome(false, "启动单节点");
    }

    private List<AnnotationAttributes> annotationAttributesFromMultiValueMap(
            MultiValueMap<String, Object> multiValueMap) {
        List<Map<String, Object>> maps = new ArrayList<>();
        multiValueMap.forEach((key, value) -> {
            for (int i = 0; i < value.size(); i++) {
                Map<String, Object> map;
                if (i < maps.size()) {
                    map = maps.get(i);
                }
                else {
                    map = new HashMap<>();
                    maps.add(map);
                }
                map.put(key, value.get(i));
            }
        });
        List<AnnotationAttributes> annotationAttributes = new ArrayList<>(maps.size());
        for (Map<String, Object> map : maps) {
            annotationAttributes.add(AnnotationAttributes.fromMap(map));
        }
        return annotationAttributes;
    }
}

 

posted @ 2022-12-04 16:52  翠微  阅读(374)  评论(0编辑  收藏  举报