项目A消费项目B的同一个dubbo类,如果存在不同的@Reference写法,可能会导致对应B服务下线后,对应内部连接不关闭,导致A调用调用B服务出现异常,此问题必现。
Environment- Dubbo version: 2.7.4.1 2.7.15,注册中心是 nacos ,本地客户端1.1.4和2.1.0都试过
- Operating System version: Mac Linux
- Java version: 1.8
-
项目B是个服务比如ProductInfoFacade 的服务提供者,项目A是消费项目B的ProductInfoFacade服务,服务中存在两种@Reference 写法。比如一个Service里面是 @Reference,另外个Service中是 @Reference(injvm = false)。
-
启动项目A和项目B服务,项目B多起几个服务(保证A能正常调用B服务)。关闭其中一台B服务的机器C,这个时候nacos会通知A服务去关闭这台C机器对应的所有dubbo消费者,正常会关闭C机器的netty连接,但是实际上由于有2个@Reference不同写法,会导致 org.apache.dubbo.rpc.protocol.dubbo.ReferenceCountExchangeClient 的 referenceCount 比正常时候多一个(每多一种这种同类的不同写法,就会比正常多一个),这样在关闭的时候,对应的netty连接不会关闭,因为ReferenceCountExchangeClient 的 close方法的 “referenceCount.decrementAndGet() <= 0” 不会小于等于0,导致最终不会关闭这个netty连接。后台有一个校验netty channel连接的任务“org.apache.dubbo.remoting.exchange.support.header.ReconnectTimerTask” (1分钟一次的)会一直重连这个netty连接,但是会失败走入到“logger.error("Fail to connect to " + channel, e);”(由于对应的C机器已经关闭了,肯定是连接不上,会有很多异常日志),同时 带@Reference(injvm = false)注解的ProductInfoFacade调用会报错,提示连接已断开。"org.apache.dubbo.rpc.RpcException: Failed to invoke remote method: getXXXX. provider: dubbo://10.132.138.241:20880/com.xxx.ProductInfoFacade?xxxxx. cause: message can not send. because channel is closed ",目前测试,带@Reference 注解的服务可以正常调度。
-
我们为什么加 @Reference(injvm = false)? 是由于我们这边并没有完全RPC化,存在
https://github.com/apache/dubbo/issues/6776
这个问题,这个问题也是2.7.8版本中解决https://github.com/apache/dubbo/issues/6224
在“org.apache.dubbo.config.spring.beans.factory.annotation.ReferenceAnnotationBeanPostProcessor.doGetInjectedBean”方法中添加了“prepareReferenceBean(referencedBeanName, referenceBean, localServiceBean);”导致的循环引用问题(“问题出现在 ServiceBean serviceBean = getServiceBean(referencedBeanName);”),代码如下@Override protected Object doGetInjectedBean(AnnotationAttributes attributes, Object bean, String beanName, Class<?> injectedType, InjectionMetadata.InjectedElement injectedElement) throws Exception { /** * The name of bean that annotated Dubbo's {@link Service @Service} in local Spring {@link ApplicationContext} */ String referencedBeanName = buildReferencedBeanName(attributes, injectedType); /** * The name of bean that is declared by {@link Reference @Reference} annotation injection */ String referenceBeanName = getReferenceBeanName(attributes, injectedType); referencedBeanNameIdx.computeIfAbsent(referencedBeanName, k -> new TreeSet<String>()).add(referenceBeanName); ReferenceBean referenceBean = buildReferenceBeanIfAbsent(referenceBeanName, attributes, injectedType); boolean localServiceBean = isLocalServiceBean(referencedBeanName, referenceBean, attributes); // 添加了这个逻辑导致的 prepareReferenceBean(referencedBeanName, referenceBean, localServiceBean); registerReferenceBean(referencedBeanName, referenceBean, localServiceBean, referenceBeanName); cacheInjectedReferenceBean(referenceBean, injectedElement); return getBeanFactory().applyBeanPostProcessorsAfterInitialization(referenceBean.get(), referenceBeanName); } .... private void prepareReferenceBean(String referencedBeanName, ReferenceBean referenceBean, boolean localServiceBean) { // Issue : https://github.com/apache/dubbo/issues/6224 if (localServiceBean) { // If the local @Service Bean exists referenceBean.setInjvm(Boolean.TRUE); exportServiceBeanIfNecessary(referencedBeanName); // If the referenced ServiceBean exits, export it immediately } } private void exportServiceBeanIfNecessary(String referencedBeanName) { if (existsServiceBean(referencedBeanName)) { // 在初始化这个ServiceBean的时候,如果遇到 @Reference这个类的,还会走这个方法,第二次就是一个Spring的引用。 ServiceBean serviceBean = getServiceBean(referencedBeanName); if (!serviceBean.isExported()) { serviceBean.export(); } } }
问题具体是 A服务的某个dubbo服务的实现类,如(ProductInfoFacadeImpl,实现的ProductInfoFacade),引入了B服务的一个Service,该Service中又有 @Reference这个 ProductInfoFacade,导致A服务在启动的时候,会去初始化 其他类的 @Reference这个 ProductInfoFacade服务时,会去加载A服务本地的ProductInfoFacadeImpl服务,Spring加载的,会去依赖B服务的Service,然后再是里面的 ProductInfoFacade时,又走了一次这个exportServiceBeanIfNecessary方法,这个时候获取的ServiceBean对象是一个SpringBean的引用,未完成初始化完,因为当前服务还在初始化这个ProductInfoFacade的ServiceBean,所以后面我们加了一个 @Reference(injvm = false),这样的话,就不会走 prepareReferenceBean 里面的 exportServiceBeanIfNecessary方法,完全走RPC了,不走本地服务,避免了这种循环依赖问题。
-
目前测试下来,如果是同一个dubbo服务的@Reference保持一样的写法,只留一种的话,不会存在这个问题。。如果存在2种,在关闭服务提供者的时候,消费者就会有问题。