All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.lettuce.core.RedisClient Maven / Gradle / Ivy

Go to download

Advanced and thread-safe Java Redis client for synchronous, asynchronous, and reactive usage. Supports Cluster, Sentinel, Pipelining, Auto-Reconnect, Codecs and much more.

The newest version!
/*
 * Copyright 2011-Present, Redis Ltd. and Contributors
 * All rights reserved.
 *
 * Licensed under the MIT License.
 *
 * This file contains contributions from third-party contributors
 * licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.lettuce.core;

import static io.lettuce.core.RedisAuthenticationHandler.createHandler;
import static io.lettuce.core.internal.LettuceStrings.*;

import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.time.Duration;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.function.Supplier;

import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.codec.RedisCodec;
import io.lettuce.core.codec.StringCodec;
import io.lettuce.core.internal.ExceptionFactory;
import io.lettuce.core.internal.Futures;
import io.lettuce.core.internal.LettuceAssert;
import io.lettuce.core.masterreplica.MasterReplica;
import io.lettuce.core.protocol.CommandExpiryWriter;
import io.lettuce.core.protocol.CommandHandler;
import io.lettuce.core.protocol.DefaultEndpoint;
import io.lettuce.core.protocol.Endpoint;
import io.lettuce.core.protocol.PushHandler;
import io.lettuce.core.pubsub.PubSubCommandHandler;
import io.lettuce.core.pubsub.PubSubEndpoint;
import io.lettuce.core.pubsub.StatefulRedisPubSubConnection;
import io.lettuce.core.pubsub.StatefulRedisPubSubConnectionImpl;
import io.lettuce.core.resource.ClientResources;
import io.lettuce.core.sentinel.StatefulRedisSentinelConnectionImpl;
import io.lettuce.core.sentinel.api.StatefulRedisSentinelConnection;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import reactor.core.publisher.Mono;

/**
 * A scalable and thread-safe Redis client supporting synchronous, asynchronous and reactive
 * execution models. Multiple threads may share one connection if they avoid blocking and transactional operations such as BLPOP
 * and MULTI/EXEC.
 * 

* {@link RedisClient} can be used with: *

    *
  • Redis Standalone
  • *
  • Redis Pub/Sub
  • *
  • Redis Sentinel, Sentinel connections
  • *
  • Redis Sentinel, Upstream connections
  • *
* * Redis Cluster is used through {@link io.lettuce.core.cluster.RedisClusterClient}. Upstream/Replica connections through * {@link MasterReplica} provide connections to Redis Upstream/Replica ("Master/Slave") setups which run either in a static * Upstream/Replica setup or are managed by Redis Sentinel. *

* {@link RedisClient} is an expensive resource. It holds a set of netty's {@link io.netty.channel.EventLoopGroup}'s that use * multiple threads. Reuse this instance as much as possible or share a {@link ClientResources} instance amongst multiple client * instances. * * @author Will Glozer * @author Mark Paluch * @see RedisURI * @see StatefulRedisConnection * @see RedisFuture * @see reactor.core.publisher.Mono * @see reactor.core.publisher.Flux * @see RedisCodec * @see ClientOptions * @see ClientResources * @see MasterReplica * @see io.lettuce.core.cluster.RedisClusterClient */ public class RedisClient extends AbstractRedisClient { private static final InternalLogger logger = InternalLoggerFactory.getInstance(RedisClient.class); private static final RedisURI EMPTY_URI = new RedisURI(); private final RedisURI redisURI; protected RedisClient(ClientResources clientResources, RedisURI redisURI) { super(clientResources); assertNotNull(redisURI); this.redisURI = redisURI; setDefaultTimeout(redisURI.getTimeout()); } /** * Creates a uri-less RedisClient. You can connect to different Redis servers but you must supply a {@link RedisURI} on * connecting. Methods without having a {@link RedisURI} will fail with a {@link java.lang.IllegalStateException}. * Non-private constructor to make {@link RedisClient} proxyable. */ protected RedisClient() { this(null, EMPTY_URI); } /** * Creates a uri-less RedisClient with default {@link ClientResources}. You can connect to different Redis servers but you * must supply a {@link RedisURI} on connecting. Methods without having a {@link RedisURI} will fail with a * {@link java.lang.IllegalStateException}. * * @return a new instance of {@link RedisClient} */ public static RedisClient create() { return new RedisClient(null, EMPTY_URI); } /** * Create a new client that connects to the supplied {@link RedisURI uri} with default {@link ClientResources}. You can * connect to different Redis servers but you must supply a {@link RedisURI} on connecting. * * @param redisURI the Redis URI, must not be {@code null} * @return a new instance of {@link RedisClient} */ public static RedisClient create(RedisURI redisURI) { assertNotNull(redisURI); return new RedisClient(null, redisURI); } /** * Create a new client that connects to the supplied uri with default {@link ClientResources}. You can connect to different * Redis servers but you must supply a {@link RedisURI} on connecting. * * @param uri the Redis URI, must not be {@code null} * @return a new instance of {@link RedisClient} */ public static RedisClient create(String uri) { LettuceAssert.notEmpty(uri, "URI must not be empty"); return new RedisClient(null, RedisURI.create(uri)); } /** * Creates a uri-less RedisClient with shared {@link ClientResources}. You need to shut down the {@link ClientResources} * upon shutting down your application. You can connect to different Redis servers but you must supply a {@link RedisURI} on * connecting. Methods without having a {@link RedisURI} will fail with a {@link java.lang.IllegalStateException}. * * @param clientResources the client resources, must not be {@code null} * @return a new instance of {@link RedisClient} */ public static RedisClient create(ClientResources clientResources) { assertNotNull(clientResources); return new RedisClient(clientResources, EMPTY_URI); } /** * Create a new client that connects to the supplied uri with shared {@link ClientResources}.You need to shut down the * {@link ClientResources} upon shutting down your application. You can connect to different Redis servers but you must * supply a {@link RedisURI} on connecting. * * @param clientResources the client resources, must not be {@code null} * @param uri the Redis URI, must not be {@code null} * * @return a new instance of {@link RedisClient} */ public static RedisClient create(ClientResources clientResources, String uri) { assertNotNull(clientResources); LettuceAssert.notEmpty(uri, "URI must not be empty"); return create(clientResources, RedisURI.create(uri)); } /** * Create a new client that connects to the supplied {@link RedisURI uri} with shared {@link ClientResources}. You need to * shut down the {@link ClientResources} upon shutting down your application.You can connect to different Redis servers but * you must supply a {@link RedisURI} on connecting. * * @param clientResources the client resources, must not be {@code null} * @param redisURI the Redis URI, must not be {@code null} * @return a new instance of {@link RedisClient} */ public static RedisClient create(ClientResources clientResources, RedisURI redisURI) { assertNotNull(clientResources); assertNotNull(redisURI); return new RedisClient(clientResources, redisURI); } /** * Open a new connection to a Redis server that treats keys and values as UTF-8 strings. * * @return A new stateful Redis connection */ public StatefulRedisConnection connect() { return connect(newStringStringCodec()); } /** * Open a new connection to a Redis server. Use the supplied {@link RedisCodec codec} to encode/decode keys and values. * * @param codec Use this codec to encode/decode keys and values, must not be {@code null} * @param Key type * @param Value type * @return A new stateful Redis connection */ public StatefulRedisConnection connect(RedisCodec codec) { checkForRedisURI(); return getConnection(connectStandaloneAsync(codec, this.redisURI, getDefaultTimeout())); } /** * Open a new connection to a Redis server using the supplied {@link RedisURI} that treats keys and values as UTF-8 strings. * * @param redisURI the Redis server to connect to, must not be {@code null} * @return A new connection */ public StatefulRedisConnection connect(RedisURI redisURI) { assertNotNull(redisURI); return getConnection(connectStandaloneAsync(newStringStringCodec(), redisURI, redisURI.getTimeout())); } /** * Open a new connection to a Redis server using the supplied {@link RedisURI} and the supplied {@link RedisCodec codec} to * encode/decode keys. * * @param codec Use this codec to encode/decode keys and values, must not be {@code null} * @param redisURI the Redis server to connect to, must not be {@code null} * @param Key type * @param Value type * @return A new connection */ public StatefulRedisConnection connect(RedisCodec codec, RedisURI redisURI) { assertNotNull(redisURI); return getConnection(connectStandaloneAsync(codec, redisURI, redisURI.getTimeout())); } /** * Open asynchronously a new connection to a Redis server using the supplied {@link RedisURI} and the supplied * {@link RedisCodec codec} to encode/decode keys. * * @param codec Use this codec to encode/decode keys and values, must not be {@code null} * @param redisURI the Redis server to connect to, must not be {@code null} * @param Key type * @param Value type * @return {@link ConnectionFuture} to indicate success or failure to connect. * @since 5.0 */ public ConnectionFuture> connectAsync(RedisCodec codec, RedisURI redisURI) { assertNotNull(redisURI); return transformAsyncConnectionException(connectStandaloneAsync(codec, redisURI, redisURI.getTimeout())); } private ConnectionFuture> connectStandaloneAsync(RedisCodec codec, RedisURI redisURI, Duration timeout) { assertNotNull(codec); checkValidRedisURI(redisURI); logger.debug("Trying to get a Redis connection for: {}", redisURI); DefaultEndpoint endpoint = new DefaultEndpoint(getOptions(), getResources()); RedisChannelWriter writer = endpoint; if (CommandExpiryWriter.isSupported(getOptions())) { writer = new CommandExpiryWriter(writer, getOptions(), getResources()); } if (CommandListenerWriter.isSupported(getCommandListeners())) { writer = new CommandListenerWriter(writer, getCommandListeners()); } StatefulRedisConnectionImpl connection = newStatefulRedisConnection(writer, endpoint, codec, timeout); ConnectionFuture> future = connectStatefulAsync(connection, endpoint, redisURI, () -> new CommandHandler(getOptions(), getResources(), endpoint), false); future.whenComplete((channelHandler, throwable) -> { if (throwable != null) { connection.closeAsync(); } }); return future; } @SuppressWarnings("unchecked") private ConnectionFuture connectStatefulAsync(StatefulRedisConnectionImpl connection, Endpoint endpoint, RedisURI redisURI, Supplier commandHandlerSupplier, Boolean isPubSub) { ConnectionBuilder connectionBuilder; if (redisURI.isSsl()) { SslConnectionBuilder sslConnectionBuilder = SslConnectionBuilder.sslConnectionBuilder(); sslConnectionBuilder.ssl(redisURI); connectionBuilder = sslConnectionBuilder; } else { connectionBuilder = ConnectionBuilder.connectionBuilder(); } ConnectionState state = connection.getConnectionState(); state.apply(redisURI); state.setDb(redisURI.getDatabase()); connection .setAuthenticationHandler(createHandler(connection, redisURI.getCredentialsProvider(), isPubSub, getOptions())); connectionBuilder.connection(connection); connectionBuilder.clientOptions(getOptions()); connectionBuilder.clientResources(getResources()); connectionBuilder.commandHandler(commandHandlerSupplier).endpoint(endpoint); connectionBuilder(getSocketAddressSupplier(redisURI), connectionBuilder, connection.getConnectionEvents(), redisURI); connectionBuilder.connectionInitializer(createHandshake(state)); ConnectionFuture> future = initializeChannelAsync(connectionBuilder); return future.thenApply(channelHandler -> (S) connection); } /** * Open a new pub/sub connection to a Redis server that treats keys and values as UTF-8 strings. * * @return A new stateful pub/sub connection */ public StatefulRedisPubSubConnection connectPubSub() { return getConnection(connectPubSubAsync(newStringStringCodec(), this.redisURI, getDefaultTimeout())); } /** * Open a new pub/sub connection to a Redis server using the supplied {@link RedisURI} that treats keys and values as UTF-8 * strings. * * @param redisURI the Redis server to connect to, must not be {@code null} * @return A new stateful pub/sub connection */ public StatefulRedisPubSubConnection connectPubSub(RedisURI redisURI) { assertNotNull(redisURI); return getConnection(connectPubSubAsync(newStringStringCodec(), redisURI, redisURI.getTimeout())); } /** * Open a new pub/sub connection to the Redis server using the supplied {@link RedisURI} and use the supplied * {@link RedisCodec codec} to encode/decode keys and values. * * @param codec Use this codec to encode/decode keys and values, must not be {@code null} * @param Key type * @param Value type * @return A new stateful pub/sub connection */ public StatefulRedisPubSubConnection connectPubSub(RedisCodec codec) { checkForRedisURI(); return getConnection(connectPubSubAsync(codec, this.redisURI, getDefaultTimeout())); } /** * Open a new pub/sub connection to the Redis server using the supplied {@link RedisURI} and use the supplied * {@link RedisCodec codec} to encode/decode keys and values. * * @param codec Use this codec to encode/decode keys and values, must not be {@code null} * @param redisURI the Redis server to connect to, must not be {@code null} * @param Key type * @param Value type * @return A new connection */ public StatefulRedisPubSubConnection connectPubSub(RedisCodec codec, RedisURI redisURI) { assertNotNull(redisURI); return getConnection(connectPubSubAsync(codec, redisURI, redisURI.getTimeout())); } /** * Open asynchronously a new pub/sub connection to the Redis server using the supplied {@link RedisURI} and use the supplied * {@link RedisCodec codec} to encode/decode keys and values. * * @param codec Use this codec to encode/decode keys and values, must not be {@code null} * @param redisURI the redis server to connect to, must not be {@code null} * @param Key type * @param Value type * @return {@link ConnectionFuture} to indicate success or failure to connect. * @since 5.0 */ public ConnectionFuture> connectPubSubAsync(RedisCodec codec, RedisURI redisURI) { assertNotNull(redisURI); return transformAsyncConnectionException(connectPubSubAsync(codec, redisURI, redisURI.getTimeout())); } private ConnectionFuture> connectPubSubAsync(RedisCodec codec, RedisURI redisURI, Duration timeout) { assertNotNull(codec); checkValidRedisURI(redisURI); PubSubEndpoint endpoint = new PubSubEndpoint<>(getOptions(), getResources()); RedisChannelWriter writer = endpoint; if (CommandExpiryWriter.isSupported(getOptions())) { writer = new CommandExpiryWriter(writer, getOptions(), getResources()); } if (CommandListenerWriter.isSupported(getCommandListeners())) { writer = new CommandListenerWriter(writer, getCommandListeners()); } StatefulRedisPubSubConnectionImpl connection = newStatefulRedisPubSubConnection(endpoint, writer, codec, timeout); ConnectionFuture> future = connectStatefulAsync(connection, endpoint, redisURI, () -> new PubSubCommandHandler<>(getOptions(), getResources(), codec, endpoint), true); return future.whenComplete((conn, throwable) -> { if (throwable != null) { conn.close(); } }); } /** * Open a connection to a Redis Sentinel that treats keys and values as UTF-8 strings. * * @return A new stateful Redis Sentinel connection */ public StatefulRedisSentinelConnection connectSentinel() { return connectSentinel(newStringStringCodec()); } /** * Open a connection to a Redis Sentinel that treats keys and use the supplied {@link RedisCodec codec} to encode/decode * keys and values. The client {@link RedisURI} must contain one or more sentinels. * * @param codec Use this codec to encode/decode keys and values, must not be {@code null} * @param Key type * @param Value type * @return A new stateful Redis Sentinel connection */ public StatefulRedisSentinelConnection connectSentinel(RedisCodec codec) { checkForRedisURI(); return getConnection(connectSentinelAsync(codec, this.redisURI, getDefaultTimeout())); } /** * Open a connection to a Redis Sentinel using the supplied {@link RedisURI} that treats keys and values as UTF-8 strings. * The client {@link RedisURI} must contain one or more sentinels. * * @param redisURI the Redis server to connect to, must not be {@code null} * @return A new connection */ public StatefulRedisSentinelConnection connectSentinel(RedisURI redisURI) { assertNotNull(redisURI); return getConnection(connectSentinelAsync(newStringStringCodec(), redisURI, redisURI.getTimeout())); } /** * Open a connection to a Redis Sentinel using the supplied {@link RedisURI} and use the supplied {@link RedisCodec codec} * to encode/decode keys and values. The client {@link RedisURI} must contain one or more sentinels. * * @param codec the Redis server to connect to, must not be {@code null} * @param redisURI the Redis server to connect to, must not be {@code null} * @param Key type * @param Value type * @return A new connection */ public StatefulRedisSentinelConnection connectSentinel(RedisCodec codec, RedisURI redisURI) { assertNotNull(redisURI); return getConnection(connectSentinelAsync(codec, redisURI, redisURI.getTimeout())); } /** * Open asynchronously a connection to a Redis Sentinel using the supplied {@link RedisURI} and use the supplied * {@link RedisCodec codec} to encode/decode keys and values. The client {@link RedisURI} must contain one or more * sentinels. * * @param codec the Redis server to connect to, must not be {@code null} * @param redisURI the Redis server to connect to, must not be {@code null} * @param Key type * @param Value type * @return A new connection * @since 5.1 */ public CompletableFuture> connectSentinelAsync(RedisCodec codec, RedisURI redisURI) { assertNotNull(redisURI); return transformAsyncConnectionException(connectSentinelAsync(codec, redisURI, redisURI.getTimeout()), redisURI); } private CompletableFuture> connectSentinelAsync(RedisCodec codec, RedisURI redisURI, Duration timeout) { assertNotNull(codec); checkValidRedisURI(redisURI); logger.debug("Trying to get a Redis Sentinel connection for one of: " + redisURI.getSentinels()); if (redisURI.getSentinels().isEmpty() && (isNotEmpty(redisURI.getHost()) || !isEmpty(redisURI.getSocket()))) { return doConnectSentinelAsync(codec, redisURI, timeout, new ConnectionMetadata(redisURI)).toCompletableFuture(); } List sentinels = redisURI.getSentinels(); Queue exceptionCollector = new LinkedBlockingQueue<>(); validateUrisAreOfSameConnectionType(sentinels); Mono> connectionLoop = null; for (RedisURI uri : sentinels) { Mono> connectionMono = Mono .fromCompletionStage(() -> doConnectSentinelAsync(codec, uri, timeout, new ConnectionMetadata(redisURI))) .onErrorMap(CompletionException.class, Throwable::getCause) .onErrorMap(e -> new RedisConnectionException("Cannot connect Redis Sentinel at " + uri, e)) .doOnError(exceptionCollector::add); if (connectionLoop == null) { connectionLoop = connectionMono; } else { connectionLoop = connectionLoop.onErrorResume(t -> connectionMono); } } if (connectionLoop == null) { return Mono .> error( new RedisConnectionException("Cannot connect to a Redis Sentinel: " + redisURI.getSentinels())) .toFuture(); } return connectionLoop.onErrorMap(e -> { RedisConnectionException ex = new RedisConnectionException( "Cannot connect to a Redis Sentinel: " + redisURI.getSentinels(), e); for (Throwable throwable : exceptionCollector) { if (e != throwable) { ex.addSuppressed(throwable); } } return ex; }).toFuture(); } private ConnectionFuture> doConnectSentinelAsync(RedisCodec codec, RedisURI redisURI, Duration timeout, ConnectionMetadata metadata) { ConnectionBuilder connectionBuilder; if (redisURI.isSsl()) { SslConnectionBuilder sslConnectionBuilder = SslConnectionBuilder.sslConnectionBuilder(); sslConnectionBuilder.ssl(redisURI); connectionBuilder = sslConnectionBuilder; } else { connectionBuilder = ConnectionBuilder.connectionBuilder(); } connectionBuilder.clientOptions(ClientOptions.copyOf(getOptions())); connectionBuilder.clientResources(getResources()); DefaultEndpoint endpoint = new DefaultEndpoint(getOptions(), getResources()); RedisChannelWriter writer = endpoint; if (CommandExpiryWriter.isSupported(getOptions())) { writer = new CommandExpiryWriter(writer, getOptions(), getResources()); } if (CommandListenerWriter.isSupported(getCommandListeners())) { writer = new CommandListenerWriter(writer, getCommandListeners()); } StatefulRedisSentinelConnectionImpl connection = newStatefulRedisSentinelConnection(writer, codec, timeout); ConnectionState state = connection.getConnectionState(); state.apply(redisURI); state.apply(metadata); connectionBuilder.connectionInitializer(createHandshake(state)); logger.debug("Connecting to Redis Sentinel, address: " + redisURI); connectionBuilder.endpoint(endpoint).commandHandler(() -> new CommandHandler(getOptions(), getResources(), endpoint)) .connection(connection); connectionBuilder(getSocketAddressSupplier(redisURI), connectionBuilder, connection.getConnectionEvents(), redisURI); ConnectionFuture sync = initializeChannelAsync(connectionBuilder); return sync.thenApply(ignore -> (StatefulRedisSentinelConnection) connection).whenComplete((ignore, e) -> { if (e != null) { logger.warn("Cannot connect Redis Sentinel at " + redisURI + ": " + e); connection.closeAsync(); } }); } /** * Set the {@link ClientOptions} for the client. * * @param clientOptions the new client options * @throws IllegalArgumentException if {@literal clientOptions} is null */ @Override public void setOptions(ClientOptions clientOptions) { super.setOptions(clientOptions); } // ------------------------------------------------------------------------- // Implementation hooks and helper methods // ------------------------------------------------------------------------- /** * Create a new instance of {@link StatefulRedisPubSubConnectionImpl} or a subclass. *

* Subclasses of {@link RedisClient} may override that method. * * @param endpoint the endpoint * @param channelWriter the channel writer * @param codec codec * @param timeout default timeout * @param Key-Type * @param Value Type * @return new instance of StatefulRedisPubSubConnectionImpl */ protected StatefulRedisPubSubConnectionImpl newStatefulRedisPubSubConnection(PubSubEndpoint endpoint, RedisChannelWriter channelWriter, RedisCodec codec, Duration timeout) { return new StatefulRedisPubSubConnectionImpl<>(endpoint, channelWriter, codec, timeout); } /** * Create a new instance of {@link StatefulRedisSentinelConnectionImpl} or a subclass. *

* Subclasses of {@link RedisClient} may override that method. * * @param channelWriter the channel writer * @param codec codec * @param timeout default timeout * @param Key-Type * @param Value Type * @return new instance of StatefulRedisSentinelConnectionImpl */ protected StatefulRedisSentinelConnectionImpl newStatefulRedisSentinelConnection( RedisChannelWriter channelWriter, RedisCodec codec, Duration timeout) { return new StatefulRedisSentinelConnectionImpl<>(channelWriter, codec, timeout, getOptions().getJsonParser()); } /** * Create a new instance of {@link StatefulRedisConnectionImpl} or a subclass. *

* Subclasses of {@link RedisClient} may override that method. * * @param channelWriter the channel writer * @param pushHandler the handler for push notifications * @param codec codec * @param timeout default timeout * @param Key-Type * @param Value Type * @return new instance of StatefulRedisConnectionImpl */ protected StatefulRedisConnectionImpl newStatefulRedisConnection(RedisChannelWriter channelWriter, PushHandler pushHandler, RedisCodec codec, Duration timeout) { return new StatefulRedisConnectionImpl<>(channelWriter, pushHandler, codec, timeout, getOptions().getJsonParser()); } /** * Get a {@link Mono} that resolves {@link RedisURI} to a {@link SocketAddress}. Resolution is performed either using Redis * Sentinel (if the {@link RedisURI} is configured with Sentinels) or via DNS resolution. *

* Subclasses of {@link RedisClient} may override that method. * * @param redisURI must not be {@code null}. * @return the resolved {@link SocketAddress}. * @see ClientResources#dnsResolver() * @see RedisURI#getSentinels() * @see RedisURI#getSentinelMasterId() */ protected Mono getSocketAddress(RedisURI redisURI) { return Mono.defer(() -> { if (redisURI.getSentinelMasterId() != null && !redisURI.getSentinels().isEmpty()) { logger.debug("Connecting to Redis using Sentinels {}, MasterId {}", redisURI.getSentinels(), redisURI.getSentinelMasterId()); return lookupRedis(redisURI).switchIfEmpty(Mono.error(new RedisConnectionException( "Cannot provide redisAddress using sentinel for masterId " + redisURI.getSentinelMasterId()))); } else { return Mono.fromCallable(() -> getResources().socketAddressResolver().resolve((redisURI))); } }); } /** * Returns a {@link String} {@link RedisCodec codec}. * * @return a {@link String} {@link RedisCodec codec}. * @see StringCodec#UTF8 */ protected RedisCodec newStringStringCodec() { return StringCodec.UTF8; } private static void validateUrisAreOfSameConnectionType(List redisUris) { boolean unixDomainSocket = false; boolean inetSocket = false; for (RedisURI sentinel : redisUris) { if (sentinel.getSocket() != null) { unixDomainSocket = true; } if (sentinel.getHost() != null) { inetSocket = true; } } if (unixDomainSocket && inetSocket) { throw new RedisConnectionException("You cannot mix unix domain socket and IP socket URI's"); } } private Mono getSocketAddressSupplier(RedisURI redisURI) { return getSocketAddress(redisURI).doOnNext(addr -> logger.debug("Resolved SocketAddress {} using {}", addr, redisURI)); } private Mono lookupRedis(RedisURI sentinelUri) { Duration timeout = sentinelUri.getTimeout(); return Mono.usingWhen( Mono.fromCompletionStage(() -> connectSentinelAsync(newStringStringCodec(), sentinelUri, timeout)), c -> { String sentinelMasterId = sentinelUri.getSentinelMasterId(); return c.reactive().getMasterAddrByName(sentinelMasterId).map(it -> { if (it instanceof InetSocketAddress) { InetSocketAddress isa = (InetSocketAddress) it; SocketAddress resolved = getResources().socketAddressResolver() .resolve(RedisURI.create(isa.getHostString(), isa.getPort())); logger.debug("Resolved Master {} SocketAddress {}:{} to {}", sentinelMasterId, isa.getHostString(), isa.getPort(), resolved); return resolved; } return it; }).timeout(timeout) // .onErrorMap(e -> { RedisCommandTimeoutException ex = ExceptionFactory .createTimeoutException("Cannot obtain master using SENTINEL MASTER", timeout); ex.addSuppressed(e); return ex; }); }, c -> Mono.fromCompletionStage(c::closeAsync), // (c, ex) -> Mono.fromCompletionStage(c::closeAsync), // c -> Mono.fromCompletionStage(c::closeAsync)); } private static ConnectionFuture transformAsyncConnectionException(ConnectionFuture future) { return future.thenCompose((v, e) -> { if (e != null) { return Futures.failed(RedisConnectionException.create(future.getRemoteAddress(), e)); } return CompletableFuture.completedFuture(v); }); } private static CompletableFuture transformAsyncConnectionException(CompletionStage future, RedisURI target) { return ConnectionFuture.from(null, future.toCompletableFuture()).thenCompose((v, e) -> { if (e != null) { return Futures.failed(RedisConnectionException.create(target.toString(), e)); } return CompletableFuture.completedFuture(v); }).toCompletableFuture(); } private static void checkValidRedisURI(RedisURI redisURI) { LettuceAssert.notNull(redisURI, "A valid RedisURI is required"); if (redisURI.getSentinels().isEmpty()) { if (isEmpty(redisURI.getHost()) && isEmpty(redisURI.getSocket())) { throw new IllegalArgumentException("RedisURI for Redis Standalone does not contain a host or a socket"); } } else { if (isEmpty(redisURI.getSentinelMasterId())) { throw new IllegalArgumentException("RedisURI for Redis Sentinel requires a masterId"); } for (RedisURI sentinel : redisURI.getSentinels()) { if (isEmpty(sentinel.getHost()) && isEmpty(sentinel.getSocket())) { throw new IllegalArgumentException("RedisURI for Redis Sentinel does not contain a host or a socket"); } } } } private static void assertNotNull(RedisCodec codec) { LettuceAssert.notNull(codec, "RedisCodec must not be null"); } private static void assertNotNull(RedisURI redisURI) { LettuceAssert.notNull(redisURI, "RedisURI must not be null"); } private static void assertNotNull(ClientResources clientResources) { LettuceAssert.notNull(clientResources, "ClientResources must not be null"); } private void checkForRedisURI() { LettuceAssert.assertState(this.redisURI != EMPTY_URI, "RedisURI is not available. Use RedisClient(Host), RedisClient(Host, Port) or RedisClient(RedisURI) to construct your client."); checkValidRedisURI(this.redisURI); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy