From 6c7ec746e910b4e3c2ae71d31a61a65eabe5fb26 Mon Sep 17 00:00:00 2001 From: Shrikant Sharat Kandula Date: Fri, 30 Jun 2023 17:31:47 +0530 Subject: [PATCH] feat: Add support for connecting to Redis clusters (#24924) This PR adds support to Appsmith server to be able to connect to ElastiCache Redis with cluster mode turned on. When the `APPSMITH_REDIS_URL` is set to `redis://...`, the current default behaviour is preserved. But if it is set to `redis-cluster://...`, then we setup a pooled connection with cluster mod enabled. --- app/server/appsmith-server/pom.xml | 10 ++++- .../server/configurations/RedisConfig.java | 43 +++++++++++++++++++ .../src/main/resources/application.properties | 2 +- 3 files changed, 53 insertions(+), 2 deletions(-) diff --git a/app/server/appsmith-server/pom.xml b/app/server/appsmith-server/pom.xml index c10a768579..bd0c0081b3 100644 --- a/app/server/appsmith-server/pom.xml +++ b/app/server/appsmith-server/pom.xml @@ -224,6 +224,14 @@ test + + + org.apache.commons + commons-pool2 + 2.11.1 + runtime + + org.junit.platform junit-platform-suite-engine @@ -455,4 +463,4 @@ - \ No newline at end of file + diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/RedisConfig.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/RedisConfig.java index 26f65109f4..a5366908bb 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/RedisConfig.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/RedisConfig.java @@ -8,10 +8,17 @@ import com.fasterxml.jackson.databind.json.JsonMapper; import io.lettuce.core.resource.ClientResources; import io.micrometer.observation.ObservationRegistry; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory; +import org.springframework.data.redis.connection.RedisClusterConfiguration; +import org.springframework.data.redis.connection.RedisNode; +import org.springframework.data.redis.connection.RedisSentinelConfiguration; +import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration; import org.springframework.data.redis.connection.lettuce.observability.MicrometerTracingAdapter; import org.springframework.data.redis.core.ReactiveRedisOperations; import org.springframework.data.redis.core.ReactiveRedisTemplate; @@ -28,9 +35,11 @@ import org.springframework.security.core.context.SecurityContextImpl; import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; import org.springframework.session.data.redis.config.annotation.web.server.EnableRedisWebSession; +import java.net.URI; import java.util.Arrays; import java.util.HashMap; import java.util.Map; +import java.util.Set; @Configuration @Slf4j @@ -38,6 +47,9 @@ import java.util.Map; @EnableRedisWebSession(maxInactiveIntervalInSeconds = 2592000) public class RedisConfig { + @Value("${appsmith.redis.url:}") + private String redisURL; + /** * 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. @@ -49,6 +61,31 @@ public class RedisConfig { return new ChannelTopic("appsmith:queue"); } + @Bean + @Primary + public ReactiveRedisConnectionFactory reactiveRedisConnectionFactory() { + final URI redisUri = URI.create(redisURL); + final String scheme = redisUri.getScheme(); + + switch (scheme) { + case "redis" -> { + return new LettuceConnectionFactory(redisUri.getHost(), redisUri.getPort()); + } + + case "redis-cluster" -> { + // For ElastiCache Redis with cluster mode enabled, with the configuration endpoint. + final LettuceClientConfiguration config = LettucePoolingClientConfiguration + .builder() + .build(); + final RedisClusterConfiguration clusterConfig = new RedisClusterConfiguration(); + clusterConfig.addClusterNode(new RedisNode(redisUri.getHost(), redisUri.getPort())); + return new LettuceConnectionFactory(clusterConfig, config); + } + + default -> throw new InvalidRedisURIException("Invalid redis scheme: " + scheme); + } + } + @Bean public RedisSerializer springSessionDefaultRedisSerializer() { return new JSONSessionRedisSerializer(); @@ -179,4 +216,10 @@ public class RedisConfig { } } + private static class InvalidRedisURIException extends RuntimeException { + public InvalidRedisURIException(String message) { + super(message); + } + } + } diff --git a/app/server/appsmith-server/src/main/resources/application.properties b/app/server/appsmith-server/src/main/resources/application.properties index ab890ec081..f3e10648c9 100644 --- a/app/server/appsmith-server/src/main/resources/application.properties +++ b/app/server/appsmith-server/src/main/resources/application.properties @@ -54,7 +54,7 @@ sentry.debug=false sentry.environment=${APPSMITH_SERVER_SENTRY_ENVIRONMENT:} # Redis Properties -spring.data.redis.url=${APPSMITH_REDIS_URL} +appsmith.redis.url=${APPSMITH_REDIS_URL} # Mail Properties # Email defaults to false, because, when true and the other SMTP properties are not set, Spring will try to use a