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 eventLoopGroupClass() { /** * @return the default {@link Channel} for socket (network/TCP) transport. */ - static Class socketChannelClass() { + public static Class socketChannelClass() { if (NativeTransports.isSocketSupported()) { return NativeTransports.socketChannelClass(); @@ -57,6 +60,18 @@ static Class socketChannelClass() { return NioSocketChannel.class; } + /** + * @return the default {@link DatagramChannel} for socket (network/UDP) transport. + */ + public static Class datagramChannelClass() { + + if (NativeTransports.isSocketSupported()) { + return NativeTransports.datagramChannelClass(); + } + + return NioDatagramChannel.class; + } + /** * Native transport support. */ @@ -79,6 +94,13 @@ static Class socketChannelClass() { return RESOURCES.socketChannelClass(); } + /** + * @return the native transport socket {@link DatagramChannel} class. + */ + static Class 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 socketChannelClass() { return null; } + @Override + public Class datagramChannelClass() { + + checkForEpollLibrary(); + return null; + } + } /** @@ -194,6 +204,14 @@ public Class socketChannelClass() { return EpollSocketChannel.class; } + @Override + public Class datagramChannelClass() { + + checkForEpollLibrary(); + + return EpollDatagramChannel.class; + } + @Override public Class 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 socketChannelClass(); + /** + * @return the {@link DatagramChannel} class. + */ + Class 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 socketChannelClass() { return null; } + @Override + public Class datagramChannelClass() { + + + checkForKqueueLibrary(); + return null; + } + + } /** @@ -194,6 +206,15 @@ public Class socketChannelClass() { return KQueueSocketChannel.class; } + @Override + public Class datagramChannelClass() { + + checkForKqueueLibrary(); + + return KQueueDatagramChannel.class; + } + + @Override public Class 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