From de323cc8f55b6ed26bd1d70b54de3d3e08328a31 Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Fri, 27 Sep 2024 15:33:21 +0200 Subject: [PATCH] Redis: expose new configuration options from the Vert.x Redis client These are: - `autoFailover` for enabling automatic failover of master connections in the sentinel mode - `topology` for enabling static topology in the replication mode This commit also fixes how the `poolRecycleTimeout` value is read. The type is turned into an `Optional` to preserve the Vert.x Redis client default value, and that value is now used in documentation. --- docs/src/main/asciidoc/redis-reference.adoc | 42 ++++++++++++- .../client/VertxRedisClientFactory.java | 4 +- .../client/config/RedisClientConfig.java | 59 ++++++++++++++----- 3 files changed, 88 insertions(+), 17 deletions(-) diff --git a/docs/src/main/asciidoc/redis-reference.adoc b/docs/src/main/asciidoc/redis-reference.adoc index 313b6f7df5bf0..87cd8313d361f 100644 --- a/docs/src/main/asciidoc/redis-reference.adoc +++ b/docs/src/main/asciidoc/redis-reference.adoc @@ -145,7 +145,7 @@ The Redis extension can operate in 4 distinct modes: * Simple client (probably what most users need). * Sentinel (when working with Redis in High Availability mode). * Cluster (when working with Redis in Clustered mode). -* Replication (single shard, one node write, multiple read). +* Replication (single shard, one node writes, multiple read). The connection url is configured with the `quarkus.redis.hosts` (or `quarkus.redis..hosts`) as follows: @@ -183,6 +183,22 @@ The client will obtain the URLs of actual Redis servers (master or replicas, dep Note that you practically never want to configure `quarkus.redis.role=sentinel`. This setting means that the Redis client will execute commands directly on one of the sentinel servers, instead of an actual Redis server guarded by the sentinels. +==== Automatic Failover + +In the sentinel mode, it is possible to configure automatic failover of _master_ connections: + +[source,properties] +---- +quarkus.redis.auto-failover=true +---- + +If enabled, the sentinel client will additionally create a connection to one sentinel node and watch for failover events. +When new master is elected, all connections to the old master are automatically closed and new connections to the new master are created. +Automatic failover makes sense for connections executing regular commands, but not for connections used to subscribe to Redis pub/sub channels. + +Note that there is a brief period of time between the old master failing and the new master being elected when the existing connections will temporarily fail all operations. +After the new master is elected, the connections will automatically fail over and start working again. + === Use the Cluster Mode When using Redis in cluster mode, you need to pass multiple _host urls_, configure the client type to `cluster` and configure the `replicas` mode: @@ -233,6 +249,30 @@ Set the `quarkus.redis.replicas` configuration property to: Note that replication in Redis is asynchronous, so replica nodes may be lagging behind the master node. +==== Static Topology + +In the replication mode, it is possible to reconfigure the Redis client to skip automatic discovery of the topology: + +[source,properties] +---- +quarkus.redis.topology=static +---- + +With static topology, the first node in the configuration is assumed to be a _master_ node, while the remaining nodes are assumed to be _replicas_. +The nodes are not verified; it is a responsibility of the application developer to ensure that the static configuration is correct. + +Note that automatic discovery of the topology is usually the preferred choice. +Static configuration should only be used when necessary. +One such case is _Amazon Elasticache for Redis (Cluster Mode Disabled)_, where: + +* master node should be set to the _primary endpoint_, and +* one replica node should be set to the _reader endpoint_. + +WARNING: Note that the reader endpoint of Elasticache for Redis (Cluster Mode Disabled) is a domain name which resolves to a CNAME record that points to one of the replicas. +The CNAME record to which the reader endpoint resolves changes over time. +This form of DNS-based load balancing does not work well with DNS resolution caching and connection pooling. +As a result, some replicas are likely to be underutilized. + === Connect to Redis Cloud To connect to redis cloud, you need the following properties: diff --git a/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/VertxRedisClientFactory.java b/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/VertxRedisClientFactory.java index 524c83e458ac6..ec2e2fc3702f0 100644 --- a/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/VertxRedisClientFactory.java +++ b/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/VertxRedisClientFactory.java @@ -85,12 +85,14 @@ public static Redis create(String name, Vertx vertx, RedisClientConfig config, T config.preferredProtocolVersion().ifPresent(options::setPreferredProtocolVersion); options.setPassword(config.password().orElse(null)); config.poolCleanerInterval().ifPresent(d -> options.setPoolCleanerInterval((int) d.toMillis())); - options.setPoolRecycleTimeout((int) config.poolRecycleTimeout().toMillis()); + config.poolRecycleTimeout().ifPresent(d -> options.setPoolRecycleTimeout((int) d.toMillis())); options.setHashSlotCacheTTL(config.hashSlotCacheTtl().toMillis()); config.role().ifPresent(options::setRole); options.setType(config.clientType()); config.replicas().ifPresent(options::setUseReplicas); + options.setAutoFailover(config.autoFailover()); + config.topology().ifPresent(options::setTopology); options.setNetClientOptions(toNetClientOptions(config)); configureTLS(name, config, tlsRegistry, options.getNetClientOptions(), hosts); diff --git a/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/config/RedisClientConfig.java b/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/config/RedisClientConfig.java index 962c368eb27d0..f07f5681be1ef 100644 --- a/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/config/RedisClientConfig.java +++ b/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/config/RedisClientConfig.java @@ -13,12 +13,13 @@ import io.vertx.redis.client.RedisClientType; import io.vertx.redis.client.RedisReplicas; import io.vertx.redis.client.RedisRole; +import io.vertx.redis.client.RedisTopology; @ConfigGroup public interface RedisClientConfig { /** - * The redis hosts to use while connecting to the redis server. Only the cluster and sentinel modes will consider more than + * The Redis hosts to use while connecting to the Redis server. Only the cluster and sentinel modes will consider more than * 1 element. *

* The URI provided uses the following schema `redis://[username:password@][host][:port][/database]` @@ -41,49 +42,47 @@ public interface RedisClientConfig { Optional hostsProviderName(); /** - * The maximum delay to wait before a blocking command to redis server times out + * The maximum delay to wait before a blocking command to Redis server times out */ @WithDefault("10s") Duration timeout(); /** - * The redis client type. + * The Redis client type. * Accepted values are: {@code STANDALONE} (default), {@code CLUSTER}, {@code REPLICATION}, {@code SENTINEL}. */ @WithDefault("standalone") RedisClientType clientType(); /** - * The master name (only considered in HA mode). + * The master name (only considered in the Sentinel mode). */ @ConfigDocDefault("mymaster") Optional masterName(); /** - * The role name (only considered in Sentinel / HA mode). + * The role name (only considered in the Sentinel mode). * Accepted values are: {@code MASTER}, {@code REPLICA}, {@code SENTINEL}. */ @ConfigDocDefault("master") Optional role(); /** - * Whether to use replicas nodes (only considered in Cluster mode). + * Whether to use replicas nodes (only considered in Cluster mode and Replication mode). * Accepted values are: {@code ALWAYS}, {@code NEVER}, {@code SHARE}. */ @ConfigDocDefault("never") Optional replicas(); /** - * The default password for cluster/sentinel connections. + * The default password for Redis connections. *

- * If not set it will try to extract the value from the current default {@code #hosts}. + * If not set, it will try to extract the value from the {@code hosts}. */ Optional password(); /** * The maximum size of the connection pool. - * When working with cluster or sentinel, this value should be at least the total number of cluster members (or - * number of sentinels + 1) */ @WithDefault("6") int maxPoolSize(); @@ -95,15 +94,16 @@ public interface RedisClientConfig { int maxPoolWaiting(); /** - * The duration indicating how often should the connection pool cleaner executes. + * The duration indicating how often should the connection pool cleaner execute. */ + @ConfigDocDefault("30s") Optional poolCleanerInterval(); /** - * The timeout for a connection recycling. + * The timeout for unused connection recycling. */ - @WithDefault("15") - Duration poolRecycleTimeout(); + @WithDefault("3m") + Optional poolRecycleTimeout(); /** * Sets how many handlers is the client willing to queue. @@ -115,7 +115,7 @@ public interface RedisClientConfig { int maxWaitingHandlers(); /** - * Tune how much nested arrays are allowed on a redis response. This affects the parser performance. + * Tune how much nested arrays are allowed on a Redis response. This affects the parser performance. */ @WithDefault("32") int maxNestedArrays(); @@ -156,6 +156,35 @@ public interface RedisClientConfig { @WithDefault("1s") Duration hashSlotCacheTtl(); + /** + * Whether automatic failover is enabled. This only makes sense for sentinel clients + * with role of {@code MASTER} and is ignored otherwise. + *

+ * If enabled, the sentinel client will additionally create a connection to one sentinel node + * and watch for failover events. When new master is elected, all connections to the old master + * are automatically closed and new connections to the new master are created. Automatic failover + * makes sense for connections executing regular commands, but not for connections used to subscribe + * to Redis pub/sub channels. + *

+ * Note that there is a brief period of time between the old master failing and the new + * master being elected when the existing connections will temporarily fail all operations. + * After the new master is elected, the connections will automatically fail over and + * start working again. + */ + @WithDefault("false") + boolean autoFailover(); + + /** + * How the Redis topology is obtained. By default, the topology is discovered automatically. + * This is the only mode for the clustered and sentinel client. For replication client, + * topology may be set statically. + *

+ * In case of a static topology for replication Redis client, the first node in the list + * is considered a master and the remaining nodes in the list are considered replicas. + */ + @ConfigDocDefault("discover") + Optional topology(); + /** * TCP config. */