问题现象
服务日志中存在以下报错:
[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]
问题排查
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就会解析为空指针。这个时候就会报错空指针异常。
3.尝试在github的issue中查找有无类似问题
翻阅issue列表,找到类似的问题。并且官方已于3.27.2版本修复。 NullPointerException in ServiceManager.resolveAll(..) · Issue #5675 · redisson/redisson · GitHub
总结
通过对这个问题的排查的过程,可以看出来当使用一些开源框架时,需要充分的了解细节,同时需要及时关注官方修复的内容。