Dubbo Spring Cloud No provider 问题分析

Dubbo Spring Cloud No provider 问题分析

No provider 问题复现`

书接上文 Spring Cloud Dubbo 整合 Consul 注册中心 后偶尔发现consumer调用的时候会产生no provider如下错误:

org.apache.dubbo.rpc.RpcException: No provider available from registry localhost:9090 for service xyz.tiecode.provider.api.BizService on consumer ip.ip.ip.ip use dubbo version 2.7.8, please check status of providers(disabled, not registered or in blacklist).
    at org.apache.dubbo.registry.integration.RegistryDirectory.doList(RegistryDirectory.java:599) ~[dubbo-2.7.8.jar:2.7.8]

这个错误第一反应先找到provider,然后去检查provider健在,然后看下consul,服务正常注册。刚开始只是偶尔出现,后来出现的频率越来越高,仔细研究终找到100%复现规律,步骤如下:

consumer 和 provider 都是up状态,然后重启provider,启动完成后调用consumer就会报错,这个时候再重启一下consumer,恢复正常调用

问题分析

forbidden

沿着报错看跟踪一下问题代码发现 org.apache.dubbo.registry.integration.RegistryDirectory#doList 中由于 forbidden=true导致主动抛出异常。

doList

forbidden是true注释中说有2中可能:1. No service provider 2. Service providers are disabled

分析代码

跟踪代码发现

  1. forbidden 是在org.apache.dubbo.registry.integration.RegistryDirectory#refreshInvoker 中设置

  2. 上述方法由org.apache.dubbo.registry.integration.RegistryDirectory#notify 调用, 重启privider的时候,在consumer打断点发现第一个参数url协议是empty,所以设置forbidden

    notify方法

  3. 再往上跟踪,发现com.alibaba.cloud.dubbo.registry.GenearalServiceSubscribeHandler#getProviderURLs中参数com.alibaba.cloud.dubbo.registry.GenearalServiceSubscribeHandler#providers是空

    请添加图片描述

  4. prividers参数是空可以理解,毕竟provider重启了,服务下线。 但是问题是为什么当provider正常上线以后未恢复正常

  5. 继续跟踪发现com.alibaba.cloud.dubbo.registry.GenearalServiceSubscribeHandler#providers属性在2中情况下会设置,第一种是程序启动的时候,第二种是在com.alibaba.cloud.dubbo.registry.DubboCloudRegistry#refreshGeneralServiceInfo方法中。第一种程序启动不需要关心不是我们这个场景,原因是重启一下consumer后恢复正常。

  6. 再跟踪发现,refreshGeneralServiceInfo是在接收到com.alibaba.cloud.dubbo.registry.DubboCloudRegistry#onApplicationEvent事件才会进行调用。

  7. 继续往上跟踪发现最终是接收 com.alibaba.cloud.dubbo.autoconfigure.DubboServiceDiscoveryAutoConfiguration#onHeartbeatEvent事件才会进行刷新instance的操作。

  8. HeartbeatEvent 事件是由org.springframework.cloud.consul.discovery.ConsulCatalogWatch#catalogServicesWatch发出的,走的协议是consul的/v1/catalog/services,也就是检查服务的注册变更。返回的数据如下, 并没有实例信息。

    1. ➜  ~ curl http://localhost:8500/v1/catalog/services
      {
          "consul": [],
          "demo-consumer": [
              "secure=false",
              "dubbo.metadata-service.urls=[ \"xxxxxxxxxxxxxx" ]",
              "dubbo.metadata.revision=0"
          ],
          "demo-provider": [
              "dubbo.protocols.dubbo.port=20881",
              "dubbo.metadata.revision=E5DC0DD9469509708A7E0A0EE5D93FB2",
              "secure=false",
              "dubbo.metadata-service.urls=[ \"dubbo://xxxxxxxxx\" ]"
          ]
      }
      
  9. 回过头分析com.alibaba.cloud.dubbo.autoconfigure.DubboServiceDiscoveryAutoConfiguration#onHeartbeatEvent方法发现, 当接收到服务的变更以后,调用#getInstances方法获取实例,此方法最终走的consul接口是/v1/health/service/,这个接口会返回那些实例是健康的。

  1. com.alibaba.cloud.dubbo.autoconfigure.DubboServiceDiscoveryAutoConfiguration#onHeartbeatEvent方法中还有一个Predicate 也就是com.alibaba.cloud.dubbo.autoconfigure.DubboServiceDiscoveryAutoConfiguration#defaultHeartbeatEventChangePredicate分析发现只有当consul服务变更的时候才会走这个方法,才会调用上述的 getInstances 的方法,而不是每次接收到心跳以后更新实例。

  2. 再回顾一下服务注册流程,先走consul的/catalog/register接口,注册服务,然后consul再进行服务健康检查,instance的状态才会是passing的。

  3. 所以上述dubbo处理instance的流程是

    1. 当服务注册后consumer心跳catalog/services发现服务变更,然后走com.alibaba.cloud.dubbo.autoconfigure.DubboServiceDiscoveryAutoConfiguration#defaultHeartbeatEventChangePredicate方法设置版本号,然后走#getInstances方法,这个时候#getInstances返回0,应为服务还不是passing状态。

    2. 当consumer第二次HeartbeatEvent心跳后,这个时候provider在consul的状态可能已经passing了,但是#getInstances方法被#defaultHeartbeatEventChangePredicate拦截,原因是心跳中的版本号不会再变, 无法继续往下走。

    3. 第N次心跳,依旧是上述问题,consumer始终无法更新provider实例列表。

      心跳接口

尝试解决

让上述#defaultHeartbeatEventChangePredicate永远返回true,不去校验版本号。根本问题原因是接收到心跳事件后,consul实例列表中passing的实例为空

@Bean
public Predicate heartBeatEventChangePredicate() {
  return event -> true;
}

目前看没什么问题,如果大佬有其他高见欢迎指点。

另外可能有人担心大量的刷consul的 health/service接口会不会导致consul压力变大,这里笔者认为无需考虑,真正生产环境使用的时候consul采用server client 的部署模式,而且1个实例对应一个client类似sidecar的部署模式。consul的server和client内部有自己的通信协议。所以这里consul的压力可以不必多虑。

你可能感兴趣的:(Dubbo Spring Cloud No provider 问题分析)