自定义Filter引起的 Failed to invoke the method 问题解决

一:问题的表现和最终结论

1 问题的表现:

出问题的项目,使用的分布式服务框架是dubbo。正常时启动项目需要启动多个服务,这里暂时定义为 server-task,server-application,server-pay 三个服务。在不知道哪一次的代码迭代后,出现了问题。服务在启动时都很顺利,没有明显报错,但是 server-application,server-pay 这两个服务都无法正常调用 server-task 服务下的 TaskRpcService的方法,且报出如下错误:

Internal server error: Failed to invoke the method getTaskInfo in the service com.test.server-task.ITaskRpcService. No provider available for the service com.test.server-task.ITaskRpcService from registry 127.0.0.1:2181 on the consumer xxx.xxx.x.xxx using the dubbo version 2.8.4. Please check if the providers have been started and registered.

乍一看,以为是 server-task 挂了或者服务启动过程有问题。在此假设的基础上,尝试重启task服务,发现问题确实解决了,调用server-task 服务下的 TaskRpcService 的方法时,不再报错。

然而,一旦其他两个服务有任意一个重启,或所有服务重启,问题会重新重现,仍然无法正常调用 server-task 服务下的 TaskRpcService 的方法,此时,又需要再次重启 server-task服务。每次项目有改动,需要启动服务时,都要对 server-task服务进行单独照顾,很麻烦。。。。同时,一直不处理,也担心会有其他的隐患。虽然最终服务还是可以正常跑起来,但是作为技术人(小白),不整的通透,怎么会进步嘞?

所以,在本地跑服务,开始了改BUG的道路。。。。

2 最终结论:

在我们项目中有一个自定义的Filter类,这里命名为 CustomFilter 类,实现了dubbo 的Fitler接口,dubbo服务会在启动时执行RegistryDirectory中的refreshInvoker方法,构建一个Filter链,同时为消费者所调用的服务创建一个 methodInvokerMap,这个 methodInvokerMap 会在消费者调用服务提供者时,生成invoker。正是构建Filter链时,这个CustomFilter 类中成员变量使得CustomFilter无法初始化导致链构建失败,methodInvokerMap为null,导致报 Failed to invoke the method 的错误。

二:解决过程详述

1 看日志

出了问题,第一反应便是查看日志。关键性的报错内容如下:

com.alibaba.dubbo.rpc.cluster.support.AbstractClusterInvoker.checkInvokers(AbstractClusterInvoker.java:251)
com.alibaba.dubbo.rpc.cluster.support.FailoverClusterInvoker.doInvoke(FailoverClusterInvoker.java:55) 
com.alibaba.dubbo.rpc.cluster.support.AbstractClusterInvoker.invoke(AbstractClusterInvoker.java:227) 
com.alibaba.dubbo.rpc.cluster.support.wrapper.MockClusterInvoker.invoke(MockClusterInvoker.java:72)
com.alibaba.dubbo.rpc.proxy.InvokerInvocationHandler.invoke(InvokerInvocationHandler.java:52)

日志报错信息

2 问题溯源

根据日志中的报错信息,首先找到 checkInvokers 方法,如下所示:
checkInvokers
invokers==null 或者 invokers.size() == 0,导致出现了日志中的报错信息。根据日志中的信息,继续追查上一级FailoverClusterInvoker.doInvoker。FailoverClusterInvoker.doInvoker如下所示:
FailoverClusterInvoker
在FailoverClusterInvoker的doInvoker方法中,invokers仍然是外部传入的。继续追查上一级AbstractClusterInvoker.invoke。AbstractClusterInvoker.invoke如下:
在这里插入图片描述
可以看出invokers由invocation经list()方法产生。

dubbo中 invocation 和 invoker 是什么?

这里简单介绍一下invocation和invoker的定义:

Invocation:是会话域,它持有调用过程中的变量,比如方法名,参数等。其接口如下:
invocation
Invoker:是实体域,它是 Dubbo 的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体,可向它发起 invoke 调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。

继续往list()方法中看:

可以看出有两处影响了invokers,分别为doList(invocation)和router.route()方法。对于router的作用,简单查阅了大佬们的文章,具体如下图:
router

看Router的介绍,可以分析出invokers大概率是在doList()时为 null 或 size为0 。

继续跟进doList(),并进入其子类:RegistryDirectory。
RegistryDirectory
可以看出,invokers是从methodInvokerMap中获得。

那么是否是因为methodInvokerMap的原因导致的呢?

跑DEBUG验证一下:
methodInvokerMap
果然是 methodInvokerMap==null 搞的鬼。

正常情况下methodInvokerMap是这样的:
正常情况
到这里,可确定是TaskRpcService(开头出问题的service)的methodInvokerMap为null,导致问题出现。

那么methodInvokerMap什么时候生成呢?
作为一个不懂dubbo奥秘的小白,就通过以下内容断定 methodInvokerMap 的生成方法:

  1. 百度了methodInvokerMap,有如下说法:
    这个map存储的key是dubbo方法名,value是提供者的访问方式。methodInvokerMap是通过refreshInvoker方法生成的。
  2. 又进行了一番百度,得知,methodInvokerMap所在的类–>RegistryDirectory,有如下作用:
    1. 获取 invoker 列表
    2. 监听注册中心的变化
    3. 刷新 invokers。
      a. 获取 invoker 列表,RegistryDirectory 实现的父类抽象方法 doList,其目的就是得到 invoker 列表,而其内部的实现主要是做了层方法名的过滤,通过方法名找到对应的 invokers。(这里的doList就是我们追溯问题时遇到的那个)
      b. 监听注册中心的变化,通过实现 NotifyListener 接口能感知到注册中心的数据变更,这其实是在服务引入的时候就订阅的。RegistryDirectory 定义了三种集合,分别是 invokerUrls 、routerUrls 、configuratorUrls 分别处理相应的配置变化,然后对应转化成对象。)
      c. 刷新 Invoker 列表,其实就是根据监听变更的 invokerUrls 做一波操作,其方法名为refreshInvoker(invokerUrls) ,会根据配置更新 invokers。(监听到注册中心变化后,对第二条作用中提到的invokerUrls进行操作)
  3. 在RegistryDirectory类中搜索methodInvokerMap,查看到底是在哪里赋过值。
    methodInvokerMap
    通过以上3条证据,可以肯定,有且仅有 refreshInvoker 方法会为methodInvokerMap赋值

3 问题定位

在这里插入图片描述
通过反复的启动项目进行debug,发现问题在执行toInvokers()方法后 newUrlInvokerMap == null ,其原因为 toInvokers 内部执行时抛出异常,具体如下:
在这里插入图片描述
这里,可以确定问题出在 protocol.refer() 方法。

查看protocol接口,根据其SPI注解,可以找到实现类 DubboProtocol:
在这里插入图片描述
在这里插入图片描述
跟进====>

这里没有直接进入DubboProtocol,而是先进入了ProtocolFIiterWrapper的refer()方法,如下图所示:
在这里插入图片描述
然后进入DubboProtocol.refer()方法,执行完后,执行ProtocolFIiterWrapper的buildInvokerChain()方法。

这里,需要进行说明:
Protocol存在两个wrapper类,分别为:
1 com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper、
2 com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper。

在dubbo中存在wrapper类的类会被wrapper实例包装后返回,因此在protocol.refer方法调用的时候,会先经过wrapper类

ListenerInvokerWrapper依然不是真正的调用者,它主要是监听了invoker的创建与销毁事件,它维护的invoker为经过ProtocolFilterWrapper转换过的Invoker,该Invoker在执行前需要先经过filter链的处理

在上图中,buildInvokerChain()方法,看字面意思,就是创建一个链

继续跟进----->
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
最终,方法执行到上图中红框位置边报错,错误逐层抛出,最终导致了调用远程服务时报错。

4 错误分析,解决问题

在这里插入图片描述
变量 EXTENSION_INSTANCES 为一个 ConcurrentHashMap,由其源码和报错位置的debug截图可知,报错是clazz.newInstance()为null。即 CustomerFilter 无法在dubbo启动,调用refreshInvoker方法 为 ITaskRpcService 进行相关操作时,进行初始化。

但是这里有个问题,clazz是个Class类,在报错之前已经被拿到了,说明 CustomerFilter 已经被加载,但是被加载的类不能初始化,why?看看代码再说。CustomFilter 的代码简化版如下:
在这里插入图片描述
类在初始化时,对内部代码的加载顺序为:
在这里插入图片描述
CustomerFilter 的成员变量是由dubbo提供的 ServiceBean 创建的。那么,是否可能是 ServiceBean 还没有被加载或初始化,导致成员变量初始化失败,最终使得 CustomerFilter 初始化失败。

将上图中三个成员变量注释掉,重新启动服务,没报错,可以正常调用 server-task 服务。

成了!

三:总结

1 知识点汇总

1.1 正常的dubbo消费者调用provider的服务,其流程如何?

dubbo中的消费者去调用服务提供者时,其大致流程如下所示:
dubbo服务调用
因为问题发生在消费者调用server-task服务时,由刚刚展示的源码可以看到invoker并没有生成,而是为null,那么问题发生的阶段很可能就在上图中protocol->Invoker阶段。后续对于问题的分析理解,也是验证了这个推测。

1.2 dubbo中invocation和invoker的大致定义;

1.3 dubbo消费者调用服务部分流程;

1.4 dubbo中 RegistryDirectory 的作用概述;

1.5 dubbo中ProtocolFIiterWrapper;

1.6 dubbo中 SPI 机制;

1.7 JAVA类初始化时内部加载顺序;

2 心得

对于一个dubbo才接触2个月的小菜鸡,在团队的帮助下最终解决了这么一个技术性问题,实在是很开心,哈哈哈~

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值