【Redis】主从配置和读写分离实现
主从配置
Master配置修改
IP:192.168.0.100,端口:6378
bind 0.0.0.0 port 6378 requirepass 123456 # 关闭持久化 appendonly nosave "" # 允许远程连接 protected-mode no
SlaveA配置修改
IP:192.168.0.100,端口:6377
bind 0.0.0.0 port 6377 requirepass 123456 # 主密码 masterauth 123456 # 关闭持久化 appendonly nosave "" # 允许远程连接 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 nosave "" # 允许远程连接 protected-mode no # 主服务器的地址 replicaof 192.168.0.100 6378 # 从机只读模式默认是开启的 replica-read-only yes
查看主redis服务
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"
查看从redis服务
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 "
读写分离实现
通过自定义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
@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); }
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(); } }
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; } }
学习时的痛苦是暂时的 未学到的痛苦是终生的