Redis主从哨兵模式连接踩坑全记录
Redis主从哨兵模式连接踩坑全记录
在分布式系统开发中,Redis主从哨兵模式是保障缓存高可用的常用架构,但实际配置过程中,往往会因密码认证、客户端适配等细节问题陷入困境。本次就分享一次从项目启动报错到最终稳定运行的完整踩坑经历,希望能为遇到类似问题的开发者提供参考。
一、背景与初始环境
项目采用Spring Boot开发,使用Redis实现缓存和分布式锁功能,Redis部署架构为「1主1从2哨兵」:
-
主节点:10.97.0.44:6379(配置密码:Catarc@123#Foton)
-
从节点:10.97.0.45:6379(未配置密码)
-
哨兵节点:10.97.0.44:26379、10.97.0.45:26379(未配置密码)
-
客户端:同时使用Lettuce(Spring Data Redis默认)和Redisson(分布式锁)
核心需求:基于自动配置实现Redis连接,确保缓存读写和分布式锁功能正常。
二、问题排查与解决过程
原来的配置redission配置
redisson:
server-address: redis://10.97.0.44
port: 6379
password: Catarc@123#Foton
修改后的配置
redisson:
master-name: mymaster
sentinel-addresses: 10.97.0.44:26379,10.97.0.45:26379
password: Catarc@123#Foton
sentinel-password: "Catarc@123#Foton"
timeout: 2000
增加配置类
package com.mom.maindata.config;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.redisson.config.SentinelServersConfig;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;
@Configuration
@ConfigurationProperties(prefix = "redisson")
public class RedissonConfig {
private String masterName;
private String sentinelAddresses;
private String password; // 主从节点的密码(Catarc@123#Foton)
private int timeout = 2000;
private int connectTimeout = 3000;
private int retryAttempts = 3;
private int retryInterval = 1000;
// 所有getter/setter保持不变
public String getMasterName() { return masterName; }
public void setMasterName(String masterName) { this.masterName = masterName; }
public String getSentinelAddresses() { return sentinelAddresses; }
public void setSentinelAddresses(String sentinelAddresses) { this.sentinelAddresses = sentinelAddresses; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public int getTimeout() { return timeout; }
public void setTimeout(int timeout) { this.timeout = timeout; }
public int getConnectTimeout() { return connectTimeout; }
public void setConnectTimeout(int connectTimeout) { this.connectTimeout = connectTimeout; }
public int getRetryAttempts() { return retryAttempts; }
public void setRetryAttempts(int retryAttempts) { this.retryAttempts = retryAttempts; }
public int getRetryInterval() { return retryInterval; }
public void setRetryInterval(int retryInterval) { this.retryInterval = retryInterval; }
@Bean
public RedissonClient redissonClient() {
if (!StringUtils.hasText(masterName) || !StringUtils.hasText(sentinelAddresses)) {
throw new RuntimeException("Redisson哨兵配置不全!");
}
Config config = new Config();
SentinelServersConfig sentinelConfig = config.useSentinelServers()
.setMasterName(masterName)
.setTimeout(timeout)
.setConnectTimeout(connectTimeout)
.setRetryAttempts(retryAttempts)
.setRetryInterval(retryInterval)
// 核心新增:哨兵节点不执行密码验证(设为null,跳过AUTH)
.setSentinelPassword(password)
// 主从节点执行密码验证(你的Catarc@123#Foton)
.setPassword(password);
// 添加哨兵地址(自动补全redis://前缀)
String[] addresses = sentinelAddresses.split(",");
for (String addr : addresses) {
String finalAddr = addr.trim();
if (!finalAddr.startsWith("redis://")) {
finalAddr = "redis://" + finalAddr;
}
sentinelConfig.addSentinelAddress(finalAddr);
}
System.out.println("=== Redisson配置完成:主从密码验证,哨兵跳过验证 ===");
return Redisson.create(config);
}
}
原来的redis依赖
<dependency>
<groupId>com.catarc</groupId>
<artifactId>common-redis</artifactId>
<version>${mom.version}</version>
<exclusions>
<exclusion>
<artifactId>common-core</artifactId>
<groupId>com.catarc</groupId>
</exclusion>
</exclusions>
</dependency>
本次问题排查共经历3个核心阶段,每个阶段对应不同的报错的和解决思路,逐步定位到根源问题。
阶段1:启动报错 - 加载了默认的RedissonConfig配置类
1.1 关键报错信息
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'boxDetailServiceImpl' defined in file [D:\workplace\kdms\maindata-boot\target\classes\com\mom\maindata\service\impl\BoxDetailServiceImpl.class]: Unsatisfied dependency expressed through constructor parameter 2; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'twInvSkuLocTLDetailServiceImpl': Unsatisfied dependency expressed through field 'redissonClient'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.redisson.api.RedissonClient' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:800)
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:229)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1354)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1204)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:564)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:524)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1380)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1300)
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:887)
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791)
... 23 common frames omitted
1.2 问题定位
加载了默认的配置类com.mom.common.redis.redisson.RedissonConfig.class
1.3 解决方案:排除默认配置类
启动类上增加
@SpringBootApplication(exclude = {com.mom.common.redis.redisson.RedissonConfig.class})
阶段2:启动报错 - 从节点无密码导致Redisson连接失败
2.1 关键报错信息
org.redisson.client.RedisException: ERR AUTH <password> called without any password configured for the default user.
channel: [id: 0x81d4ccca, L:/10.35.84.244:52151 - R:/10.97.0.45:6379]
command: (AUTH), params: (password masked)
2.2 问题定位
从报错日志可见,Redisson客户端向从节点(10.97.0.45:6379)发送了AUTH密码验证命令,但从节点未配置密码,导致认证失败。核心原因是「主从节点密码不一致」,Redisson使用统一密码连接所有节点时适配失败。
2.3 解决方案:给从节点配置与主节点一致的密码
登录从节点执行以下命令,设置密码并持久化配置:
# 连接从节点Redis
redis-cli -h 10.97.0.45 -p 6379
# 设置与主节点相同的密码
10.97.0.45:6379> CONFIG SET requirepass "Catarc@123#Foton"
# 验证密码生效
10.97.0.45:6379> AUTH Catarc@123#Foton # 返回OK则生效
# 持久化配置(避免重启丢失)
10.97.0.45:6379> CONFIG REWRITE
# 重启从节点Redis服务(Docker环境示例)
docker restart redis-slave
操作完成后,主从节点密码统一,Redisson连接主从节点的报错消失,但新的问题随之出现。
阶段3:持续报错 - Redisson向哨兵节点发送AUTH命令
3.1 关键报错信息
org.redisson.client.RedisException: ERR AUTH <password> called without any password configured for the default user.
channel: [id: 0xe50674da, L:/10.35.84.244:57695 - R:/10.97.0.45:26379]
command: (AUTH), params: (password masked)
注意到此次报错的目标节点是哨兵节点(26379端口),而非主从节点(6379端口)。
3.2 问题定位
Redisson默认会将主从节点的密码透传给哨兵节点,若哨兵节点未配置密码,会触发AUTH报错。虽然项目启动正常,但持续报错会占用日志资源,且可能影响哨兵集群状态更新。核心原因是「哨兵节点未配置密码,但Redisson默认执行认证」。
3.3 解决方案:给哨兵节点配置密码并适配客户端
此问题有两种解决思路:一是禁用Redisson对哨兵的认证(低版本可能存在bug),二是给哨兵节点配置密码(推荐,符合生产规范)。本次选择方案二,步骤如下:
步骤1:修改哨兵配置文件(redis-sentinel.conf)
在44和45节点的哨兵配置中新增自身登录密码,完整配置如下:
# 哨兵端口
port 26379
# Docker中禁用后台运行
daemonize no
# 新增:哨兵节点自身登录密码(与主从一致)
requirepass "Catarc@123#Foton"
# 监控主节点:名称、主节点IP、端口、故障判定阈值
sentinel monitor mymaster 10.97.0.44 6379 1
# 判定主节点主观下线的超时时间(5秒)
sentinel down-after-milliseconds mymaster 5000
# 故障转移时,最多1个从节点同时同步新主节点数据
sentinel parallel-syncs mymaster 1
# 故障转移超时时间(60秒)
sentinel failover-timeout mymaster 60000
# 哨兵访问主节点的密码(与主节点一致)
sentinel auth-pass mymaster "Catarc@123#Foton"
关键说明:requirepass是哨兵节点自身的登录密码,sentinel auth-pass是哨兵访问主节点的密码,两者需与主从密码一致。
步骤2:重启哨兵服务(Docker环境)
# 停止现有哨兵容器
docker stop redis-sentinel
# 启动哨兵容器(挂载修改后的配置文件)
docker run -d \
--name redis-sentinel \
-p 26379:26379 \
-v /你的配置路径/redis-sentinel.conf:/etc/redis/sentinel.conf \
redis:latest \
redis-sentinel /etc/redis/sentinel.conf
步骤4:验证哨兵密码生效
# 连接哨兵节点
redis-cli -h 10.97.0.44 -p 26379
# 执行命令提示需要认证
10.97.0.44:26379> PING # 返回 (error) NOAUTH Authentication required.
# 输入密码认证
10.97.0.44:26379> AUTH "Catarc@123#Foton" # 返回OK则生效
阶段4:新报错 - Lettuce客户端连接哨兵失败
给哨兵配置密码后,Redisson报错消失,但出现Lettuce客户端连接失败的新问题。
4.1 关键报错信息
org.springframework.data.redis.RedisConnectionFailureException: Unable to connect to Redis;
nested exception is org.springframework.data.redis.connection.PoolException: Could not get a resource from the pool;
nested exception is io.lettuce.core.RedisConnectionException: Cannot connect to a Redis Sentinel: [redis://10.97.0.44:26379?timeout=2s, redis://10.97.0.45:26379?timeout=2s]
Caused by: io.lettuce.core.RedisCommandExecutionException: NOAUTH HELLO must be called with the client already authenticated...
4.2 问题定位
Lettuce是Spring Data Redis的默认客户端,项目配置中仅设置了主从节点密码(spring.redis.password),未配置哨兵节点密码,导致Lettuce连接哨兵时因未认证失败。核心原因是「Spring Redis配置遗漏哨兵密码」。
4.3 解决方案:补充Spring Redis和Redisson的哨兵密码配置
修改项目application.yml配置文件,补充哨兵密码及Lettuce客户端优化配置,关键修改如下:
spring:
redis:
timeout: 2000ms # 补充单位,避免解析异常
sentinel:
master: mymaster # 与哨兵配置一致
nodes: 10.97.0.44:26379,10.97.0.45:26379 # 哨兵节点列表
password: "Catarc@123#Foton" # 新增:哨兵节点登录密码
password: "Catarc@123#Foton" # 主从节点密码(加双引号处理特殊字符)
lettuce:
client-options: # 修正参数名称,适配Spring Boot规范
redis:
sentinel:
refresh-period: 1000ms # 哨兵信息刷新周期
cluster:
refresh:
adaptive: true # 自适应拓扑刷新
period: 2000ms # 拓扑刷新周期
connect-timeout: 2000ms
disconnect-on-reconnect-failure: false # 允许重连
auto-reconnect: true # 自动重连
pool:
max-active: 16
max-idle: 8
min-idle: 4
max-wait: -1ms
# Redisson配置(显式配置哨兵密码,适配低版本)
redisson:
master-name: mymaster
sentinel-addresses: 10.97.0.44:26379,10.97.0.45:26379
password: "Catarc@123#Foton" # 主从密码
sentinel-password: "Catarc@123#Foton" # 新增:哨兵密码
timeout: 2000ms
三、最终验证
修改完成后重启项目,进行以下验证:
-
项目启动无报错,日志中无AUTH相关异常;
-
缓存功能正常:可正常读写Redis缓存数据;
-
分布式锁功能正常:调用/test/lock接口能成功获取和释放锁;
-
哨兵集群状态正常:Redisson能正常更新集群状态,故障转移功能可用。
四、核心经验与总结
本次问题的核心是「密码配置不完整」和「客户端适配问题」,总结以下关键经验,避免后续踩坑:
1. 主从哨兵密码统一是关键
Redis主从哨兵模式中,建议主节点、从节点、哨兵节点使用相同的密码,避免因密码不一致导致的认证失败。生产环境中,密码需包含特殊字符并妥善保管。
2. 客户端配置需区分主从与哨兵
-
Spring Redis(Lettuce):需同时配置
spring.redis.password(主从)和spring.redis.sentinel.password(哨兵); -
Redisson:低版本需显式配置
sentinel-password,避免密码透传问题; -
特殊字符处理:密码含@、#等特殊字符时,需用双引号包裹,避免配置解析异常。
3. 报错日志是定位问题的核心工具
遇到Redis连接问题时,重点关注以下信息:
-
报错目标节点的IP和端口(区分主从6379和哨兵26379);
-
核心错误信息(如ERR AUTH、NOAUTH);
-
客户端类型(Redisson或Lettuce)。
4. 配置规范与持久化不可忽视
修改Redis/哨兵配置后,需执行CONFIG REWRITE持久化配置,避免重启后配置丢失;Docker环境下,需确保配置文件正确挂载,防止容器重启后配置重置。
通过本次问题排查,深刻体会到分布式架构中「细节决定成败」,看似简单的密码配置,若考虑不周就会导致整个缓存模块异常。希望本文的踩坑经历能帮助大家少走弯路,顺利实现Redis主从哨兵模式的稳定部署。
浙公网安备 33010602011771号