业务应用报错找不到 Dubbo 服务提供者,简单排查了下,确认服务提供方没有注册上该服务,查了下代码,发现该接口有多个实现,每个实现都是 Dubbo 的服务提供者,通过 Dubbo 的 服务分组 进行区分。
从服务提供方的启动日志中可以看到有如下输出:
1 | 2021-12-06 22:32:48,432 WARN org.apache.dubbo.config.context.ConfigManager:492 - [DUBBO] Duplicate ServiceBean found, there already has one default ServiceBean or more than two ServiceBeans have the same id, you can try to give each ServiceBean a different id : <dubbo:service beanName="ServiceBean:me.tianshuang.service.TestService:1.0:group1" unexported="false" exported="false" ref="me.tianshuang.service.impl.TestServiceImpl@6ff7b3d5" interface="me.tianshuang.service.TestService" uniqueServiceName="group1/me.tianshuang.service.TestService:1.0" prefix="dubbo.service.me.tianshuang.service.TestService" deprecated="false" group="group1" dynamic="true" version="1.0" id="me.tianshuang.service.TestService" valid="true" />, dubbo version: 2.7.5, current host: 192.168.1.9 |
相同的问题在 GitHub 上可以看到已经有其他用户反馈:Dubbo 2.7.5: Duplicate ServiceBean found · Issue #5923,表现与我们本地测试的一致。上面日志输出的源码位于 ConfigManager.java at dubbo-2.7.5,对应的源码为:
1 | static <C extends AbstractConfig> void addIfAbsent(C config, Map<String, C> configsMap, boolean unique) |
查询调用栈帧可知由 InitDestroyAnnotationBeanPostProcessor
对该 Dubbo 服务对应的 ServiceBean
的 @PostConstruct
注解标记的 addIntoConfigManager()
方法处理时触发,源码位于 AbstractConfig.java at dubbo-2.7.5:
1 | /** |
根据上面的日志及源码我们知道,当服务有多个实现时,即在 Dubbo 2.7.5 中使用注解配置的服务分组时,首个服务实现之后的服务实现因为 getId(config)
方法返回的 key
与之前的服务实现返回的 key
相同,导致不会被加入 configsMap
,从而导致后续的服务导出不会将该服务实现进行导出。其中服务导出的源码位于 DubboBootstrap.java at dubbo-2.7.5,因为服务实现没有被加入至 configsMap
,从而使服务导出调用的 configManager.getServices()
返回的集合中没有包含我们需要导出的服务实现,最终造成消费者找不到服务提供者。
该问题在 GitHub 上有多个 issue 反馈,其中 2.7.5和2.7.6的group和version同一个接口只能注册一个服务 · Issue #6056 这个 issue 中提到:
我也发现了 使用注解是不行的 xml配置可以的
于是我在本地进行了测试,发现果然 XML 配置是可以正常进行服务导出的,而通过 Dubbo @Service 注解配置无法导出,在本地进行源码调试后,发现差异在于对 ServiceBean
的 id
属性的设置上。其中 XML 配置时,如果用户在 <dubbo:service />
中配置了 id
,那么会使用用户显式指定的 id
作为 ServiceBean
的 id
属性值,如果用户没有显式指定 id
, 那么会依次尝试使用配置的 name
与 interface
去作为 id
,且在此过程中,如果 id
值与之前的 id
值有重复,那么会将当前 id
后面追加数字以避免出现重复的 id
,该段处理逻辑位于 DubboBeanDefinitionParser.java at dubbo-2.7.5:
1 | private static BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass, boolean required) { |
即读取并设置不重复的 id
并设置至 beanDefinition
中,在后续实例化 ServiceBean
时会将该此处生成的 id
值设置至 ServiceBean
实例中,保证了服务的正常发布,这就是 issue 中有用户反馈 XML 配置可以正常使用服务分组的实现机制。
而当使用 Dubbo 的 @Service 注解配置服务实现时,在 @Service 的属性中,是没有提供 id
属性进行配置的,所以通过 @Service 配置 Dubbo 服务时,该 ServiceBean
的 id
属性值无法由用户显式指定,而是在实例化 ServiceBean
时在 setInterface(String interfaceName)
方法中被设置,其源码位于 ServiceConfigBase.java at dubbo-2.7.5:
1 | public void setInterface(String interfaceName) { |
可以看出,使用的接口名作为 ServiceBean
实例的 id
值,这也解释了我们一开始遇到的问题,当同一接口有多个实现且它们都通过 Dubbo @Service 注解配置,此时它们的 ServiceBean
具有相同的 id
值,导致只有该接口的第一个实现被加入到 configsMap
,也只有第一个实现进行了服务导出,后续的实现都没有被导出,最终导致了消费端的无提供者异常。
继续在 GitHub 的 issue 列表中搜索,可以看到在其中一个 issue 中作者给出了一个紧急的解决方案,该 issue 为:Dubbo 2.7.5 服务分组,provider服务接口提供多个group,注册中心只看到一个 · Issue #5779,其中介绍了一个类:DubboConfigDefaultPropertyValueBeanPostProcessor.java at master,其源码如下:
1 | /** |
可以看出,该类是自 Dubbo 2.7.6 开始提供,其实现非常简单,即在 id
属性未被赋值的情况下,将 id
属性设置为 Spring 中的 beanName
。我将该类集成到使用 Dubbo 2.7.5 的环境中以尝试解决 id
值重复的问题,发现存在两个问题,一个是该 BeanPostProcessor
实现了 PriorityOrdered
接口,导致该类作为 bean
实例化时,注意不能放入使用了 @Autowired
注解的配置类中,否则会导致该配置类的其他 bean 在 AutowiredAnnotationBeanPostProcessor
处理前执行,如果依赖 @Autowired
的字段,则会触发空指针异常,该问题其实在 PriorityOrdered (Spring Framework 5.3.13 API) 文档中已经有说明:
Note: PriorityOrdered post-processor beans are initialized in a special phase, ahead of other post-processor beans. This subtly affects their autowiring behavior: they will only be autowired against beans which do not require eager initialization for type matching.
另一个问题为根据 Dubbo 2.7.5 的源码,在执行 BeanPostProcessor
的处理逻辑之前,ServiceBean
的 id
属性已经被设置,这导致该 BeanPostProcessor
中的 id
属性设置逻辑并不会被执行。结合 Dubbo 2.7.5 之后的 Git 提交记录,我们知道在 2.7.5 之后的 do not set default id (#6236) · apache/dubbo@a0be776 · GitHub 这一次 Git 提交中,将设置 id
的部分进行了注释,猜测该补丁在未设置 ServiceBean
的 id
属性时才能正常使用,所以我对该补丁进行了调整,即将判断 id
属性是否设置的部分进行了移除,调整为强制覆盖,以修复无法覆盖 ServiceBean
实例的 id
属性的问题,为了避免该补丁产生其他影响,同时将未使用的代码进行了移除,并且限制仅覆盖所有 ServiceBean
的 id
属性,减少其影响面,调整后的代码如下:
1 | /** |
在进行了如上的调整之后,成功支持了 Dubbo 2.7.5 @Service 注解方式使用服务分组特性。记录该问题是因为我们应用中的 Dubbo 已经做了部分调整,不能轻易升级,所以尽量在不升级 Dubbo 版本的情况下处理该问题。
Reference
注解版接口多实现分组方式bug · Issue #6383 · apache/dubbo · GitHub
对同个接口的不同实现通过@Service设置不同group无法生效,zk中只注册了其中一个group。 · Issue #6283 · apache/dubbo · GitHub
Improve the readability of the getOrder method by tianshuang · Pull Request #9361 · apache/dubbo · GitHub