-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Open
Description
Bug Report
Current Behavior
Intermittent Error / Exception when using RedisClusterConfiguration to Connect with Azure Managed Redis with High Availablity enabled.
Stack trace
io.lettuce.core.RedisCommandExecutionException: ERR unknown subcommand 'MYID'
at io.lettuce.core.internal.ExceptionFactory.createExecutionException(ExceptionFactory.java:151)
at io.lettuce.core.internal.ExceptionFactory.createExecutionException(ExceptionFactory.java:120)
at io.lettuce.core.protocol.AsyncCommand.completeResult(AsyncCommand.java:124)
at io.lettuce.core.protocol.AsyncCommand.complete(AsyncCommand.java:115)
at io.lettuce.core.protocol.CommandWrapper.complete(CommandWrapper.java:67)
at io.lettuce.core.protocol.CommandWrapper.complete(CommandWrapper.java:67)
at io.lettuce.core.cluster.ClusterCommand.complete(ClusterCommand.java:50)
at io.lettuce.core.protocol.CommandWrapper.complete(CommandWrapper.java:67)
at io.lettuce.core.protocol.CommandWrapper.complete(CommandWrapper.java:67)
at io.lettuce.core.protocol.CommandHandler.complete(CommandHandler.java:769)
at io.lettuce.core.protocol.CommandHandler.decode(CommandHandler.java:704)
at io.lettuce.core.protocol.CommandHandler.channelRead(CommandHandler.java:621)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1519)
at io.netty.handler.ssl.SslHandler.decodeJdkCompatible(SslHandler.java:1377)
at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1428)
at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:530)
at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:469)
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:290)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1357)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:868)
at io.netty.channel.epoll.AbstractEpollStreamChannel$EpollStreamUnsafe.epollInReady(AbstractEpollStreamChannel.java:799)
at io.netty.channel.epoll.EpollEventLoop.processReady(EpollEventLoop.java:501)
at io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:399)
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:998)
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base/java.lang.Thread.run(Unknown Source)Input Code
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@CustomLog
@Configuration
@EnableCaching
@EnableRetry
public class ApplicationCacheConfiguration {
@Bean(name = REDIS_CACHE_MANAGER)
@Primary
public CacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory,
RedisCacheConfiguration customerCacheConfiguration) {
Map<String, RedisCacheConfiguration> cacheConfigurations = new HashMap<>();
cacheConfigurations.put(CUSTOMER_CACHE, customerCacheConfiguration);
return RedisCacheManager.builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory))
.withInitialCacheConfigurations(cacheConfigurations)
.build();
}
@Bean
public RedisCacheConfiguration customerCacheConfiguration() {
// This is the recommended way to specify a whitelist for a small range
return RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new FastJsonRedisSerializer<>(Customer.class)))
.entryTtl(Duration.ofSeconds(redisCustomerCacheTtlInSeconds));
}
@Bean
public RedisConnectionFactory redisConnectionFactory(LettucePoolingClientConfiguration lettucePoolingClientConfiguration) {
RedisConfiguration redisConfiguration;
if (useRedisSentinel) {
RedisSentinelConfiguration sentinelConfiguration = new RedisSentinelConfiguration()
.master("mymaster") // Needs to be in sync with the Redis Helm configuration
.sentinel(redisHostName, redisPort); // Note: redisPort == sentinelPort in this case
sentinelConfiguration.setPassword(RedisPassword.of(redisPassword));
sentinelConfiguration.setSentinelPassword(RedisPassword.of(redisPassword));
redisConfiguration = sentinelConfiguration;
} else if (useCluster) {
// When useRedisSentinel is false and useCluster is true, connect to Azure Managed Redis in cluster mode
RedisClusterConfiguration clusterConfiguration = new RedisClusterConfiguration();
clusterConfiguration.addClusterNode(new RedisClusterNode(redisHostName, redisPort));
clusterConfiguration.setPassword(RedisPassword.of(azureRedisPassword));
redisConfiguration = clusterConfiguration;
} else {
// Standalone Azure Managed Redis
RedisStandaloneConfiguration standaloneConfiguration = new RedisStandaloneConfiguration(redisHostName, redisPort);
standaloneConfiguration.setPassword(RedisPassword.of(azureRedisPassword));
redisConfiguration = standaloneConfiguration;
}
LettuceConnectionFactory lettuceConnectionFactory = new
LettuceConnectionFactory(redisConfiguration, lettucePoolingClientConfiguration);
// remember the app settings
lettuceConnectionFactory.afterPropertiesSet();
log.atInfo()
.with(ApplicationConstants.LOG_SESSION_CACHE_SIZE, JSON.toJSONString(lettucePoolingClientConfiguration))
.log("Building lettuce connection pool");
return lettuceConnectionFactory;
}
@Bean
public LettucePoolingClientConfiguration lettucePoolingClientConfiguration(GenericObjectPoolConfig<StatefulConnection<?, ?>> genericObjectPoolConfig) {
SocketOptions socketOptions = SocketOptions.builder()
.connectTimeout(Duration.ofSeconds(redisConnectionTimeoutInSeconds))
.keepAlive(true) // Enable TCP keepalive for long-lived connections
.build();
ClientOptions clientOptions = ClientOptions.builder()
.socketOptions(socketOptions)
.autoReconnect(true)
.disconnectedBehavior(ClientOptions.DisconnectedBehavior.REJECT_COMMANDS)
.build();
LettucePoolingClientConfiguration.LettucePoolingClientConfigurationBuilder lettucePoolingClientConfigurationBuilder = LettucePoolingClientConfiguration.builder()
.clientOptions(clientOptions)
.commandTimeout(Duration.ofSeconds(redisCommandTimeoutInSeconds))
.poolConfig(genericObjectPoolConfig);
if (useRedisSentinel) {
// Only in real Sentinel environments
lettucePoolingClientConfigurationBuilder.readFrom(ReadFrom.REPLICA_PREFERRED);
}
// Enable SSL/TLS for Azure Redis Enterprise
if (isSslEnabled) {
lettucePoolingClientConfigurationBuilder
.useSsl()
.disablePeerVerification(); // Required for Azure private endpoints with self-signed certs
}
return lettucePoolingClientConfigurationBuilder.build();
}
@Bean
@SuppressWarnings("java:S1452")
public GenericObjectPoolConfig<StatefulConnection<?, ?>> genericObjectPoolConfig() {
GenericObjectPoolConfig<StatefulConnection<?, ?>> poolConfig = new GenericObjectPoolConfig<>();
poolConfig.setMaxIdle(maxIdle);
poolConfig.setMinIdle(minIdle);
poolConfig.setMaxWait(Duration.ofMillis(maxWaitMillis));
poolConfig.setMaxTotal(maxTotal);
// Validation & eviction for stale connections
poolConfig.setTestWhileIdle(true);
poolConfig.setTimeBetweenEvictionRuns(Duration.ofMinutes(5));
return poolConfig;
}
public boolean isClubMetadataCacheEnabled() {
return isDbCacheEnabled;
}
}Input Code
import org.springframework.data.redis.connection.*;
RedisClusterConfiguration clusterConfiguration = new RedisClusterConfiguration();
clusterConfiguration.addClusterNode(new RedisClusterNode(redisHostName, redisPort));
clusterConfiguration.setPassword(RedisPassword.of(azureRedisPassword));
redisConfiguration = clusterConfiguration;Expected behavior/code
No MYID Error when using RedisClusterConfiguration
Environment
- Java 25
- Spring Boot Version 3.5.7
- Azure Managed Redis with High Availability Enabled and with OSSCluster policy
- Lettuce version(s): 6.6.0.RELEASE
- Redis version: 7.4