一、前言
自从使用springboot后,好久没有下载tomcat了,今天遇到一甲方爸爸要求使用外置tomcat,忽感菊花一紧,遇到安全意识较强的甲方了(此处由于笔者曾经供职过甲方,为什么需要用外置tomcat此处啰嗦一句:外置tomcat才能更好的控制安全-安全基线版本,不然springboot内部内置的tomcat甲方的技术管理部如何控制tomcat基线版本?),没办法,老老实实把客户分支代码打包方式修改为war吧。
二、出现问题
老老实实修改打包方式,增加SpringBootServletInitializer子类,然后就满怀期待打包,打包生成war一切都很顺利,对应ServletInitializer子类如下:
/**
* 〈Servlet初始化〉<br>
* 〈外置tomcat启动引导〉
*
* @author ghostp
* @see [相关类/方法](可选)
* @since [产品/模块版本] (可选)
*/
@Configuration
public class ServletInitializer extends SpringBootServletInitializer implements WebApplicationInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
setRegisterErrorPageFilter(false);
builder.sources(DroolsApplication.class);
return super.configure(builder);
}
}
放到外置tomcat中,开始启动也是一切OK,并没有出现网上说的需要排内置tomcat问题。
可是启动后等待半天(此处项目工程启动需要加载较多信息入缓存和内存,故耗时较高),看一切都还OK,项目中启动加载数据和定时任务运行也是杠杠的(查看项目日志),自认为一切OK,访问 http://localhost:8081后发现长期等待,页面没有任何反应,查看页面tab一直在转圈,打开页面开发者工具,看到一直在等待
这么看来是项目根本就没有启动完成?还是就是慢?尝试等待页面响应,再次等了10分钟,惊喜的发现没有出现访问超时错误,竟然还在等待。。。。这TM让人奔溃了。
没办法,继续分析吧。
三、分析之路
按照现象分析,肯定是项目没有启动完成,tomcat一直在等待,那么尝试使用tomcat控制台模式发布war是否可行呢?立马进行测试,此处需要注意,tomcat默认是没有用户的,需要大家配置,可以参见:Tomcat9配置进入manager webapp和HostManager页面_zjxht62的博客-优快云博客_tomcat9 webapp
然后兴冲冲的进入manager管理进行war上传,
可惜,上传出错,
通过查看tomcat日志,发现是war包过大导致。
21-Jul-2022 17:42:54.392 严重 [http-nio-8081-exec-10] org.apache.catalina.core.ApplicationContext.log HTMLManager: 失败 - 部署上传失败,异常信息:[org.apache.tomcat.util.http.fileupload.impl.SizeLimitExceededException: the request was rejected because its size (284113069) exceeds the configured maximum (52428800)]
java.lang.IllegalStateException: org.apache.tomcat.util.http.fileupload.impl.SizeLimitExceededException: the request was rejected because its size (284113069) exceeds the configured maximum (52428800)
at org.apache.catalina.connector.Request.parseParts(Request.java:2974)
at org.apache.catalina.connector.Request.parseParameters(Request.java:3263)
at org.apache.catalina.connector.Request.getParameter(Request.java:1141)
at org.apache.catalina.connector.RequestFacade.getParameter(RequestFacade.java:381)
at org.apache.catalina.filters.CsrfPreventionFilter.doFilter(CsrfPreventionFilter.java:127)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.apache.catalina.filters.HttpHeaderSecurityFilter.doFilter(HttpHeaderSecurityFilter.java:126)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:660)
at org.apache.catalina.valves.RequestFilterValve.process(RequestFilterValve.java:378)
at org.apache.catalina.valves.RemoteAddrValve.invoke(RemoteAddrValve.java:56)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:687)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:360)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:399)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:890)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1743)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)
Caused by: org.apache.tomcat.util.http.fileupload.impl.SizeLimitExceededException: the request was rejected because its size (284113069) exceeds the configured maximum (52428800)
at org.apache.tomcat.util.http.fileupload.impl.FileItemIteratorImpl.init(FileItemIteratorImpl.java:161)
at org.apache.tomcat.util.http.fileupload.impl.FileItemIteratorImpl.getMultiPartStream(FileItemIteratorImpl.java:205)
at org.apache.tomcat.util.http.fileupload.impl.FileItemIteratorImpl.findNextItem(FileItemIteratorImpl.java:224)
at org.apache.tomcat.util.http.fileupload.impl.FileItemIteratorImpl.<init>(FileItemIteratorImpl.java:142)
at org.apache.tomcat.util.http.fileupload.FileUploadBase.getItemIterator(FileUploadBase.java:252)
at org.apache.tomcat.util.http.fileupload.FileUploadBase.parseRequest(FileUploadBase.java:276)
at org.apache.catalina.connector.Request.parseParts(Request.java:2932)
... 31 more
这时候N年前使用tomcat经验派上用途了,直接把war包提交到tomcat的webapps文件夹下面,然后不断刷新manager页面,终于看到自己上传的项目目录,
然后点击启动,(出去抽根烟,平静下心情)等待一段时间后发现是OK的,启动成功,这个... ...脑裂了,难道跟甲方爸爸说用控制台模式启动?服务器重启怎么办?这必须要解决啊,实施不吐槽,自己也过意不去。
四、项目源码分析
利索的,赶紧看看源码吧。
1、springboot的application.yml里面的端口跟tomcat端口一致,嗯....这里难道有冲突?不应该啊,修改后测试下,把application.yml的server.port端口修改为8080,再次启动,OK,启动成功了,此处再次去抽一支。
2、抽完回来后测试说项目很多功能包系统异常.......F**K,赶紧看看什么异常吧,看到日志中很多restTemplate远程调用不通,调用的端口号是8080。这下感觉醍醐灌顶了,原来项目为了微服务化处理,内部采用了远程调用非耦合工程,而本次给甲方爸爸的版本是代码库在一起的,统一打包的模式,配置服务化的配置中使用了${server.port}来获取了统一工程远程服务的地址。那就说明,项目启动过程中调用了远程服务?原来server.port的端口和tomcat端口不一致,restTemplate会自动失败,而如果端口一致,会出现死循环。
按照这个思路进行了排查(24个服务配置,按照二分法查找方式进行测试配置),终于在4次测试后发现了某个服务在项目启动过程中调用了,此处出现了死循环,启动中调用了远程服务获取数据,此处形成了死循环。
tomcat等待项目启动结果,项目main线程远程调用了tomcat服务。。。。
而如果使用tomcat控制台发布,此处的tomcat主线程启动是成功状态,项目内部调用是OK的情况,故不出现死循环。
通过日志找到启动过程中远程调用的地方,发现确实有远程调用,先注释掉。
然后再进行启动,OK,tomcat启动成功,项目功能验证OK。
五、总结
问题处理完成了,此处问题出现也很奇葩,项目后期开发过程中需要进行代码审核,不能再出现此种现象。
tomcat目前的这种机制是否可以进行更改,端口要不在启动同步项目启动完成后开放,要不主线程不要依赖项目启动,至少保障访问端口不要一直等待。