40 Failed to check the status of the service com.hx.UserService 问题

前言 

大概是 本周周二[02.26]的时候吧, 我们同事 遇到了这样一个问题, 这个项目 不依赖其他项目, 然后 在开发环境启动能够启动成功, 然后 在测试环境 启动, 却抛出了 "no provider" 的异常 

Caused by: java.lang.IllegalStateException: Failed to check the status of the service com.hx.helloDubbo.service.UserService. No provider available for the service com.hx.helloDubbo.service.UserService from the url zookeeper://192.168.0.190:2181/com.alibaba.dubbo.registry.RegistryService?application=HelloDubboProvider&dubbo=2.5.3&interface=com.hx.helloDubbo.service.UserService&methods=hello,saveUser,addFriends&pid=7000&retries=0&revision=0.0.1&side=consumer&timestamp=1551513161891 to the consumer 192.168.17.1 use dubbo version 2.5.3
	at com.alibaba.dubbo.config.ReferenceConfig.createProxy(ReferenceConfig.java:420)

因为 这个项目, 没有依赖,  service1 依赖 service2, service1, service2 都是 @Service 注解声明的实例, 然后 provider 放出去了这两个 服务, 其他的 就没得啥子了, 结果 尼玛 报了一个 "no provider"

 

这里 先说一下 问题的原因, 以免各位上下文的不完全清楚 

除了 上面所介绍的一些上下文之外, 还有一点, 就是 在该项目的 custom.xml 里面多声明了一个 reference 消费了自己项目放出去的 IService2 

然后 开发环境这边, 我们另外一台机器 启动了这个项目, 有 IService2 对应的服务, 所以 开发环境启动起来了 

然后 测试环境这边, 只有一台机器, 启动这个项目的时候, IService2 还未注册到注册中心, 然后 reference 对应的 referenceBean 初始化的时候 校验给定的 服务是否可用的时候 抛出了异常  

ReferenceConfig. createProxy 

if (c && ! invoker.isAvailable()) {
            throw new IllegalStateException("Failed to check the status of the service " + interfaceName + ". No provider available for the service " + (group == null ? "" : group + "/") + interfaceName + (version == null ? "" : ":" + version) + " from the url " + invoker.getUrl() + " to the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion());
        }

项目基本信息 : dubbo 版本 : 2.5.3 

 

问题的排查

这个问题, 首先 就是根据 现有的线索, 在 ReferenceConfig.java:420 打个断点, 看一下情况 

对应的 invoker 实例是 MockClusterInvoker, isAvailable 方法委托给了 组合的 RegistryDirectory 实例 

进 RegistryDirectory. isAvailable 方法发现, 对应的 urlInvokerMap 为空, 而 同样的情况 在开发环境, 这里 urlInvokerMap 是有 providerUrl 的 

然后 来到 RegistryDirectory. refreshInvoker 方法打个断点, 发现 category 为 provider 的时候, 开发环境 和 测试环境 对应的 invokeUrls 里面的协议头不一样, 开发环境是 正常的 "dubbo://", 而测试环境同样的情况下, 协议头是 "empty://" 

然后 网上回溯, 找到 这里 url 的生成的地方, ZookeeperRegistry. doSubscribe 

        这里面 查询了 三个 category[providers, configurators, routers], 然后每个 category 添加对应的 listener, 对应 zk 目录下面的 有什么变动的话, 通知到 RegistryDirectory 这边进行相应的变化处理 [刷新 providers, routers 等等] 

        然后 添加了 listener 之后, 显式通知了一下 RegistryDirectory 根据现有的数据 进行刷新 

在 ZookeeperRegistry. doSubscribe 里面, 开发环境 和 测试环境 主要的不同 在于 zkClient.addChildListener 

        开发环境这边, 拿到了 对应的 /dubbo/$category 路径下面的子目录, 然后 测试环境这边 拿到的是 空[没有provider]

        然后 经过 toUrlsWithEmpty 封装一下, 就成了我们 上面 看到的, 两个环境的 不同的协议头 [开发环境是 正常的 "dubbo://", 而测试环境同样的情况下, 协议头是 "empty://" ]

 

这个问题, 主要是 一开始没想到 会有这么一句 reference 引用自己的服务, 实际也不应该有, 所以 一开始发现这个问题的时候 缺少大致的方向

不过 现在看来的话, 这里报错的是 ReferenceConfig 就应该想到是 对应的消费的服务出现的问题, 直接 看 provider.xml 

 

 附一下这里 构造这种情况的方式 

providerAndCustom.xml 

<dubbo:application name="HelloDubboProvider" />

	<!-- zookeeper注册中心 -->
	<dubbo:registry protocol="zookeeper" address="192.168.0.190:2181" />
	<dubbo:protocol name="dubbo" port="20881" />

	<bean id="localUserService" class="com.hx.helloDubbo.local.service.LocalUserService" >
		<property name="userService" ref="userServiceFromDubbo" />
	</bean>
	<!--<dubbo:reference id="userServiceFromDubbo" interface="com.hx.helloDubbo.service.UserService" check="false" retries="0" />-->
	<dubbo:reference id="userServiceFromDubbo" interface="com.hx.helloDubbo.service.UserService" retries="0" />

	<dubbo:service interface="com.hx.helloDubbo.service.UserService" ref="userService" />

	<bean id="userService" class="com.hx.helloDubbo.service.impl.UserServiceImpl02" />
	<bean id="userService02" class="com.hx.helloDubbo.service.impl.UserServiceImpl" />

 

总结

1. 其实 再抽象一下的话, 这个问题 就是一个 普通的 "no provider", 只不过 我们发现这个问题的时候, 没有向这个方向考虑, 导致走了一些弯路 

 

2. 附上一些 注册中心上面创建如下目录结构的代码 

 

RegistryProtocol. doRefer

 

ZookeeperRegistry. doSubscribe

ZookeeperRegistry. toCategoriesPath

 

 

完