diff --git a/pom.xml b/pom.xml index 7576e14ee3..a55f97afc5 100644 --- a/pom.xml +++ b/pom.xml @@ -206,6 +206,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 599c4c2249..d6c908d1cc 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 */ @@ -294,6 +295,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(Class eventLoopGroupClass) { for (;;) { diff --git a/src/main/java/io/lettuce/core/RedisClient.java b/src/main/java/io/lettuce/core/RedisClient.java index 76511ef79b..c501184396 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 diff --git a/src/main/java/io/lettuce/core/Transports.java b/src/main/java/io/lettuce/core/Transports.java index 7ec977d30d..a29b8b1233 100644 --- a/src/main/java/io/lettuce/core/Transports.java +++ b/src/main/java/io/lettuce/core/Transports.java @@ -30,6 +30,8 @@ import io.netty.channel.ChannelOption; 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.NioChannelOption; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.util.internal.logging.InternalLogger; @@ -40,9 +42,10 @@ * and domain socket transports. * * @author Mark Paluch + * @author Yohei Ueki * @since 4.4 */ -class Transports { +public class Transports { private static final InternalLogger logger = InternalLoggerFactory.getInstance(Transports.class); @@ -61,7 +64,7 @@ static Class eventLoopGroupClass() { /** * @return the default {@link Channel} for socket (network/TCP) transport. */ - static Class socketChannelClass() { + public static Class socketChannelClass() { if (NativeTransports.isAvailable()) { return NativeTransports.socketChannelClass(); @@ -70,6 +73,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; + } + /** * Initialize the {@link Bootstrap} and apply {@link SocketOptions}. * @@ -146,6 +161,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 ce1a5d9039..cfd700e629 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 diff --git a/src/main/java/io/lettuce/core/resource/AddressResolverGroupProvider.java b/src/main/java/io/lettuce/core/resource/AddressResolverGroupProvider.java new file mode 100644 index 0000000000..f0e663627b --- /dev/null +++ b/src/main/java/io/lettuce/core/resource/AddressResolverGroupProvider.java @@ -0,0 +1,65 @@ +package io.lettuce.core.resource; + +import java.util.function.Supplier; + +import io.lettuce.core.Transports; +import io.netty.channel.socket.SocketChannel; +import io.netty.resolver.AddressResolverGroup; +import io.netty.resolver.DefaultAddressResolverGroup; +import io.netty.resolver.dns.DnsAddressResolverGroup; +import io.netty.resolver.dns.DnsNameResolverBuilder; +import io.netty.util.internal.logging.InternalLogger; +import io.netty.util.internal.logging.InternalLoggerFactory; + +/** + * Wraps and provides {@link AddressResolverGroup} classes. This is to protect the user from {@link ClassNotFoundException}'s + * caused by the absence of the {@literal netty-dns-resolver} library during runtime. This class will be deleted when + * {@literal netty-dns-resolver} becomes mandatory. Internal API. + * + * @author Yohei Ueki + * @since xxx + */ +class AddressResolverGroupProvider { + + private static final InternalLogger logger = InternalLoggerFactory.getInstance(AddressResolverGroupProvider.class); + + private static final AddressResolverGroup 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 158a160bea..a995afbfec 100644 --- a/src/main/java/io/lettuce/core/resource/EpollProvider.java +++ b/src/main/java/io/lettuce/core/resource/EpollProvider.java @@ -25,9 +25,11 @@ import io.netty.channel.EventLoopGroup; import io.netty.channel.epoll.Epoll; import io.netty.channel.epoll.EpollChannelOption; +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; @@ -39,6 +41,7 @@ * the {@literal netty-transport-native-epoll} library during runtime. Internal API. * * @author Mark Paluch + * @author Yohei Ueki * @since 4.4 */ public class EpollProvider { @@ -168,6 +171,13 @@ public SocketAddress newSocketAddress(String socketPath) { return null; } + @Override + public Class datagramChannelClass() { + + checkForEpollLibrary(); + return null; + } + } /** @@ -209,6 +219,14 @@ public Class socketChannelClass() { return EpollSocketChannel.class; } + @Override + public Class datagramChannelClass() { + + checkForEpollLibrary(); + + return EpollDatagramChannel.class; + } + @Override public Class domainSocketChannelClass() { diff --git a/src/main/java/io/lettuce/core/resource/EventLoopResources.java b/src/main/java/io/lettuce/core/resource/EventLoopResources.java index 17b5365622..e9c3db0193 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 Domain Socket {@link Channel} class. */ diff --git a/src/main/java/io/lettuce/core/resource/KqueueProvider.java b/src/main/java/io/lettuce/core/resource/KqueueProvider.java index b8580212d0..20aebdee58 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 SocketAddress newSocketAddress(String socketPath) { 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 domainSocketChannelClass() { 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