【Redis】主从配置和读写分离实现
主从配置
docker pull redis:6.0.3
Master配置修改
IP:192.168.0.100,端口:6378
bind 0.0.0.0
port 6378
# 密码验证
requirepass 123456
# 守护进程
daemonize yes
# 关闭持久化
appendonly no
save ""
# 允许远程连接
protected-mode no
# 不使用diskless同步方式
repl-diskless-sync no
SlaveA配置修改
IP:192.168.0.100,端口:6377
bind 0.0.0.0
port 6377
# 密码验证
requirepass 123456
# 守护进程
daemonize yes
# 关闭持久化
appendonly no
save ""
# 允许远程连接
protected-mode no
# 主密码
masterauth 123456
# 主服务器的地址
replicaof 192.168.0.100 6377
# 从机只读模式默认是开启的
replica-read-only yes
SlaveB配置修改
IP:192.168.0.100,端口:6376
bind 0.0.0.0
port 6376
# 密码验证
requirepass 123456
# 守护进程
daemonize yes
# 关闭持久化
appendonly no
save ""
# 允许远程连接
protected-mode no
# 主密码
masterauth 123456
# 主服务器的地址
replicaof 192.168.0.100 6376
# 从机只读模式默认是开启的
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; } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南