Skip to content

Commit

Permalink
Merge pull request #29893 from cescoffier/redis-metrics
Browse files Browse the repository at this point in the history
  • Loading branch information
cescoffier authored Dec 18, 2022
2 parents d573d17 + cd43420 commit 173b208
Show file tree
Hide file tree
Showing 39 changed files with 639 additions and 39 deletions.
107 changes: 89 additions & 18 deletions docs/src/main/asciidoc/redis-reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,9 @@ public class RedisExample {
}
----

TIP: When using `@RedisClientName`, you can omit the `@Inject` annotation.
TIP: You can omit the `@Inject` annotation when using `@RedisClientName`.

== Connecting to Redis
== Connection to Redis

The Redis extension can operate in 4 distinct modes:

Expand All @@ -152,7 +152,7 @@ The connection url is configured with the `quarkus.redis.hosts` (or `quarkus.red
quarkus.redis.hosts=redis://[:password@]host[:port][/db-number]
----

=== Using Unix Socket
=== Unix Socket

When using unix-socket, you need:

Expand Down Expand Up @@ -196,7 +196,7 @@ quarkus.redis.hosts=redis://localhost:7000
quarkus.redis.client-type=replication
----

=== Connecting to Redis Cloud
=== Redis Cloud

To connect to redis cloud, you need the following properties:

Expand All @@ -213,13 +213,13 @@ We recommend the latter, and if possible, using secrets or an environment variab

The associated environment variable is `QUARKUS_REDIS_PASSWORD`, or `QUARKUS_REDIS_<NAME>_PASSWORD` for named clients.

== Using the high-level clients (data sources)
== Quarkus client API for data sources

Quarkus exposes a high-level API on top of Redis.
This API is type-safe and structured around the notion of _group_, inherited from the https://redis.io/commands/command-docs/[Redis command organization].
This API lets you execute Redis commands more conveniently and safely.

=== Injecting data sources
=== Inject data sources

For each configured Redis client, two Redis data sources are exposed:

Expand Down Expand Up @@ -334,7 +334,7 @@ To store binary data, use `byte[]`.
The `value` group is used to manipulate https://redis.io/docs/manual/data-types/#strings[Redis Strings].
Thus, this group is not limited to Java Strings but can be used for integers (like a counter) or binary content (like images).

==== Caching values
==== Work with cached values

You can use Redis as a cache using the `setex` command, which stores a given value to a given key for a given duration.
The following snippet shows how such a command can be used to store `BusinessObject` for 1 second.
Expand Down Expand Up @@ -377,7 +377,7 @@ The `set` method can also receive a `SetArgs` argument that modify the behavior:
- `keepttl()` - Retain the time to live associated with the key.
====

==== Storing binary data
==== Store binary data

Redis _strings_ can be used to store binary data, such as images.
In this case, we will use `byte[]` as value type:
Expand Down Expand Up @@ -411,7 +411,7 @@ public static class MyBinaryRepository {
}
----

==== Storing a counter
==== Store a counter

You can store counters in Redis as demonstrated below:

Expand Down Expand Up @@ -454,10 +454,10 @@ There are other methods that can be useful to manipulate counters, such as:
- `set` - to set an initial value if needed
- `decr` and `decrby` - allows decrementing the stored value

==== Using pub/sub
==== Communicate with pub/sub

Redis allows sending _messages_ to channels and listening for these messages.
These features are available from the the `pubsub` group.
These features are available from the `pubsub` group.

The following snippets shows how a _cache_ can emit a `Notification` after every `set`, and how a subscriber can receive the notification.

Expand Down Expand Up @@ -521,7 +521,7 @@ public static class MyCache {
}
----

==== Using transactions
==== Redis transactions

Redis transactions are slightly different from relational database transactions.
Redis transactions are a batch of commands executed altogether.
Expand Down Expand Up @@ -585,7 +585,7 @@ TransactionResult result = ds.withTransaction(tx -> {

IMPORTANT: You cannot use the pub/sub feature from within a transaction.

==== Using optimistic locking
==== Optimistic locking

To use optimistic locking, you need to use a variant of the `withTransaction` method, allowing the execution of code before the transaction starts.
In other words, it will be executed as follows:
Expand Down Expand Up @@ -634,7 +634,7 @@ These commands must not modify the watched keys.

The transaction is aborted if the pre-transaction block throws an exception (or produces a failure when using the reactive API).

==== Executing custom commands
==== Execute custom commands

To execute a custom command, or a command not supported by the API, use the following approach:

Expand Down Expand Up @@ -708,7 +708,7 @@ INCR counter
EXEC
----

=== Loading configuration
=== Configuration

The data is loaded when the application starts.
By default, it drops the whole database before importing.
Expand Down Expand Up @@ -744,7 +744,7 @@ So when you access the `/q/health/ready` endpoint of your application you will h

This behavior can be disabled by setting the `quarkus.redis.health.enabled` property to `false` in your `application.properties`.

== Providing Redis Hosts Programmatically
== Programmatic Redis Hosts

The `RedisHostsProvider` programmatically provides redis hosts.
This allows for configuration of properties like redis connection password coming from other sources.
Expand Down Expand Up @@ -775,7 +775,7 @@ The host provider can be used to configure the redis client like shown below
quarkus.redis.hosts-provider-name=hosts-provider
----

== Customizing the Redis options programmatically
== Customize the Redis options programmatically

You can expose a bean implementing the `io.quarkus.redis.client.RedisOptionsCustomizer` interface to customize the Redis client options.
The bean is called for each configured Redis client:
Expand All @@ -802,7 +802,78 @@ public static class MyExampleCustomizer implements RedisOptionsCustomizer {

See xref:redis-dev-services.adoc[Redis Dev Service].

== Redis client metrics

=== Enable metrics collection

Redis client metrics are automatically enabled when the application also uses the xref:micrometer.adoc[`quarkus-micrometer`] extension.
Micrometer collects the metrics of all the Redis clients implemented by the application.

As an example, if you export the metrics to Prometheus, you will get:

[source, text]
----
# HELP redis_commands_duration_seconds The duration of the operations (commands of batches
# TYPE redis_commands_duration_seconds summary
redis_commands_duration_seconds_count{client_name="<default>",} 3.0
redis_commands_duration_seconds_sum{client_name="<default>",} 0.047500042
# HELP redis_commands_duration_seconds_max The duration of the operations (commands of batches
# TYPE redis_commands_duration_seconds_max gauge
redis_commands_duration_seconds_max{client_name="<default>",} 0.033273167
# HELP redis_pool_active The number of resources from the pool currently used
# TYPE redis_pool_active gauge
redis_pool_active{pool_name="<default>",pool_type="redis",} 0.0
# HELP redis_pool_ratio Pool usage ratio
# TYPE redis_pool_ratio gauge
redis_pool_ratio{pool_name="<default>",pool_type="redis",} 0.0
# HELP redis_pool_queue_size Number of pending elements in the waiting queue
# TYPE redis_pool_queue_size gauge
redis_pool_queue_size{pool_name="<default>",pool_type="redis",} 0.0
# HELP redis_commands_failure_total The number of operations (commands or batches) that have been failed
# TYPE redis_commands_failure_total counter
redis_commands_failure_total{client_name="<default>",} 0.0
# HELP redis_commands_success_total The number of operations (commands or batches) that have been executed successfully
# TYPE redis_commands_success_total counter
redis_commands_success_total{client_name="<default>",} 3.0
# HELP redis_pool_idle The number of resources from the pool currently used
# TYPE redis_pool_idle gauge
redis_pool_idle{pool_name="<default>",pool_type="redis",} 6.0
# HELP redis_pool_completed_total Number of times resources from the pool have been acquired
# TYPE redis_pool_completed_total counter
redis_pool_completed_total{pool_name="<default>",pool_type="redis",} 3.0
# HELP redis_commands_count_total The number of operations (commands or batches) executed
# TYPE redis_commands_count_total counter
redis_commands_count_total{client_name="<default>",} 3.0
# HELP redis_pool_usage_seconds Time spent using resources from the pool
# TYPE redis_pool_usage_seconds summary
redis_pool_usage_seconds_count{pool_name="<default>",pool_type="redis",} 3.0
redis_pool_usage_seconds_sum{pool_name="<default>",pool_type="redis",} 0.024381375
# HELP redis_pool_usage_seconds_max Time spent using resources from the pool
# TYPE redis_pool_usage_seconds_max gauge
redis_pool_usage_seconds_max{pool_name="<default>",pool_type="redis",} 0.010671542
# HELP redis_pool_queue_delay_seconds Time spent in the waiting queue before being processed
# TYPE redis_pool_queue_delay_seconds summary
redis_pool_queue_delay_seconds_count{pool_name="<default>",pool_type="redis",} 3.0
redis_pool_queue_delay_seconds_sum{pool_name="<default>",pool_type="redis",} 0.022341249
# HELP redis_pool_queue_delay_seconds_max Time spent in the waiting queue before being processed
# TYPE redis_pool_queue_delay_seconds_max gauge
redis_pool_queue_delay_seconds_max{pool_name="<default>",pool_type="redis",} 0.021926083
----

The Redis client name can be found in the _tags_.

The metrics contain both the Redis connection pool metrics (`redis_pool_*`) and the metrics about the command execution (`redis_commands_*`) such as the number of command, successes, failures, and durations.

=== Disable metrics collection

To disable the Redis client metrics when `quarkus-micrometer` is used, add the following property to the application configuration:

[source, properties]
----
quarkus.micrometer.binder.redis.enabled=false
----

[[redis-configuration-reference]]
== Configuration Reference
== Configuration reference

include::{generated-dir}/config/quarkus-redis-client.adoc[opts=optional, leveloffset=+1]
11 changes: 10 additions & 1 deletion extensions/micrometer/deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-vertx-http-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-redis-client-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-common-spi</artifactId>
Expand Down Expand Up @@ -97,6 +101,12 @@
<artifactId>quarkus-reactive-routes-deployment</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-redis-client-deployment</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>io.rest-assured</groupId>
Expand All @@ -109,7 +119,6 @@
<artifactId>microprofile-metrics-api</artifactId>
<scope>test</scope>
</dependency>

</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package io.quarkus.micrometer.deployment.binder;

import java.util.function.BooleanSupplier;

import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.micrometer.runtime.MicrometerRecorder;
import io.quarkus.micrometer.runtime.config.MicrometerConfig;

public class RedisBinderProcessor {

static final String OBSERVABLE_CLIENT = "io.quarkus.redis.runtime.client.ObservableRedis";
static final String METRICS_BEAN_CLASS = "io.quarkus.micrometer.runtime.binder.redis.RedisMetricsBean";

static final Class<?> OBSERVABLE_CLIENT_CLASS = MicrometerRecorder.getClassForName(OBSERVABLE_CLIENT);

static class RedisMetricsSupportEnabled implements BooleanSupplier {
MicrometerConfig mConfig;

public boolean getAsBoolean() {
return OBSERVABLE_CLIENT_CLASS != null && mConfig.checkBinderEnabledWithDefault(mConfig.binder.redis);
}
}

@BuildStep(onlyIf = RedisMetricsSupportEnabled.class)
AdditionalBeanBuildItem addRedisClientMetric() {
return AdditionalBeanBuildItem.unremovableOf(METRICS_BEAN_CLASS);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public class GlobalDefaultDisabledTest {
.withConfigurationResource("test-logging.properties")
.overrideConfigKey("quarkus.micrometer.registry-enabled-default", "false")
.overrideConfigKey("quarkus.micrometer.binder-enabled-default", "false")
.overrideConfigKey("quarkus.redis.devservices.enabled", "false")
.withApplicationRoot((jar) -> jar
.addClasses(Util.class));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ public class MetricFiltersTest {
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addClasses(AnnotatedFilter.class, NonAnnotatedFilter.class,
MeterFilterProducer.class));
MeterFilterProducer.class))
.overrideConfigKey("quarkus.redis.devservices.enabled", "false");

@Test
public void testCDIFilters() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public class MetricsFromMetricsFactoryTestCase {
.withConfigurationResource("test-logging.properties")
.overrideConfigKey("quarkus.micrometer.registry-enabled-default", "false")
.overrideConfigKey("quarkus.micrometer.binder-enabled-default", "false")
.overrideConfigKey("quarkus.redis.devservices.enabled", "false")
.withApplicationRoot((jar) -> jar
.addClass(MeasureThis.class));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public class MicrometerDisabledTest {
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addAsResource(new StringAsset("quarkus.micrometer.enabled=false"), "application.properties"))
.overrideConfigKey("quarkus.redis.devservices.enabled", "false")
.assertException(t -> {
Assertions.assertEquals(DeploymentException.class, t.getClass());
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public class GrpcMetricsDisabledTest {

.overrideConfigKey("quarkus.micrometer.binder-enabled-default", "false")
.overrideConfigKey("quarkus.micrometer.registry-enabled-default", "false")
.overrideConfigKey("quarkus.redis.devservices.enabled", "false")
.withEmptyApplication();

@Inject
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public class HttpDevModeConfigTest {
"quarkus.micrometer.binder.http-server.enabled=true\n" +
"quarkus.micrometer.binder.http-server.ignore-patterns=/http\n" +
"quarkus.micrometer.binder.vertx.enabled=true\n" +
"quarkus.redis.devservices.enabled=false\n" +
"orange=banana"), "application.properties"));

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public class KafkaClientMetricsDisabledTest {
.overrideConfigKey("quarkus.micrometer.binder.kafka.enabled", "true")
.overrideConfigKey("quarkus.micrometer.binder-enabled-default", "false")
.overrideConfigKey("quarkus.micrometer.registry-enabled-default", "false")
.overrideConfigKey("quarkus.redis.devservices.enabled", "false")
.withEmptyApplication();

@Inject
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public class KafkaStreamsMetricsDisabledTest {
.overrideConfigKey("quarkus.micrometer.binder.kafka.enabled", "true")
.overrideConfigKey("quarkus.micrometer.binder-enabled-default", "false")
.overrideConfigKey("quarkus.micrometer.registry-enabled-default", "false")
.overrideConfigKey("quarkus.redis.devservices.enabled", "false")
.withEmptyApplication();

@Inject
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package io.quarkus.micrometer.deployment.binder;

import static org.junit.jupiter.api.Assertions.assertTrue;

import javax.enterprise.inject.Instance;
import javax.inject.Inject;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.redis.runtime.client.ObservableRedisMetrics;
import io.quarkus.test.QuarkusUnitTest;

public class RedisClientMetricsDisabledTest {

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withConfigurationResource("test-logging.properties")
.overrideConfigKey("quarkus.micrometer.binder.redis.enabled", "false")
.overrideConfigKey("quarkus.micrometer.binder-enabled-default", "false")
.overrideConfigKey("quarkus.micrometer.registry-enabled-default", "false")
.overrideConfigKey("quarkus.redis.devservices.enabled", "false")
.withEmptyApplication();

@Inject
Instance<ObservableRedisMetrics> bean;

@Test
void testNoInstancePresentIfNoRedisClientsClass() {
assertTrue(bean.isUnsatisfied(),
"No redis metrics bean");
}

}
Loading

0 comments on commit 173b208

Please sign in to comment.