Redisson域名解析NullPointerException问题排查

266 阅读1分钟

问题现象

服务日志中存在以下报错: [redisson-netty-1-49] io.netty.util.concurrent.DefaultPromise [581] -| An exception was thrown by org.redisson.connection.ServiceManager$$Lambda$1220/1368778533.operationComplete() java.lang.NullPointerException: null at org.redisson.connection.ServiceManager.lambda$resolveAll$1(ServiceManager.java:305) ~[redisson-3.27.1.jar:3.27.1] at io.netty.util.concurrent.DefaultPromise.notifyListener0(DefaultPromise.java:578) ~[netty-common-4.1.68.Final.jar:4.1.68.Final] image.png

问题排查

1.阅读源码

报错服务依赖Redisson的版本是3.27.1版本,这个方法的作用是将一个可能包含域名的 RedisURI 对象解析为一个或多个包含 IP 地址的 RedisURI 对象,并返回这些对象的列表。这个方法是异步的,它将立即返回一个 CompletableFuture 对象,调用者可以使用这个对象来等待解析结果。

public CompletableFuture<List<RedisURI>> resolveAll(RedisURI uri) {
    if (uri.isIP()) {
        RedisURI mappedUri = toURI(uri.getScheme(), uri.getHost(), "" + uri.getPort());
        return CompletableFuture.completedFuture(Collections.singletonList(mappedUri));
    }

    AddressResolver<InetSocketAddress> resolver = resolverGroup.getResolver(group.next());
    Future<List<InetSocketAddress>> f = resolver.resolveAll(InetSocketAddress.createUnresolved(uri.getHost(), uri.getPort()));
    CompletableFuture<List<RedisURI>> result = new CompletableFuture<>();
    f.addListener((GenericFutureListener<Future<List<InetSocketAddress>>>) future -> {
        List<RedisURI> nodes = future.getNow().stream().map(addr -> {
            return toURI(uri.getScheme(), addr.getAddress().getHostAddress(), "" + addr.getPort());
        }).collect(Collectors.toList());
        result.complete(nodes);
    });
    return result;
}

调用的场景为:SentinelConnectionManager :SentinelConnectionManager 和 performSentinelDNSCheck。这两个方法用于管理和执行 Redis Sentinel 的 DNS 检查。

private void SentinelConnectionManager() {
    monitorFuture = serviceManager.newTimeout(t -> {
        CompletableFuture<Void> f = performSentinelDNSCheck();
        f.thenAccept(r -> scheduleSentinelDNSCheck());
    }, config.getDnsMonitoringInterval(), TimeUnit.MILLISECONDS);
}

private CompletableFuture<Void> performSentinelDNSCheck() {
    List<CompletableFuture<List<RedisURI>>> futures = new ArrayList<>();
    for (RedisURI host : sentinelHosts) {
        CompletableFuture<List<RedisURI>> allNodes = serviceManager.resolveAll(host);
        CompletableFuture<List<RedisURI>> f = allNodes.whenComplete((nodes, ex) -> {
            if (ex != null) {
                log.error("Unable to resolve {}", host.getHost(), ex);
                return;
            }

            nodes.stream()
                    .filter(uri -> {
                        return !sentinels.containsKey(uri) && !disconnectedSentinels.contains(uri);
                    })
                    .forEach(uri -> {
                        try {
                            byte[] addr = NetUtil.createByteArrayFromIpAddressString(uri.getHost());
                            InetSocketAddress address = new InetSocketAddress(InetAddress.getByAddress(host.getHost(), addr), uri.getPort());
                            registerSentinel(address);
                        } catch (UnknownHostException e) {
                            log.error(e.getMessage(), e);
                        }
                    });
        });
        futures.add(f);
    }
    return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
}

2. 分析代码

从上面的代码来看主要是在future.getNow()中没有判断返回的状态,如果在获取域名解析结果异常的时候就会netty就会解析为空指针。这个时候就会报错空指针异常。

image.png

3.尝试在github的issue中查找有无类似问题

翻阅issue列表,找到类似的问题。并且官方已于3.27.2版本修复。 NullPointerException in ServiceManager.resolveAll(..) · Issue #5675 · redisson/redisson · GitHub

image.png

总结

通过对这个问题的排查的过程,可以看出来当使用一些开源框架时,需要充分的了解细节,同时需要及时关注官方修复的内容。