-
Notifications
You must be signed in to change notification settings - Fork 4.6k
feat(redis): support redis sentinel configuration #41568
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
c0f23bd
0bb2e21
f6f4349
b6795a6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,6 +8,8 @@ | |
| import io.lettuce.core.AbstractRedisClient; | ||
| import io.lettuce.core.ClientOptions; | ||
| import io.lettuce.core.RedisClient; | ||
| import io.lettuce.core.RedisCredentials; | ||
| import io.lettuce.core.RedisURI; | ||
| import io.lettuce.core.TimeoutOptions; | ||
| import io.lettuce.core.cluster.ClusterClientOptions; | ||
| import io.lettuce.core.cluster.RedisClusterClient; | ||
|
|
@@ -24,6 +26,7 @@ | |
| import org.springframework.data.redis.connection.RedisConfiguration; | ||
| import org.springframework.data.redis.connection.RedisNode; | ||
| import org.springframework.data.redis.connection.RedisPassword; | ||
| import org.springframework.data.redis.connection.RedisSentinelConfiguration; | ||
| import org.springframework.data.redis.connection.RedisStandaloneConfiguration; | ||
| import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration; | ||
| import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; | ||
|
|
@@ -59,6 +62,9 @@ public class RedisConfig { | |
| @Value("${appsmith.redis.url:}") | ||
| private String redisURL; | ||
|
|
||
| @Value("${appsmith.redis.sentinel.master:}") | ||
| private String redisSentinelMaster; | ||
|
|
||
| /** | ||
| * This is the topic to which we will publish & subscribe to. We can have multiple topics based on the messages | ||
| * that we wish to broadcast. Starting with a single one for now. | ||
|
|
@@ -78,13 +84,15 @@ public ReactiveRedisConnectionFactory reactiveRedisConnectionFactory() { | |
|
|
||
| switch (scheme) { | ||
| case "redis" -> { | ||
| log.info("Creating Redis standalone connection configuration"); | ||
| final RedisStandaloneConfiguration config = | ||
| new RedisStandaloneConfiguration(redisUri.getHost(), redisUri.getPort()); | ||
| fillAuthentication(redisUri, config); | ||
| return new LettuceConnectionFactory(config); | ||
| } | ||
|
|
||
| case "rediss" -> { | ||
| log.info("Creating Redis standalone SSL connection configuration"); | ||
| final RedisStandaloneConfiguration config = | ||
| new RedisStandaloneConfiguration(redisUri.getHost(), redisUri.getPort()); | ||
| fillAuthentication(redisUri, config); | ||
|
|
@@ -93,7 +101,20 @@ public ReactiveRedisConnectionFactory reactiveRedisConnectionFactory() { | |
| return new LettuceConnectionFactory(config, clientConfig); | ||
| } | ||
|
|
||
| case "redis-sentinel" -> { | ||
| log.info("Creating Redis Sentinel connection configuration"); | ||
| validateSentinelMaster(); | ||
| final RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration() | ||
| .master(redisSentinelMaster) | ||
| .sentinel(redisUri.getHost(), redisUri.getPort()); | ||
| fillAuthentication(redisUri, sentinelConfig); | ||
| final LettuceClientConfiguration clientConfig = | ||
| LettuceClientConfiguration.builder().build(); | ||
| return new LettuceConnectionFactory(sentinelConfig, clientConfig); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. QQ: Does it provide connection pooling out of the box? Or is it not covered in the current implementation? |
||
| } | ||
|
|
||
| case "redis-cluster" -> { | ||
| log.info("Creating Redis Cluster connection configuration"); | ||
| // For ElastiCache Redis with cluster mode enabled, with the configuration endpoint. | ||
| final RedisClusterConfiguration clusterConfig = new RedisClusterConfiguration(); | ||
| fillAuthentication(redisUri, clusterConfig); | ||
|
|
@@ -112,16 +133,13 @@ public AbstractRedisClient redisClient() { | |
| String redisurl = redisURL; | ||
| final URI redisUri = URI.create(redisURL); | ||
| String scheme = redisUri.getScheme(); | ||
| boolean isCluster = false; | ||
|
|
||
| if ("redis-cluster".equalsIgnoreCase(scheme)) { | ||
| isCluster = true; | ||
| // java clients do not support redis-cluster scheme | ||
| if (redisurl.startsWith("redis-cluster://")) { | ||
| redisurl = "redis://" + redisurl.substring("redis-cluster://".length()); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The existing code has |
||
| } | ||
| } | ||
|
|
||
| if (isCluster) { | ||
| RedisClusterClient redisClusterClient = RedisClusterClient.create(redisurl); | ||
| redisClusterClient.setOptions(ClusterClientOptions.builder() | ||
| .timeoutOptions(TimeoutOptions.builder() | ||
|
|
@@ -132,6 +150,24 @@ public AbstractRedisClient redisClient() { | |
| return redisClusterClient; | ||
| } | ||
|
|
||
| if ("redis-sentinel".equalsIgnoreCase(scheme)) { | ||
| validateSentinelMaster(); | ||
| // Create new RedisURI with Sentinel configuration | ||
| RedisURI.Builder uriBuilder = RedisURI.builder() | ||
| .withSentinelMasterId(redisSentinelMaster) | ||
| .withSentinel(redisUri.getHost(), redisUri.getPort()); | ||
|
|
||
| fillAuthentication(redisUri, uriBuilder); | ||
| RedisClient redisClient = RedisClient.create(uriBuilder.build()); | ||
| redisClient.setOptions(ClientOptions.builder() | ||
| .timeoutOptions(TimeoutOptions.builder() | ||
| .timeoutCommands(true) | ||
| .fixedTimeout(Duration.ofMillis(2000)) | ||
| .build()) | ||
| .build()); | ||
| return redisClient; | ||
| } | ||
|
|
||
| RedisClient redisClient = RedisClient.create(redisurl); | ||
| redisClient.setOptions(ClientOptions.builder() | ||
| .timeoutOptions(TimeoutOptions.builder() | ||
|
|
@@ -144,11 +180,32 @@ public AbstractRedisClient redisClient() { | |
| } | ||
|
|
||
| private void fillAuthentication(URI redisUri, RedisConfiguration.WithAuthentication config) { | ||
| RedisCredentials credentials = extractCredentials(redisUri); | ||
| if (credentials != null) { | ||
| config.setUsername(credentials.getUsername()); | ||
| config.setPassword(RedisPassword.of(credentials.getPassword())); | ||
| } | ||
| } | ||
|
|
||
| private void fillAuthentication(URI redisUri, RedisURI.Builder uriBuilder) { | ||
| RedisCredentials credentials = extractCredentials(redisUri); | ||
| if (credentials != null) { | ||
| uriBuilder.withAuthentication(credentials.getUsername(), credentials.getPassword()); | ||
| } | ||
| } | ||
|
|
||
| private RedisCredentials extractCredentials(URI redisUri) { | ||
| final String userInfo = redisUri.getUserInfo(); | ||
| if (StringUtils.isNotEmpty(userInfo)) { | ||
| final String[] parts = userInfo.split(":", 2); | ||
| config.setUsername(parts[0]); | ||
| config.setPassword(RedisPassword.of(parts.length > 1 ? parts[1] : null)); | ||
| return RedisCredentials.just(parts[0], parts.length > 1 ? parts[1] : null); | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| private void validateSentinelMaster() { | ||
| if (StringUtils.isEmpty(redisSentinelMaster)) { | ||
| throw new IllegalStateException("Redis Sentinel Master is not configured"); | ||
| } | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -67,6 +67,7 @@ sentry.environment=${APPSMITH_SERVER_SENTRY_ENVIRONMENT:} | |
| # Redis Properties | ||
| appsmith.redis.url=${APPSMITH_REDIS_URL} | ||
| appsmith.redis.git.url=${APPSMITH_REDIS_GIT_URL:${APPSMITH_REDIS_URL}} | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Concern: If a user sets |
||
| appsmith.redis.sentinel.master=${APPSMITH_REDIS_SENTINEL_MASTER:} | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No Helm/Docker documentation update: I think |
||
|
|
||
| # Mail Properties | ||
| # Email defaults to false, because, when true and the other SMTP properties are not set, Spring will try to use a | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current implementation only supports a single sentinel node. In production, Redis Sentinel deployments typically have 3+ sentinel nodes for quorum. A single sentinel node defeats the purpose of Sentinel (if that one sentinel goes down, the client can't discover the master).