问题:使用redisson连接redis时出现如下错误
org.redisson.client.RedisConnectionException: Unable to connect to Redis server: localhost/127.0.0.1:6379
at org.redisson.connection.pool.ConnectionPool$2$1.operationComplete(ConnectionPool.java:137) ~[redisson-3.8.1.jar:na]
at io.netty.util.concurrent.DefaultPromise.notifyListener0(DefaultPromise.java:511) ~[netty-common-4.1.29.Final.jar:4.1.29.Final]
at io.netty.util.concurrent.DefaultPromise.notifyListenersNow(DefaultPromise.java:485) ~[netty-common-4.1.29.Final.jar:4.1.29.Final]
at io.netty.util.concurrent.DefaultPromise.notifyListeners(DefaultPromise.java:424) ~[netty-common-4.1.29.Final.jar:4.1.29.Final]
at io.netty.util.concurrent.DefaultPromise.tryFailure(DefaultPromise.java:121) ~[netty-common-4.1.29.Final.jar:4.1.29.Final]
at org.redisson.misc.RedissonPromise.tryFailure(RedissonPromise.java:108) ~[redisson-3.8.1.jar:na]
at org.redisson.connection.pool.ConnectionPool.promiseFailure(ConnectionPool.java:305) ~[redisson-3.8.1.jar:na]
...
Caused by: java.lang.IllegalStateException: failed to create a new resolver
at io.netty.resolver.AddressResolverGroup.getResolver(AddressResolverGroup.java:71) ~[netty-resolver-4.1.29.Final.jar:4.1.29.Final]
at io.netty.bootstrap.Bootstrap.doResolveAndConnect0(Bootstrap.java:200) ~[netty-transport-4.1.29.Final.jar:4.1.29.Final]
at io.netty.bootstrap.Bootstrap.access$000(Bootstrap.java:49) ~[netty-transport-4.1.29.Final.jar:4.1.29.Final]
...
Caused by: java.lang.ClassCastException: io.netty.bootstrap.FailedChannel cannot be cast to io.netty.channel.socket.DatagramChannel
at io.netty.resolver.dns.DnsNameResolver.<init>(DnsNameResolver.java:359) ~[netty-resolver-dns-4.1.29.Final.jar:4.1.29.Final]
at io.netty.resolver.dns.DnsNameResolverBuilder.build(DnsNameResolverBuilder.java:412) ~[netty-resolver-dns-4.1.29.Final.jar:4.1.29.Final]
at io.netty.resolver.dns.DnsAddressResolverGroup.newNameResolver(DnsAddressResolverGroup.java:114) ~[netty-resolver-dns-4.1.29.Final.jar:4.1.29.Final]
...
检查过环境,redis已经启动,而且端口没错,程序连接的地址和端口也没错,可以认为不是个人问题,那么错从何来?
排错
Caused by: java.lang.ClassCastException: io.netty.bootstrap.FailedChannel cannot be cast to io.netty.channel.socket.DatagramChannel
第一感觉,jar包冲突导致,再怎么说,我也不认为netty这种开源软件会犯这么低级的错误
复制了DnsNameResolver的源码到自己的项目,然后修改如下代码,并加入断点进行调试
调试会进入AbstractBootstrap的register方法,然后进入initAndRegister方法
结果在newChannel的时候出现异常,catch后竟然返回了一个FailedChannel,而外面的调用却认为返回的必定是个DatagramChannel。
原来真的是低级错误。。。 而且这个低级错误还把真正的异常给屏蔽掉了。
真正的异常
查看调试到的异常变量
真正的异常原来是java.net.SocketException: “maximum number of DatagramSockets reached”
解决方法
从调试中可以知道 this.channelFactory类型为ReflectiveChannelFactory,它的newChannel方法:
public T newChannel() {
try {
return (Channel)this.clazz.getConstructor().newInstance();
} catch (Throwable var2) {
throw new ChannelException("Unable to create Channel from class " + this.clazz, var2);
}
}
而在这里,给它的类型是NioDatagramChannel
public NioDatagramChannel() {
this(newSocket(DEFAULT_SELECTOR_PROVIDER));
}
private static java.nio.channels.DatagramChannel newSocket(SelectorProvider provider) {
try {
return provider.openDatagramChannel();
} catch (IOException var2) {
throw new ChannelException("Failed to open a socket.", var2);
}
}
openDatagramChannel方法
public DatagramChannel openDatagramChannel() throws IOException {
return new DatagramChannelImpl(this);
}
DatagramChannelImpl的构造
public DatagramChannelImpl(SelectorProvider var1) throws IOException {
super(var1);
ResourceManager.beforeUdpCreate();
ResourceManager的beforeUdpCreate实现
private static final int DEFAULT_MAX_SOCKETS = 25;
private static final int maxSockets;
private static final AtomicInteger numSockets;
public ResourceManager() {
}
public static void beforeUdpCreate() throws SocketException {
if (System.getSecurityManager() != null && numSockets.incrementAndGet() > maxSockets) {
numSockets.decrementAndGet();
throw new SocketException("maximum number of DatagramSockets reached");
}
}
...
static {
String var0 = (String)AccessController.doPrivileged(new GetPropertyAction("sun.net.maxDatagramSockets"));
int var1 = 25;
try {
if (var0 != null) {
var1 = Integer.parseInt(var0);
}
} catch (NumberFormatException var3) {
;
}
maxSockets = var1;
numSockets = new AtomicInteger(0);
}
从这里可以看出,UDP socket最大数默认限定为25,但是可以通过系统属性(“sun.net.maxDatagramSockets”)来修改。
在程序启动的时候,加入 -Dsun.net.maxDatagramSockets=xxx即可。
最后的疑问
什么原因导致这种问题?
我的程序中刚好还启动了embeded cassandra,以及使用了cassandra-driver-core来连接cassandra,因此UDP socket一下子就被占用了很多,而redisson在启动的时候需要初始化自己的连接池,刚好也需要用到UDP socket,所以就导致了这个问题。而netty的ClassCastException吃掉了真正的异常,使得这个问题的排查增加了难度。