
com.lambdaworks.redis.RedisClient Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of lettuce Show documentation
Show all versions of lettuce Show documentation
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-2016 the original author or authors.
*
* 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
*
* http://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 com.lambdaworks.redis;
import static com.lambdaworks.redis.LettuceStrings.isEmpty;
import static com.lambdaworks.redis.LettuceStrings.isNotEmpty;
import static com.lambdaworks.redis.internal.LettuceClassUtils.isPresent;
import java.net.ConnectException;
import java.net.SocketAddress;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Supplier;
import com.lambdaworks.redis.api.StatefulRedisConnection;
import com.lambdaworks.redis.codec.RedisCodec;
import com.lambdaworks.redis.codec.StringCodec;
import com.lambdaworks.redis.internal.LettuceAssert;
import com.lambdaworks.redis.protocol.DefaultEndpoint;
import com.lambdaworks.redis.protocol.CommandHandler;
import com.lambdaworks.redis.pubsub.PubSubCommandHandler;
import com.lambdaworks.redis.pubsub.PubSubEndpoint;
import com.lambdaworks.redis.pubsub.StatefulRedisPubSubConnection;
import com.lambdaworks.redis.pubsub.StatefulRedisPubSubConnectionImpl;
import com.lambdaworks.redis.resource.ClientResources;
import com.lambdaworks.redis.resource.SocketAddressResolver;
import com.lambdaworks.redis.sentinel.StatefulRedisSentinelConnectionImpl;
import com.lambdaworks.redis.sentinel.api.StatefulRedisSentinelConnection;
import com.lambdaworks.redis.sentinel.api.async.RedisSentinelAsyncCommands;
/**
* A scalable thread-safe Redis client. Multiple threads may share one connection if they avoid
* blocking and transactional operations such as BLPOP and MULTI/EXEC. {@link RedisClient} is an expensive resource. It holds a
* set of netty's {@link io.netty.channel.EventLoopGroup}'s that consist of up to {@code Number of CPU's * 4} threads. Reuse
* this instance as much as possible.
*
* @author Will Glozer
* @author Mark Paluch
*/
public class RedisClient extends AbstractRedisClient {
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(), redisURI.getUnit());
}
/**
* 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}.
*/
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 {@literal 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 {@literal 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 {@literal 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 {@literal null}
* @param uri the Redis URI, must not be {@literal 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 {@literal null}
* @param redisURI the Redis URI, must not be {@literal 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 {@literal null}
* @param Key type
* @param Value type
* @return A new stateful Redis connection
*/
public StatefulRedisConnection connect(RedisCodec codec) {
checkForRedisURI();
return connectStandalone(codec, this.redisURI, defaultTimeout());
}
/**
* 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 {@literal null}
* @return A new connection
*/
public StatefulRedisConnection connect(RedisURI redisURI) {
return connectStandalone(newStringStringCodec(), redisURI, Timeout.from(redisURI));
}
/**
* 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 {@literal null}
* @param redisURI the Redis server to connect to, must not be {@literal null}
* @param Key type
* @param Value type
* @return A new connection
*/
public StatefulRedisConnection connect(RedisCodec codec, RedisURI redisURI) {
return connectStandalone(codec, redisURI, Timeout.from(redisURI));
}
private StatefulRedisConnection connectStandalone(RedisCodec codec, RedisURI redisURI, Timeout timeout) {
assertNotNull(codec);
checkValidRedisURI(redisURI);
DefaultEndpoint endpoint = new DefaultEndpoint(clientOptions);
StatefulRedisConnectionImpl connection = newStatefulRedisConnection((RedisChannelWriter) endpoint, codec,
timeout.timeout, timeout.timeUnit);
connectStateful(connection, redisURI, endpoint, () -> new CommandHandler(clientResources, endpoint));
return connection;
}
private void connectStateful(StatefulRedisConnectionImpl connection, RedisURI redisURI,
DefaultEndpoint endpoint, Supplier commandHandlerSupplier) {
ConnectionBuilder connectionBuilder;
if (redisURI.isSsl()) {
SslConnectionBuilder sslConnectionBuilder = SslConnectionBuilder.sslConnectionBuilder();
sslConnectionBuilder.ssl(redisURI);
connectionBuilder = sslConnectionBuilder;
} else {
connectionBuilder = ConnectionBuilder.connectionBuilder();
}
connectionBuilder.connection(connection);
connectionBuilder.clientOptions(clientOptions);
connectionBuilder.clientResources(clientResources);
connectionBuilder.commandHandler(commandHandlerSupplier).endpoint(endpoint);
connectionBuilder(getSocketAddressSupplier(redisURI), connectionBuilder, redisURI);
channelType(connectionBuilder, redisURI);
initializeChannel(connectionBuilder);
if (redisURI.getPassword() != null && redisURI.getPassword().length != 0) {
connection.async().auth(new String(redisURI.getPassword()));
}
if (redisURI.getDatabase() != 0) {
connection.async().select(redisURI.getDatabase());
}
}
/**
* 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 connectPubSub(newStringStringCodec(), redisURI, defaultTimeout());
}
/**
* 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 {@literal null}
* @return A new stateful pub/sub connection
*/
public StatefulRedisPubSubConnection connectPubSub(RedisURI redisURI) {
return connectPubSub(newStringStringCodec(), redisURI, Timeout.from(redisURI));
}
/**
* 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 {@literal null}
* @param Key type
* @param Value type
* @return A new stateful pub/sub connection
*/
public StatefulRedisPubSubConnection connectPubSub(RedisCodec codec) {
checkForRedisURI();
return connectPubSub(codec, redisURI, defaultTimeout());
}
/**
* 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 {@literal null}
* @param redisURI the redis server to connect to, must not be {@literal null}
* @param Key type
* @param Value type
* @return A new connection
*/
public StatefulRedisPubSubConnection connectPubSub(RedisCodec codec, RedisURI redisURI) {
return connectPubSub(codec, redisURI, Timeout.from(redisURI));
}
private StatefulRedisPubSubConnection connectPubSub(RedisCodec codec, RedisURI redisURI,
Timeout timeout) {
assertNotNull(codec);
checkValidRedisURI(redisURI);
PubSubEndpoint endpoint = new PubSubEndpoint(clientOptions);
StatefulRedisPubSubConnectionImpl connection = newStatefulRedisPubSubConnection(endpoint,
endpoint, codec, timeout.timeout, timeout.timeUnit);
connectStateful(connection, redisURI, endpoint, () -> new PubSubCommandHandler<>(clientResources, codec, endpoint));
return connection;
}
/**
* 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 {@literal null}
* @param Key type
* @param Value type
* @return A new stateful Redis Sentinel connection
*/
public StatefulRedisSentinelConnection connectSentinel(RedisCodec codec) {
checkForRedisURI();
return connectSentinel(codec, redisURI, defaultTimeout());
}
/**
* 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 {@literal null}
* @return A new connection
*/
public StatefulRedisSentinelConnection connectSentinel(RedisURI redisURI) {
return connectSentinel(newStringStringCodec(), redisURI, Timeout.from(redisURI));
}
/**
* 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 {@literal null}
* @param redisURI the Redis server to connect to, must not be {@literal null}
* @param Key type
* @param Value type
* @return A new connection
*/
public StatefulRedisSentinelConnection connectSentinel(RedisCodec codec, RedisURI redisURI) {
return connectSentinel(codec, redisURI, Timeout.from(redisURI));
}
private StatefulRedisSentinelConnection connectSentinel(RedisCodec codec, RedisURI redisURI,
Timeout timeout) {
assertNotNull(codec);
checkValidRedisURI(redisURI);
ConnectionBuilder connectionBuilder = ConnectionBuilder.connectionBuilder();
connectionBuilder.clientOptions(ClientOptions.copyOf(getOptions()));
connectionBuilder.clientResources(clientResources);
DefaultEndpoint endpoint = new DefaultEndpoint(clientOptions);
StatefulRedisSentinelConnectionImpl connection = newStatefulRedisSentinelConnection(endpoint,
codec, timeout.timeout, timeout.timeUnit);
logger.debug("Trying to get a Sentinel connection for one of: " + redisURI.getSentinels());
connectionBuilder.endpoint(endpoint).commandHandler(() -> new CommandHandler(clientResources, endpoint)).connection(connection);
connectionBuilder(getSocketAddressSupplier(redisURI), connectionBuilder, redisURI);
if (redisURI.getSentinels().isEmpty() && (isNotEmpty(redisURI.getHost()) || !isEmpty(redisURI.getSocket()))) {
channelType(connectionBuilder, redisURI);
initializeChannel(connectionBuilder);
} else {
boolean connected = false;
boolean first = true;
Exception causingException = null;
validateUrisAreOfSameConnectionType(redisURI.getSentinels());
for (RedisURI uri : redisURI.getSentinels()) {
if (first) {
channelType(connectionBuilder, uri);
first = false;
}
connectionBuilder.socketAddressSupplier(getSocketAddressSupplier(uri));
if (logger.isDebugEnabled()) {
SocketAddress socketAddress = SocketAddressResolver.resolve(redisURI, clientResources.dnsResolver());
logger.debug("Connecting to Sentinel, address: " + socketAddress);
}
try {
initializeChannel(connectionBuilder);
connected = true;
break;
} catch (Exception e) {
logger.warn("Cannot connect sentinel at " + uri + ": " + e.toString());
causingException = e;
if (e instanceof ConnectException) {
continue;
}
}
}
if (!connected) {
throw new RedisConnectionException("Cannot connect to a sentinel: " + redisURI.getSentinels(),
causingException);
}
}
return connection;
}
/**
* Create a new instance of {@link StatefulRedisPubSubConnectionImpl} or a subclass.
*
* @param endpoint the endpoint
* @param channelWriter the channel writer
* @param codec codec
* @param timeout default timeout
* @param unit default timeout unit
* @param Key-Type
* @param Value Type
* @return new instance of StatefulRedisPubSubConnectionImpl
*/
protected StatefulRedisPubSubConnectionImpl newStatefulRedisPubSubConnection(PubSubEndpoint endpoint,
RedisChannelWriter channelWriter, RedisCodec codec, long timeout, TimeUnit unit) {
return new StatefulRedisPubSubConnectionImpl<>(endpoint, channelWriter, codec, timeout, unit);
}
/**
* Create a new instance of {@link StatefulRedisSentinelConnectionImpl} or a subclass.
*
* @param channelWriter the channel writer
* @param codec codec
* @param timeout default timeout
* @param unit default timeout unit
* @param Key-Type
* @param Value Type
* @return new instance of StatefulRedisSentinelConnectionImpl
*/
protected StatefulRedisSentinelConnectionImpl newStatefulRedisSentinelConnection(
RedisChannelWriter channelWriter, RedisCodec codec, long timeout, TimeUnit unit) {
return new StatefulRedisSentinelConnectionImpl<>(channelWriter, codec, timeout, unit);
}
/**
* Create a new instance of {@link StatefulRedisConnectionImpl} or a subclass.
*
* @param channelWriter the channel writer
* @param codec codec
* @param timeout default timeout
* @param unit default timeout unit
* @param Key-Type
* @param Value Type
* @return new instance of StatefulRedisConnectionImpl
*/
protected StatefulRedisConnectionImpl newStatefulRedisConnection(RedisChannelWriter channelWriter,
RedisCodec codec, long timeout, TimeUnit unit) {
return new StatefulRedisConnectionImpl<>(channelWriter, codec, timeout, unit);
}
private 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 Supplier getSocketAddressSupplier(final RedisURI redisURI) {
return () -> {
try {
SocketAddress socketAddress = getSocketAddress(redisURI);
logger.debug("Resolved SocketAddress {} using {}", socketAddress, redisURI);
return socketAddress;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RedisCommandInterruptedException(e);
} catch (TimeoutException | ExecutionException e) {
throw new RedisException(e);
}
};
}
/**
* Returns the {@link ClientResources} which are used with that client.
*
* @return the {@link ClientResources} for this client
*/
public ClientResources getResources() {
return clientResources;
}
private SocketAddress getSocketAddress(RedisURI redisURI)
throws InterruptedException, TimeoutException, ExecutionException {
SocketAddress redisAddress;
if (redisURI.getSentinelMasterId() != null && !redisURI.getSentinels().isEmpty()) {
logger.debug("Connecting to Redis using Sentinels {}, MasterId {}", redisURI.getSentinels(),
redisURI.getSentinelMasterId());
redisAddress = lookupRedis(redisURI);
if (redisAddress == null) {
throw new RedisConnectionException(
"Cannot provide redisAddress using sentinel for masterId " + redisURI.getSentinelMasterId());
}
} else {
redisAddress = SocketAddressResolver.resolve(redisURI, clientResources.dnsResolver());
}
return redisAddress;
}
private SocketAddress lookupRedis(RedisURI sentinelUri) throws InterruptedException, TimeoutException, ExecutionException {
StatefulRedisSentinelConnection connection = connectSentinel(
sentinelUri);
try {
return connection.async().getMasterAddrByName(sentinelUri.getSentinelMasterId()).get(timeout, unit);
} finally {
connection.close();
}
}
private void checkValidRedisURI(RedisURI redisURI) {
LettuceAssert.notNull(redisURI, "A valid RedisURI is needed");
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("TRedisURI 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");
}
}
}
}
protected RedisCodec newStringStringCodec() {
return StringCodec.UTF8;
}
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);
}
/**
* 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);
}
private Timeout defaultTimeout() {
return Timeout.of(timeout, unit);
}
private static class Timeout {
final long timeout;
final TimeUnit timeUnit;
private Timeout(long timeout, TimeUnit timeUnit) {
this.timeout = timeout;
this.timeUnit = timeUnit;
}
private static Timeout of(long timeout, TimeUnit timeUnit) {
return new Timeout(timeout, timeUnit);
}
private static Timeout from(RedisURI redisURI) {
LettuceAssert.notNull(redisURI, "A valid RedisURI is needed");
return new Timeout(redisURI.getTimeout(), redisURI.getUnit());
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy