Quarkus原生构建问题:未找到NameResolverProviders致etcd通信失败
我有一个经充分测试的Quarkus微服务,使用最新版Quarkus 3.2.2.Final,尝试构建为native模式时出现问题,该问题在JVM模式下不存在。
启动原生微服务时,会收到警告:
"io.grpc.NameResolverRegistry","level":"WARNING","message":"No NameResolverProviders found via ServiceLoader, including for DNS. This is probably due to a broken build. If using ProGuard, check your configuration"
随后与etcd的通信失败,报错cannot find a NameResolver for ip:///localhost:2379,应用退出。
在JVM DEBUG模式下可见共注册了7个解析器。我尝试显式注册反射类,但无效果:
@RegisterForReflection(targets={ IPNameResolver.class, HttpNameResolver.class, HttpResolverProvider.class, IPResolverProvider.class, DnsSrvNameResolver.class, NameResolverProvider.class, LoadBalancerProvider.class, DnsNameResolverProvider.class, NameResolverRegistry.class })
我怀疑问题并非类未正确注册,而是原生模式下动态类加载的特性导致。构建过程中无警告,请问下一步该如何排查?
编辑1:已安装特性
Installed features: [cache, cdi, hibernate-validator, kafka-client, micrometer, mongodb-client, mongodb-panache, narayana-jta, quartz, reactive-routes, resteasy-reactive, resteasy-reactive-jackson, scheduler, security, security-properties-file, smallrye-context-propagation, smallrye-health, smallrye-jwt, smallrye-reactive-messaging, smallrye-reactive-messaging-kafka, vertx]
原生构建命令行中可见:-H:-UseServiceLoaderFeature
编辑2:堆栈跟踪
2023-07-27 15:50:24,930 ERROR [io.qua.run.Application] (main) Failed to start application (with profile [prod]): java.lang.RuntimeException: Failed to start quarkus at io.quarkus.runner.ApplicationImpl.doStart(Unknown Source) at io.quarkus.runtime.Application.start(Application.java:101) at io.quarkus.runtime.ApplicationLifecycleManager.run(ApplicationLifecycleManager.java:111) at io.quarkus.runtime.Quarkus.run(Quarkus.java:71) at io.quarkus.runtime.Quarkus.run(Quarkus.java:44) at io.quarkus.runtime.Quarkus.run(Quarkus.java:124) at com.y.z.responsedispatcher.ResponseDispatcher.main(ResponseDispatcher.java:10) Caused by: java.lang.IllegalArgumentException: cannot find a NameResolver for ip:///localhost:2379 at io.grpc.internal.ManagedChannelImpl.getNameResolver(ManagedChannelImpl.java:742) at io.grpc.internal.ManagedChannelImpl.getNameResolver(ManagedChannelImpl.java:751) at io.grpc.internal.ManagedChannelImpl.<init>(ManagedChannelImpl.java:631) at io.grpc.internal.ManagedChannelImplBuilder.build(ManagedChannelImplBuilder.java:631) at io.grpc.internal.AbstractManagedChannelImplBuilder.build(AbstractManagedChannelImplBuilder.java:297) at io.vertx.grpc.VertxChannelBuilder.build(VertxChannelBuilder.java:309) at io.etcd.jetcd.impl.ClientConnectionManager.getChannel(ClientConnectionManager.java:70) at io.etcd.jetcd.impl.ClientConnectionManager.newStub(ClientConnectionManager.java:102) at io.etcd.jetcd.impl.WatchImpl.<init>(WatchImpl.java:68) at io.etcd.jetcd.impl.ClientImpl.lambda$new$5(ClientImpl.java:53) at io.etcd.jetcd.support.MemorizingClientSupplier.get(MemorizingClientSupplier.java:37) at io.etcd.jetcd.impl.ClientImpl.getWatchClient(ClientImpl.java:85) at y.z.etcd.commons.core.EtcdClient.watchValues(EtcdClient.java:118) at y.z.etcd.commons.core.EtcdClient.watchValues(EtcdClient.java:112) at y.z.etcd.commons.core.EtcdClient_ClientProxy.watchValues(Unknown Source) at y.z.etcd.commons.z.services.zEtcdCache.init(zEtcdCache.java:41) at y.z.etcd.commons.z.services.zEtcdCache_Bean.doCreate(Unknown Source) at y.z.etcd.commons.z.services.zEtcdCache_Bean.create(Unknown Source) at y.z.etcd.commons.z.services.zEtcdCache_Bean.create(Unknown Source) at io.quarkus.arc.impl.AbstractSharedContext.createInstanceHandle(AbstractSharedContext.java:113) at io.quarkus.arc.impl.AbstractSharedContext$1.get(AbstractSharedContext.java:37) at io.quarkus.arc.impl.AbstractSharedContext$1.get(AbstractSharedContext.java:34) at io.quarkus.arc.impl.LazyValue.get(LazyValue.java:26) at io.quarkus.arc.impl.ComputingCache.computeIfAbsent(ComputingCache.java:69) at io.quarkus.arc.impl.AbstractSharedContext.get(AbstractSharedContext.java:34) at io.quarkus.arc.impl.ClientProxies.getApplicationScopedDelegate(ClientProxies.java:21) at y.z.etcd.commons.z.services.zEtcdCache_ClientProxy.arc$delegate(Unknown Source) at y.z.etcd.commons.z.services.zEtcdCache_ClientProxy.arc_contextualInstance(Unknown Source) at y.z.etcd.commons.z.services.zEtcdCache_Observer_Synthetic_7e505f92dfea7e2f74425f463632933f0b546710.notify(Unknown Source) at io.quarkus.arc.impl.EventImpl$Notifier.notifyObservers(EventImpl.java:346) at io.quarkus.arc.impl.EventImpl$Notifier.notify(EventImpl.java:328) at io.quarkus.arc.impl.EventImpl.fire(EventImpl.java:82) at io.quarkus.arc.runtime.ArcRecorder.fireLifecycleEvent(ArcRecorder.java:155) at io.quarkus.arc.runtime.ArcRecorder.handleLifecycleEvents(ArcRecorder.java:106) at io.quarkus.deployment.steps.LifecycleEventsBuildStep$startupEvent1144526294.deploy_0(Unknown Source) at io.quarkus.deployment.steps.LifecycleEventsBuildStep$startupEvent1144526294.deploy(Unknown Source) ... 7 more
1. 修复-H:-UseServiceLoaderFeature参数问题
构建命令中的-H:-UseServiceLoaderFeature是核心问题——该参数禁用了GraalVM的ServiceLoader特性,而gRPC正是通过ServiceLoader加载NameResolverProvider实现类的。JVM模式下ServiceLoader可正常工作,但native模式下禁用该特性后,所有依赖ServiceLoader的动态加载都会失效,这就是为什么没有任何NameResolverProviders被注册的原因。
解决方法:移除构建命令中的-H:-UseServiceLoaderFeature参数。如果是通过Quarkus配置文件设置的,找到对应配置项删除即可。
2. 手动注册gRPC解析器(必须保留上述参数时)
如果因特殊需求必须保留-H:-UseServiceLoaderFeature,需要手动注册gRPC的NameResolverProvider实现:
- 创建应用启动时的注册类:
import io.grpc.NameResolverRegistry; import io.grpc.ipv4.IPv4NameResolverProvider; import io.grpc.ipv6.IPv6NameResolverProvider; import io.grpc.dns.DnsNameResolverProvider; import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.event.Observes; import io.quarkus.runtime.StartupEvent; @ApplicationScoped public class GrpcResolverRegistrar { void onStartup(@Observes StartupEvent event) { NameResolverRegistry registry = NameResolverRegistry.getDefaultRegistry(); registry.register(IPv4NameResolverProvider.getInstance()); registry.register(IPv6NameResolverProvider.getInstance()); registry.register(DnsNameResolverProvider.getInstance()); // 若需要其他解析器,比如HttpResolverProvider,可在此处补充注册 } }
- 确保这些Provider类被包含进native镜像:通过
@RegisterForReflection标注,或在quarkus.native.additional-build-args中添加参数:
--initialize-at-build-time=io.grpc.ipv4.IPv4NameResolverProvider,io.grpc.ipv6.IPv6NameResolverProvider,io.grpc.dns.DnsNameResolverProvider
3. 验证etcd客户端配置
etcd客户端使用的ip:///localhost:2379协议需要对应的IP NameResolver。确认已引入gRPC的IP resolver依赖(通常包含在grpc-netty或grpc-core中),且这些类在native构建时被正确包含。
4. 检查扩展兼容性
确保使用的etcd客户端(jetcd)与Quarkus 3.2.2.Final兼容,可尝试升级jetcd到最新版本,或查看Quarkus官方文档中关于gRPC和etcd的native支持说明。
5. 启用native构建详细日志
添加以下参数到native构建命令,查看哪些类未被正确注册,辅助定位问题:
-H:+ReportExceptionStackTraces -H:Log=registerResource:trace
内容的提问来源于stack exchange,提问作者stuhpa

