diff --git a/pom.xml b/pom.xml
index de17b7af10..83f830063b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -205,6 +205,12 @@
true
+
+ io.netty
+ netty-resolver-dns
+ true
+
+
diff --git a/src/main/java/io/lettuce/core/AbstractRedisClient.java b/src/main/java/io/lettuce/core/AbstractRedisClient.java
index d67dcc8fb9..206a31a70b 100644
--- a/src/main/java/io/lettuce/core/AbstractRedisClient.java
+++ b/src/main/java/io/lettuce/core/AbstractRedisClient.java
@@ -73,6 +73,7 @@
* @author Mark Paluch
* @author Jongyeol Choi
* @author Poorva Gokhale
+ * @author Yohei Ueki
* @since 3.0
* @see ClientResources
*/
@@ -297,6 +298,15 @@ protected void channelType(ConnectionBuilder connectionBuilder, ConnectionPoint
}
}
+ protected void resolver(ConnectionBuilder connectionBuilder, ConnectionPoint connectionPoint) {
+
+ LettuceAssert.notNull(connectionPoint, "ConnectionPoint must not be null");
+
+ if (connectionPoint.getSocket() == null) {
+ connectionBuilder.bootstrap().resolver(clientResources.addressResolverGroup());
+ }
+ }
+
private EventLoopGroup getEventLoopGroup(ConnectionPoint connectionPoint) {
for (;;) {
diff --git a/src/main/java/io/lettuce/core/RedisClient.java b/src/main/java/io/lettuce/core/RedisClient.java
index 0d51225835..5241a8c234 100644
--- a/src/main/java/io/lettuce/core/RedisClient.java
+++ b/src/main/java/io/lettuce/core/RedisClient.java
@@ -75,6 +75,7 @@
*
* @author Will Glozer
* @author Mark Paluch
+ * @author Yohei Ueki
* @see RedisURI
* @see StatefulRedisConnection
* @see RedisFuture
@@ -322,6 +323,7 @@ private ConnectionFuture connectStatefulAsync(StatefulRedisConnecti
connectionBuilder(getSocketAddressSupplier(redisURI), connectionBuilder, redisURI);
connectionBuilder.connectionInitializer(createHandshake(state));
channelType(connectionBuilder, redisURI);
+ resolver(connectionBuilder, redisURI);
ConnectionFuture> future = initializeChannelAsync(connectionBuilder);
@@ -599,6 +601,7 @@ private ConnectionFuture> doConnect
connectionBuilder(getSocketAddressSupplier(redisURI), connectionBuilder, redisURI);
channelType(connectionBuilder, redisURI);
+ resolver(connectionBuilder, redisURI);
ConnectionFuture> sync = initializeChannelAsync(connectionBuilder);
return sync.thenApply(ignore -> (StatefulRedisSentinelConnection) connection).whenComplete((ignore, e) -> {
diff --git a/src/main/java/io/lettuce/core/Transports.java b/src/main/java/io/lettuce/core/Transports.java
index ac5b95fdf7..72dedf126c 100644
--- a/src/main/java/io/lettuce/core/Transports.java
+++ b/src/main/java/io/lettuce/core/Transports.java
@@ -22,6 +22,8 @@
import io.netty.channel.Channel;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.DatagramChannel;
+import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
/**
@@ -29,9 +31,10 @@
* native socket transports.
*
* @author Mark Paluch
+ * @author Yohei Ueki
* @since 4.4
*/
-class Transports {
+public class Transports {
/**
* @return the default {@link EventLoopGroup} for socket transport that is compatible with {@link #socketChannelClass()}.
@@ -48,7 +51,7 @@ static Class extends EventLoopGroup> eventLoopGroupClass() {
/**
* @return the default {@link Channel} for socket (network/TCP) transport.
*/
- static Class extends Channel> socketChannelClass() {
+ public static Class extends Channel> socketChannelClass() {
if (NativeTransports.isSocketSupported()) {
return NativeTransports.socketChannelClass();
@@ -57,6 +60,18 @@ static Class extends Channel> socketChannelClass() {
return NioSocketChannel.class;
}
+ /**
+ * @return the default {@link DatagramChannel} for socket (network/UDP) transport.
+ */
+ public static Class extends DatagramChannel> datagramChannelClass() {
+
+ if (NativeTransports.isSocketSupported()) {
+ return NativeTransports.datagramChannelClass();
+ }
+
+ return NioDatagramChannel.class;
+ }
+
/**
* Native transport support.
*/
@@ -79,6 +94,13 @@ static Class extends Channel> socketChannelClass() {
return RESOURCES.socketChannelClass();
}
+ /**
+ * @return the native transport socket {@link DatagramChannel} class.
+ */
+ static Class extends DatagramChannel> datagramChannelClass() {
+ return RESOURCES.datagramChannelClass();
+ }
+
/**
* @return the native transport domain socket {@link Channel} class.
*/
diff --git a/src/main/java/io/lettuce/core/cluster/RedisClusterClient.java b/src/main/java/io/lettuce/core/cluster/RedisClusterClient.java
index 518af0b12a..f05dd48c80 100644
--- a/src/main/java/io/lettuce/core/cluster/RedisClusterClient.java
+++ b/src/main/java/io/lettuce/core/cluster/RedisClusterClient.java
@@ -135,6 +135,7 @@
* possible.
*
* @author Mark Paluch
+ * @author Yohei Ueki
* @since 3.0
* @see RedisURI
* @see StatefulRedisClusterConnection
@@ -815,6 +816,7 @@ private ConnectionBuilder createConnectionBuilder(RedisChannelHandler ADDRESS_RESOLVER_GROUP;
+
+ static {
+ boolean dnsResolverAvailable;
+ try {
+ Class.forName("io.netty.resolver.dns.DnsAddressResolverGroup");
+ dnsResolverAvailable = true;
+ } catch (ClassNotFoundException e) {
+ dnsResolverAvailable = false;
+ }
+
+ // create addressResolverGroup instance via Supplier to avoid NoClassDefFoundError.
+ Supplier> supplier;
+ if (dnsResolverAvailable) {
+ logger.debug("Starting with netty's non-blocking DNS resolver library");
+ supplier = AddressResolverGroupProvider::defaultDnsAddressResolverGroup;
+ } else {
+ logger.debug("Starting without optional netty's non-blocking DNS resolver library");
+ supplier = () -> DefaultAddressResolverGroup.INSTANCE;
+ }
+ ADDRESS_RESOLVER_GROUP = supplier.get();
+ }
+
+ /**
+ * Returns the {@link AddressResolverGroup} for dns resolution.
+ *
+ * @return the {@link DnsAddressResolverGroup} if {@literal netty-dns-resolver} is available, otherwise return
+ * {@link DefaultAddressResolverGroup#INSTANCE}.
+ * @since xxx
+ */
+ static AddressResolverGroup> addressResolverGroup() {
+ return ADDRESS_RESOLVER_GROUP;
+ }
+
+ private static DnsAddressResolverGroup defaultDnsAddressResolverGroup() {
+ return new DnsAddressResolverGroup(new DnsNameResolverBuilder().channelType(Transports.datagramChannelClass())
+ .socketChannelType(Transports.socketChannelClass().asSubclass(SocketChannel.class)));
+ }
+
+}
diff --git a/src/main/java/io/lettuce/core/resource/ClientResources.java b/src/main/java/io/lettuce/core/resource/ClientResources.java
index 0029a99b93..9d834739da 100644
--- a/src/main/java/io/lettuce/core/resource/ClientResources.java
+++ b/src/main/java/io/lettuce/core/resource/ClientResources.java
@@ -24,6 +24,7 @@
import io.lettuce.core.metrics.CommandLatencyCollectorOptions;
import io.lettuce.core.metrics.CommandLatencyRecorder;
import io.lettuce.core.tracing.Tracing;
+import io.netty.resolver.AddressResolverGroup;
import io.netty.util.Timer;
import io.netty.util.concurrent.EventExecutorGroup;
import io.netty.util.concurrent.Future;
@@ -45,10 +46,12 @@
* {@link DnsResolver} to collect latency details. Requires the {@literal LatencyUtils} library.
* {@link Timer} for scheduling
* {@link Tracing} to trace Redis commands.
+ * {@link AddressResolverGroup} for dns resolution.
*
*
* @author Mark Paluch
* @author Mikhael Sokolov
+ * @author Yohei Ueki
* @since 3.4
* @see DefaultClientResources
*/
@@ -241,6 +244,18 @@ default Builder commandLatencyCollector(CommandLatencyCollector commandLatencyCo
*/
Builder tracing(Tracing tracing);
+ /**
+ * Sets the {@link AddressResolverGroup} for dns resolution. This option is only effective if
+ * {@link DnsResolvers#UNRESOLVED} is used as {@link DnsResolver}. Defaults to
+ * {@link io.netty.resolver.DefaultAddressResolverGroup#INSTANCE} if {@literal netty-dns-resolver} is not available,
+ * otherwise defaults to {@link io.netty.resolver.dns.DnsAddressResolverGroup}.
+ *
+ * @param addressResolverGroup the {@link AddressResolverGroup} instance, must not be {@code null}.
+ * @return {@code this} {@link Builder}
+ * @since xxx
+ */
+ Builder addressResolverGroup(AddressResolverGroup> addressResolverGroup);
+
/**
* @return a new instance of {@link DefaultClientResources}.
*/
@@ -385,4 +400,12 @@ default Builder commandLatencyCollector(CommandLatencyCollector commandLatencyCo
*/
Tracing tracing();
+ /**
+ * Return the {@link AddressResolverGroup} instance for dns resolution.
+ *
+ * @return the address resolver group.
+ * @since xxx
+ */
+ AddressResolverGroup> addressResolverGroup();
+
}
diff --git a/src/main/java/io/lettuce/core/resource/DefaultClientResources.java b/src/main/java/io/lettuce/core/resource/DefaultClientResources.java
index aff50dd992..71e5fa3860 100644
--- a/src/main/java/io/lettuce/core/resource/DefaultClientResources.java
+++ b/src/main/java/io/lettuce/core/resource/DefaultClientResources.java
@@ -35,6 +35,7 @@
import io.lettuce.core.metrics.MetricCollector;
import io.lettuce.core.resource.Delay.StatefulDelay;
import io.lettuce.core.tracing.Tracing;
+import io.netty.resolver.AddressResolverGroup;
import io.netty.util.HashedWheelTimer;
import io.netty.util.Timer;
import io.netty.util.concurrent.DefaultEventExecutorGroup;
@@ -70,9 +71,11 @@
* a {@code socketAddressResolver} which is a provided instance of {@link SocketAddressResolver}.
* a {@code timer} that is a provided instance of {@link io.netty.util.HashedWheelTimer}.
* a {@code tracing} that is a provided instance of {@link Tracing}.
+ * a {@code addressResolverGroup} that is a provided instance of {@link AddressResolverGroup}.
*
*
* @author Mark Paluch
+ * @author Yohei Ueki
* @since 3.4
*/
public class DefaultClientResources implements ClientResources {
@@ -103,6 +106,12 @@ public class DefaultClientResources implements ClientResources {
*/
public static final NettyCustomizer DEFAULT_NETTY_CUSTOMIZER = DefaultNettyCustomizer.INSTANCE;
+ /**
+ * Default {@link AddressResolverGroup}.
+ */
+ public static final AddressResolverGroup> DEFAULT_ADDRESS_RESOLVER_GROUP = AddressResolverGroupProvider
+ .addressResolverGroup();
+
static {
int threads = Math.max(1, SystemPropertyUtil.getInt("io.netty.eventLoopThreads",
@@ -147,6 +156,8 @@ public class DefaultClientResources implements ClientResources {
private final Tracing tracing;
+ private final AddressResolverGroup> addressResolverGroup;
+
private volatile boolean shutdownCalled = false;
protected DefaultClientResources(Builder builder) {
@@ -243,6 +254,7 @@ protected DefaultClientResources(Builder builder) {
reconnectDelay = builder.reconnectDelay;
nettyCustomizer = builder.nettyCustomizer;
tracing = builder.tracing;
+ addressResolverGroup = builder.addressResolverGroup;
if (!sharedTimer && timer instanceof HashedWheelTimer) {
((HashedWheelTimer) timer).start();
@@ -308,6 +320,8 @@ public static class Builder implements ClientResources.Builder {
private Tracing tracing = Tracing.disabled();
+ private AddressResolverGroup> addressResolverGroup = DEFAULT_ADDRESS_RESOLVER_GROUP;
+
private Builder() {
}
@@ -569,6 +583,25 @@ public Builder tracing(Tracing tracing) {
return this;
}
+ /**
+ * Sets the {@link AddressResolverGroup} for dns resolution. This option is only effective if
+ * {@link DnsResolvers#UNRESOLVED} is used as {@link DnsResolver}. Defaults to
+ * {@link io.netty.resolver.DefaultAddressResolverGroup#INSTANCE} if {@literal netty-dns-resolver} is not available,
+ * otherwise defaults to {@link io.netty.resolver.dns.DnsAddressResolverGroup}.
+ *
+ * @param addressResolverGroup the {@link AddressResolverGroup} instance, must not be {@code null}.
+ * @return {@code this} {@link ClientResources.Builder}
+ * @since xxx
+ */
+ @Override
+ public Builder addressResolverGroup(AddressResolverGroup> addressResolverGroup) {
+
+ LettuceAssert.notNull(addressResolverGroup, "AddressResolverGroup must not be null");
+
+ this.addressResolverGroup = addressResolverGroup;
+ return this;
+ }
+
/**
* @return a new instance of {@link DefaultClientResources}.
*/
@@ -603,7 +636,7 @@ public DefaultClientResources.Builder mutate() {
.commandLatencyPublisherOptions(commandLatencyPublisherOptions()).dnsResolver(dnsResolver())
.eventBus(eventBus()).eventExecutorGroup(eventExecutorGroup()).reconnectDelay(reconnectDelay)
.socketAddressResolver(socketAddressResolver()).nettyCustomizer(nettyCustomizer()).timer(timer())
- .tracing(tracing());
+ .tracing(tracing()).addressResolverGroup(addressResolverGroup());
builder.sharedCommandLatencyCollector = sharedEventLoopGroupProvider;
builder.sharedEventExecutor = sharedEventExecutor;
@@ -742,4 +775,9 @@ public Tracing tracing() {
return tracing;
}
+ @Override
+ public AddressResolverGroup> addressResolverGroup() {
+ return addressResolverGroup;
+ }
+
}
diff --git a/src/main/java/io/lettuce/core/resource/EpollProvider.java b/src/main/java/io/lettuce/core/resource/EpollProvider.java
index 53613c8046..ec0be70deb 100644
--- a/src/main/java/io/lettuce/core/resource/EpollProvider.java
+++ b/src/main/java/io/lettuce/core/resource/EpollProvider.java
@@ -22,9 +22,11 @@
import io.netty.channel.Channel;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.epoll.Epoll;
+import io.netty.channel.epoll.EpollDatagramChannel;
import io.netty.channel.epoll.EpollDomainSocketChannel;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.epoll.EpollSocketChannel;
+import io.netty.channel.socket.DatagramChannel;
import io.netty.channel.unix.DomainSocketAddress;
import io.netty.util.concurrent.EventExecutorGroup;
import io.netty.util.internal.SystemPropertyUtil;
@@ -36,6 +38,7 @@
* the {@literal netty-transport-native-epoll} library during runtime. Internal API.
*
* @author Mark Paluch
+ * @author Yohei Ueki
* @since 4.4
*/
public class EpollProvider {
@@ -153,6 +156,13 @@ public Class extends Channel> socketChannelClass() {
return null;
}
+ @Override
+ public Class extends DatagramChannel> datagramChannelClass() {
+
+ checkForEpollLibrary();
+ return null;
+ }
+
}
/**
@@ -194,6 +204,14 @@ public Class extends Channel> socketChannelClass() {
return EpollSocketChannel.class;
}
+ @Override
+ public Class extends DatagramChannel> datagramChannelClass() {
+
+ checkForEpollLibrary();
+
+ return EpollDatagramChannel.class;
+ }
+
@Override
public Class extends EventLoopGroup> eventLoopGroupClass() {
diff --git a/src/main/java/io/lettuce/core/resource/EventLoopResources.java b/src/main/java/io/lettuce/core/resource/EventLoopResources.java
index de95355823..3ebe241866 100644
--- a/src/main/java/io/lettuce/core/resource/EventLoopResources.java
+++ b/src/main/java/io/lettuce/core/resource/EventLoopResources.java
@@ -21,12 +21,14 @@
import io.netty.channel.Channel;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.epoll.EpollEventLoopGroup;
+import io.netty.channel.socket.DatagramChannel;
import io.netty.util.concurrent.EventExecutorGroup;
/**
* Interface to encapsulate EventLoopGroup resources.
*
* @author Mark Paluch
+ * @author Yohei Ueki
* @since 6.0
*/
public interface EventLoopResources {
@@ -58,6 +60,11 @@ public interface EventLoopResources {
*/
Class extends Channel> socketChannelClass();
+ /**
+ * @return the {@link DatagramChannel} class.
+ */
+ Class extends DatagramChannel> datagramChannelClass();
+
/**
* @return the {@link EventLoopGroup} class.
*/
diff --git a/src/main/java/io/lettuce/core/resource/KqueueProvider.java b/src/main/java/io/lettuce/core/resource/KqueueProvider.java
index a938e2aa1b..5e250376f0 100644
--- a/src/main/java/io/lettuce/core/resource/KqueueProvider.java
+++ b/src/main/java/io/lettuce/core/resource/KqueueProvider.java
@@ -22,9 +22,11 @@
import io.netty.channel.Channel;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.kqueue.KQueue;
+import io.netty.channel.kqueue.KQueueDatagramChannel;
import io.netty.channel.kqueue.KQueueDomainSocketChannel;
import io.netty.channel.kqueue.KQueueEventLoopGroup;
import io.netty.channel.kqueue.KQueueSocketChannel;
+import io.netty.channel.socket.DatagramChannel;
import io.netty.channel.unix.DomainSocketAddress;
import io.netty.util.concurrent.EventExecutorGroup;
import io.netty.util.internal.SystemPropertyUtil;
@@ -36,6 +38,7 @@
* the {@literal netty-transport-native-kqueue} library during runtime. Internal API.
*
* @author Mark Paluch
+ * @author Yohei Ueki
* @since 4.4
*/
public class KqueueProvider {
@@ -153,6 +156,15 @@ public Class extends Channel> socketChannelClass() {
return null;
}
+ @Override
+ public Class extends DatagramChannel> datagramChannelClass() {
+
+
+ checkForKqueueLibrary();
+ return null;
+ }
+
+
}
/**
@@ -194,6 +206,15 @@ public Class extends Channel> socketChannelClass() {
return KQueueSocketChannel.class;
}
+ @Override
+ public Class extends DatagramChannel> datagramChannelClass() {
+
+ checkForKqueueLibrary();
+
+ return KQueueDatagramChannel.class;
+ }
+
+
@Override
public Class extends EventLoopGroup> eventLoopGroupClass() {
diff --git a/src/test/java/io/lettuce/core/resource/DefaultClientResourcesUnitTests.java b/src/test/java/io/lettuce/core/resource/DefaultClientResourcesUnitTests.java
index e98f53cc55..0eb49f02cd 100644
--- a/src/test/java/io/lettuce/core/resource/DefaultClientResourcesUnitTests.java
+++ b/src/test/java/io/lettuce/core/resource/DefaultClientResourcesUnitTests.java
@@ -30,6 +30,7 @@
import io.lettuce.test.TestFutures;
import io.lettuce.test.resource.FastShutdown;
import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.resolver.AddressResolverGroup;
import io.netty.util.HashedWheelTimer;
import io.netty.util.Timer;
import io.netty.util.concurrent.EventExecutorGroup;
@@ -39,6 +40,7 @@
* Unit tests for {@link DefaultClientResources}.
*
* @author Mark Paluch
+ * @author Yohei Ueki
*/
class DefaultClientResourcesUnitTests {
@@ -108,16 +110,19 @@ void testProvidedResources() {
EventBus eventBusMock = mock(EventBus.class);
CommandLatencyCollector latencyCollectorMock = mock(CommandLatencyCollector.class);
NettyCustomizer nettyCustomizer = mock(NettyCustomizer.class);
+ AddressResolverGroup> addressResolverGroup = mock(AddressResolverGroup.class);
DefaultClientResources sut = DefaultClientResources.builder().eventExecutorGroup(executorMock)
.eventLoopGroupProvider(groupProviderMock).timer(timerMock).eventBus(eventBusMock)
- .commandLatencyRecorder(latencyCollectorMock).nettyCustomizer(nettyCustomizer).build();
+ .commandLatencyRecorder(latencyCollectorMock).nettyCustomizer(nettyCustomizer)
+ .addressResolverGroup(addressResolverGroup).build();
assertThat(sut.eventExecutorGroup()).isSameAs(executorMock);
assertThat(sut.eventLoopGroupProvider()).isSameAs(groupProviderMock);
assertThat(sut.timer()).isSameAs(timerMock);
assertThat(sut.eventBus()).isSameAs(eventBusMock);
assertThat(sut.nettyCustomizer()).isSameAs(nettyCustomizer);
+ assertThat(sut.addressResolverGroup()).isSameAs(addressResolverGroup);
assertThat(TestFutures.getOrTimeout(sut.shutdown())).isTrue();
@@ -137,11 +142,11 @@ void mutateResources() {
Timer timerMock2 = mock(Timer.class);
EventBus eventBusMock = mock(EventBus.class);
CommandLatencyCollector latencyCollectorMock = mock(CommandLatencyCollector.class);
-
+ AddressResolverGroup> addressResolverGroupMock = mock(AddressResolverGroup.class);
ClientResources sut = ClientResources.builder().eventExecutorGroup(executorMock)
.eventLoopGroupProvider(groupProviderMock).timer(timerMock).eventBus(eventBusMock)
- .commandLatencyRecorder(latencyCollectorMock).build();
+ .commandLatencyRecorder(latencyCollectorMock).addressResolverGroup(addressResolverGroupMock).build();
ClientResources copy = sut.mutate().timer(timerMock2).build();
@@ -151,6 +156,7 @@ void mutateResources() {
assertThat(sut.timer()).isSameAs(timerMock);
assertThat(copy.timer()).isSameAs(timerMock2).isNotSameAs(timerMock);
assertThat(sut.eventBus()).isSameAs(eventBusMock);
+ assertThat(sut.addressResolverGroup()).isSameAs(addressResolverGroupMock);
assertThat(TestFutures.getOrTimeout(sut.shutdown())).isTrue();
diff --git a/src/test/jmh/io/lettuce/core/protocol/EmptyClientResources.java b/src/test/jmh/io/lettuce/core/protocol/EmptyClientResources.java
index 82a5373d8d..d38d2bd42b 100644
--- a/src/test/jmh/io/lettuce/core/protocol/EmptyClientResources.java
+++ b/src/test/jmh/io/lettuce/core/protocol/EmptyClientResources.java
@@ -28,6 +28,7 @@
import io.lettuce.core.metrics.CommandMetrics;
import io.lettuce.core.resource.*;
import io.lettuce.core.tracing.Tracing;
+import io.netty.resolver.AddressResolverGroup;
import io.netty.util.Timer;
import io.netty.util.concurrent.EventExecutorGroup;
import io.netty.util.concurrent.Future;
@@ -36,6 +37,7 @@
/**
* @author Mark Paluch
+ * @author Yohei Ueki
*/
public class EmptyClientResources implements ClientResources {
@@ -123,6 +125,11 @@ public Tracing tracing() {
return Tracing.disabled();
}
+ @Override
+ public AddressResolverGroup> addressResolverGroup() {
+ return null;
+ }
+
public static class EmptyCommandLatencyCollector implements CommandLatencyCollector {
@Override