现象

在前后端联调过程中,有同学向我反馈Docker启动起来了,但是接口出现了404,并同时向我展示了Tomcat的日志.
表现如下
image.png
错误堆栈如下
image.png

是不是修改了配置导致的?

  1. 这个同学的项目配置特殊一点,它没有使用ContextListener来加载Spring的配置,所有的配置都在Servlet中进行了加载。

而Tomcat的默认配置则是,即使Servlet加载失败了,Tomcat依旧会启动成功。⚠️ 此时jsp的defaultServlet是正常启动的,因此可以通过我们的流量检查。

but,这个问题在2019年尾就已经被修改了,正确的配置了 failCtxIfServletStartFails=true . 在Servlet加载失败的时候,让Tomcat启动失败。我去查看配置的时候它正是true。

题外话,如果Servlet真的加载失败了,会出现一行日志
One or more Servlets failed to load on startup. Full details will be found in the appropriate container log file

所以,这次的问题不是由于Tomcat的配置引起的,那么为什么在Tomcat启动的时候没有把异常抛出来,而是等待流量接收的时候才爆发呢。

寻找真实原因

由于通过了K8S的就绪检查和存活检测,基本上可以肯定Tomcat正常启动了。然后出现了一个转折。该同学向我提供了一个图
image.png
这个 ITradeServiceDubbo 是该项目暴露的一个Dubbo服务,又在Controller中引用了这个Dubbo服务,然后加载的是Dubbo的ServiceBean, 这个Bean还成功实例化了,只是在进行类型检测的时候失败了。

这一步中间又让我误以为是Dubbo在处理的过程中存在Bug导致报错了。最后的真实原因不是这个。

然后我看他的代码,发现了如下一个数据
image.png
使用的是 prototype 的Scope , 此时问题就很明显了,正式因为这个Scope的问题。

解答

Scope 常用的有两种

  • singleton: 一个bean在一个Spring IOC容器中只实例化一个实例
  • prototype: 如果在Bean A中注入了带有prototype实例B的时候,每次实例化A都会实例化B,SpringMVC处理prototype的Controller每次都会重新生成。
    • prorotype等同于getBean的一个过程

又因为他的配置确实有问题,所以会导致,每次实例化Controller的时候都会报错,从而Servlet处理失败,这种情况下,Tomcat会认为是404.
image.png
prototype类型的Bean是脱离Spring本身的生命周期的。 也不会进行实例化,更不会调用 initdestory 等方法。所以它可以跳过Tomcat的启动检测。从而逃离K8S的就绪检测和存活检测

prototype类型的Bean如果被注入到singleton的bean中的时候,prototype的bean在实例化的时候就会被注入,被引用N次就会实例化N次,这里可以通过 jmap histo pid |grep bean 来进行验证。

回顾

一开始项目是没有问题的,只是昨天有同学错误的使用了Dubbo,然后引发了这个问题。 一开始还以为是Tomcat镜像的配置被改动了

主要是存在以下两个错误

  • Dubbo的错误使用
  • Scope(“prototype”) 的理解没有到位

官方文档