一个有点古怪的问题:Redisson无法连接redis

问题:使用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.(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的源码到自己的项目,然后修改如下代码,并加入断点进行调试
一个有点古怪的问题:Redisson无法连接redis_第1张图片

调试会进入AbstractBootstrapregister方法,然后进入initAndRegister方法
一个有点古怪的问题:Redisson无法连接redis_第2张图片
结果在newChannel的时候出现异常,catch后竟然返回了一个FailedChannel,而外面的调用却认为返回的必定是个DatagramChannel

原来真的是低级错误。。。 而且这个低级错误还把真正的异常给屏蔽掉了。


真正的异常

查看调试到的异常变量
一个有点古怪的问题:Redisson无法连接redis_第3张图片
真正的异常原来是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();

ResourceManagerbeforeUdpCreate实现

    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吃掉了真正的异常,使得这个问题的排查增加了难度。

你可能感兴趣的:(redis)